diff --git a/.copier-answers.yml b/.copier-answers.yml
index f844b711..7ad1c140 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
-_commit: 0.4.3
+_commit: 0.7.1
_src_path: gh:pawamoy/copier-pdm.git
author_email: pawamoy@pm.me
author_fullname: "Timoth\xE9e Mazzucotelli"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b5750cd7..019502b4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,9 +27,9 @@ jobs:
uses: actions/checkout@v2
- name: Set up PDM
- uses: pdm-project/setup-pdm@v2
+ uses: pdm-project/setup-pdm@v2.5
with:
- python-version: 3.8
+ python-version: "3.8"
- name: Set cache variables
id: set_variables
@@ -49,15 +49,13 @@ jobs:
run: pdm lock
- name: Install dependencies
- run: |
- pdm install -G duty -G docs -G quality -G typing
- pip install safety
+ run: pdm install -G duty -G docs -G quality -G typing -G security
- name: Check if the documentation builds correctly
run: pdm run duty check-docs
- name: Check the code quality
- run: pdm run duty check-code-quality
+ run: pdm run duty check-quality
- name: Check if the code is correctly typed
run: pdm run duty check-types
@@ -69,8 +67,17 @@ jobs:
strategy:
matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- python-version: [3.6, 3.7, 3.8, 3.9]
+ os:
+ - ubuntu-latest
+ - macos-latest
+ - windows-latest
+ python-version:
+ - "3.6"
+ - "3.7"
+ - "3.8"
+ - "3.9"
+ - "3.10"
+ - "3.11-dev"
runs-on: ${{ matrix.os }}
@@ -79,7 +86,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up PDM
- uses: pdm-project/setup-pdm@v2
+ uses: pdm-project/setup-pdm@v2.5
with:
python-version: ${{ matrix.python-version }}
diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile
new file mode 100644
index 00000000..33f285c2
--- /dev/null
+++ b/.gitpod.dockerfile
@@ -0,0 +1,7 @@
+FROM gitpod/workspace-full
+USER gitpod
+ENV PIP_USER=no
+ENV PYTHON_VERSIONS=
+RUN pip3 install pipx; \
+ pipx install pdm; \
+ pipx ensurepath
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 00000000..23a3c2b7
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,13 @@
+vscode:
+ extensions:
+ - ms-python.python
+
+image:
+ file: .gitpod.dockerfile
+
+ports:
+- port: 8000
+ onOpen: notify
+
+tasks:
+- init: make setup
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f1c2b98..c5593cd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [0.17.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.17.0) - 2021-12-27
+
+[Compare with 0.16.2](https://github.com/mkdocstrings/mkdocstrings/compare/0.16.2...0.17.0)
+
+### Features
+- Add `show_signature` rendering option ([024ee82](https://github.com/mkdocstrings/mkdocstrings/commit/024ee826bb6f0aa297ba857bc18075d6f4162cad) by Will Da Silva). [Issue #341](https://github.com/mkdocstrings/mkdocstrings/issues/341), [PR #342](https://github.com/mkdocstrings/mkdocstrings/pull/342)
+- Support Keyword Args and Yields sections ([1286427](https://github.com/mkdocstrings/mkdocstrings/commit/12864271b7f997af7b421a834919b1e686793905) by Timothée Mazzucotelli). [Issue #205](https://github.com/mkdocstrings/mkdocstrings/issues/205) and [#324](https://github.com/mkdocstrings/mkdocstrings/issues/324), [PR #331](https://github.com/mkdocstrings/mkdocstrings/pull/331)
+
+### Bug Fixes
+- Do minimum work when falling back to re-collecting an object to get its anchor ([f6cf570](https://github.com/mkdocstrings/mkdocstrings/commit/f6cf570255df17db1088b6e6cd94bcc823b3b17f) by Timothée Mazzucotelli). [Issue #329](https://github.com/mkdocstrings/mkdocstrings/issues/329), [PR #330](https://github.com/mkdocstrings/mkdocstrings/pull/330)
+
+### Code Refactoring
+- Return multiple identifiers from fallback method ([78c498c](https://github.com/mkdocstrings/mkdocstrings/commit/78c498c4a6cfc33cc6ceab9829426bd64e518d44) by Timothée Mazzucotelli). [Issue mkdocstrings/autorefs#11](https://github.com/mkdocstrings/autorefs/issues/11), [PR #350](https://github.com/mkdocstrings/mkdocstrings/pull/350)
+
+
## [0.16.2](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.16.2) - 2021-10-04
[Compare with 0.16.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.16.1...0.16.2)
diff --git a/Makefile b/Makefile
index 97aa6931..58291575 100644
--- a/Makefile
+++ b/Makefile
@@ -4,13 +4,14 @@ SHELL := bash
DUTY = $(shell [ -n "${VIRTUAL_ENV}" ] || echo pdm run) duty
args = $(foreach a,$($(subst -,_,$1)_args),$(if $(value $a),$a="$($a)"))
-check_code_quality_args = files
+check_quality_args = files
docs_serve_args = host port
release_args = version
test_args = match
BASIC_DUTIES = \
changelog \
+ check-dependencies \
clean \
coverage \
docs \
@@ -21,9 +22,7 @@ BASIC_DUTIES = \
release
QUALITY_DUTIES = \
- check \
- check-code-quality \
- check-dependencies \
+ check-quality \
check-docs \
check-types \
test
@@ -32,10 +31,19 @@ QUALITY_DUTIES = \
help:
@$(DUTY) --list
+.PHONY: lock
+lock:
+ @pdm lock
+
.PHONY: setup
setup:
@bash scripts/setup.sh
+.PHONY: check
+check:
+ @bash scripts/multirun.sh duty check-quality check-types check-docs
+ @$(DUTY) check-dependencies
+
.PHONY: $(BASIC_DUTIES)
$(BASIC_DUTIES):
@$(DUTY) $@ $(call args,$@)
diff --git a/README.md b/README.md
index cb207ff0..d914dc4f 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
[](https://mkdocstrings.github.io/)
[](https://pypi.org/project/mkdocstrings/)
[](https://anaconda.org/conda-forge/mkdocstrings)
+[](https://gitpod.io/#https://github.com/mkdocstrings/mkdocstrings)
[](https://gitter.im/mkdocstrings/community)
Automatic documentation from sources, for [MkDocs](https://mkdocs.org/).
@@ -80,7 +81,7 @@ Automatic documentation from sources, for [MkDocs](https://mkdocs.org/).
Numpy-style requires an extra dependency from `pytkdocs`: `pytkdocs[numpy-style]`.*
- **Admonition support in docstrings:** blocks like `Note:` or `Warning:` will be transformed
- to their [admonition](https://squidfunk.github.io/mkdocs-material/extensions/admonition/) equivalent.
+ to their [admonition](https://squidfunk.github.io/mkdocs-material/reference/admonitions/) equivalent.
*We do not support nested admonitions in docstrings!*
- **Every object has a TOC entry:** we render a heading for each object, meaning *MkDocs* picks them into the Table
@@ -91,10 +92,6 @@ Automatic documentation from sources, for [MkDocs](https://mkdocs.org/).
- **Source code display:** *mkdocstrings* can add a collapsible div containing the highlighted source code
of the Python object.
-## Roadmap
-
-See the [Feature Roadmap issue](https://github.com/mkdocstrings/mkdocstrings/issues/183) on the bugtracker.
-
## Requirements
*mkdocstrings* requires Python 3.6 or above.
@@ -119,13 +116,6 @@ pyenv global system 3.6.12
```
-This project currently only works with the Material theme of MkDocs.
-Therefore, it is required that you have it installed.
-
-```
-pip install mkdocs-material
-```
-
## Installation
With `pip`:
diff --git a/config/flake8.ini b/config/flake8.ini
index 2b50d854..e0a4cfbd 100644
--- a/config/flake8.ini
+++ b/config/flake8.ini
@@ -9,6 +9,10 @@ ignore =
A001
# missing docstring in magic method
D105
+ # multi-line docstring summary should start at the first line
+ D212
+ # does not support Parameters sections
+ D417
# whitespace before ':' (incompatible with Black)
E203
# redundant with E0602 (undefined variable)
diff --git a/config/mypy.ini b/config/mypy.ini
index e88e9042..814e2ac8 100644
--- a/config/mypy.ini
+++ b/config/mypy.ini
@@ -1,15 +1,5 @@
[mypy]
ignore_missing_imports = true
exclude = tests/fixtures/
-
-[mypy-docutils.*]
-ignore_missing_imports = true
-
-[mypy-markdown.*]
-ignore_missing_imports = true
-
-[mypy-toml.*]
-ignore_missing_imports = true
-
-[mypy-yaml.*]
-ignore_missing_imports = true
+warn_unused_ignores = true
+show_error_codes = true
diff --git a/duties.py b/duties.py
index 2dcc706c..879445e2 100644
--- a/duties.py
+++ b/duties.py
@@ -1,11 +1,14 @@
"""Development tasks."""
+import importlib
import os
import re
import sys
+import tempfile
+from contextlib import suppress
from functools import wraps
+from io import StringIO
from pathlib import Path
-from shutil import which
from typing import List, Optional, Pattern
from urllib.request import urlopen
@@ -103,7 +106,7 @@ def changelog(ctx):
)
-@duty(pre=["check_code_quality", "check_types", "check_docs", "check_dependencies"])
+@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies"])
def check(ctx):
"""Check it all!
@@ -113,7 +116,7 @@ def check(ctx):
@duty
-def check_code_quality(ctx, files=PY_SRC):
+def check_quality(ctx, files=PY_SRC):
"""Check the code quality.
Arguments:
@@ -131,22 +134,36 @@ def check_dependencies(ctx):
Arguments:
ctx: The context instance (passed automatically).
"""
- nofail = False
- safety = which("safety")
- if not safety:
- pipx = which("pipx")
- if pipx:
- safety = f"{pipx} run safety"
- else:
- safety = "safety"
- nofail = True
- ctx.run(
- f"pdm export -f requirements --without-hashes | {safety} check --stdin --full-report",
- title="Checking dependencies",
- pty=PTY,
- nofail=nofail,
+ # undo possible patching
+ # see https://github.com/pyupio/safety/issues/348
+ for module in sys.modules: # noqa: WPS528
+ if module.startswith("safety.") or module == "safety":
+ del sys.modules[module] # noqa: WPS420
+
+ importlib.invalidate_caches()
+
+ # reload original, unpatched safety
+ from safety.formatter import report
+ from safety.safety import check as safety_check
+ from safety.util import read_requirements
+
+ # retrieve the list of dependencies
+ requirements = ctx.run(
+ ["pdm", "export", "-f", "requirements", "--without-hashes"],
+ title="Exporting dependencies as requirements",
+ allow_overrides=False,
)
+ # check using safety as a library
+ def safety(): # noqa: WPS430
+ packages = list(read_requirements(StringIO(requirements)))
+ vulns = safety_check(packages=packages, ignore_ids="", key="", db_mirror="", cached=False, proxy={})
+ output_report = report(vulns=vulns, full=True, checked_packages=len(packages))
+ if vulns:
+ print(output_report)
+
+ ctx.run(safety, title="Checking dependencies")
+
def no_docs_py36(nofail=True):
"""Decorate a duty that builds docs to warn that it's not possible on Python 3.6.
@@ -184,14 +201,56 @@ def check_docs(ctx):
ctx.run("mkdocs build -s", title="Building documentation", pty=PTY)
-@duty
-def check_types(ctx):
+@duty # noqa: WPS231
+def check_types(ctx): # noqa: WPS231
"""Check that the code is correctly typed.
Arguments:
ctx: The context instance (passed automatically).
"""
- ctx.run(f"mypy --config-file config/mypy.ini {PY_SRC}", title="Type-checking", pty=PTY, progress=True)
+ # NOTE: the following code works around this issue:
+ # https://github.com/python/mypy/issues/10633
+
+ # compute packages directory path
+ py = f"{sys.version_info.major}.{sys.version_info.minor}"
+ pkgs_dir = Path("__pypackages__", py, "lib").resolve()
+
+ # build the list of available packages
+ packages = {}
+ for package in pkgs_dir.glob("*"):
+ if package.suffix not in {".dist-info", ".pth"} and package.name != "__pycache__":
+ packages[package.name] = package
+
+ # handle .pth files
+ for pth in pkgs_dir.glob("*.pth"):
+ with suppress(OSError):
+ for package in Path(pth.read_text().splitlines()[0]).glob("*"): # noqa: WPS440
+ if package.suffix != ".dist-info":
+ packages[package.name] = package
+
+ # create a temporary directory to assign to MYPYPATH
+ with tempfile.TemporaryDirectory() as tmpdir:
+
+ # symlink the stubs
+ ignore = set()
+ for stubs in (path for name, path in packages.items() if name.endswith("-stubs")): # noqa: WPS335
+ Path(tmpdir, stubs.name).symlink_to(stubs, target_is_directory=True)
+ # try to symlink the corresponding package
+ # see https://www.python.org/dev/peps/pep-0561/#stub-only-packages
+ pkg_name = stubs.name.replace("-stubs", "")
+ if pkg_name in packages:
+ ignore.add(pkg_name)
+ Path(tmpdir, pkg_name).symlink_to(packages[pkg_name], target_is_directory=True)
+
+ # create temporary mypy config to ignore stubbed packages
+ newconfig = Path("config", "mypy.ini").read_text()
+ newconfig += "\n" + "\n\n".join(f"[mypy-{pkg}.*]\nignore_errors=true" for pkg in ignore)
+ tmpconfig = Path(tmpdir, "mypy.ini")
+ tmpconfig.write_text(newconfig)
+
+ # set MYPYPATH and run mypy
+ os.environ["MYPYPATH"] = tmpdir
+ ctx.run(f"mypy --config-file {tmpconfig} {PY_SRC}", title="Type-checking", pty=PTY)
@duty(silent=True)
@@ -282,7 +341,7 @@ def release(ctx, version):
ctx.run("git push --tags", title="Pushing tags", pty=False)
ctx.run("pdm build", title="Building dist/wheel", pty=PTY)
ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY)
- docs_deploy.run() # type: ignore
+ docs_deploy.run()
@duty(silent=True)
@@ -305,16 +364,6 @@ def test(ctx, match: str = ""):
ctx: The context instance (passed automatically).
match: A pytest expression to filter selected tests.
"""
- try: # noqa: WPS229
- import sphinx # isort:skip # noqa: F401
- import docutils # isort:skip # noqa: F401
- except ImportError:
- py = f"{sys.version_info.major}.{sys.version_info.minor}"
- ctx.run(
- f"pip install sphinx docutils --no-deps -t __pypackages__/{py}/lib",
- title="Installing additional test dependencies",
- )
-
py_version = f"{sys.version_info.major}{sys.version_info.minor}"
os.environ["COVERAGE_FILE"] = f".coverage.{py_version}"
ctx.run(
diff --git a/pyproject.toml b/pyproject.toml
index 5e3fa81c..ac0bd9de 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,27 +4,40 @@ build-backend = "pdm.pep517.api"
[project]
name = "mkdocstrings"
-version = {use_scm = true}
description = "Automatic documentation from sources, for MkDocs."
authors = [{name = "Timothée Mazzucotelli", email = "pawamoy@pm.me"}]
license = {file = "LICENSE"}
readme = "README.md"
-requires-python = ">=3.6"
+requires-python = ">=3.6.2"
keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"]
-dynamic = ["version", "classifiers"]
+dynamic = ["version"]
classifiers = [
"Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
"License :: OSI Approved :: ISC License (ISCL)",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Topic :: Documentation",
+ "Topic :: Software Development",
+ "Topic :: Software Development :: Documentation",
+ "Topic :: Utilities",
"Typing :: Typed",
]
dependencies = [
- "Jinja2>=2.11.1,<4.0",
- "Markdown~=3.3",
- "MarkupSafe>=1.1,<3.0",
- "mkdocs~=1.2",
- "mkdocs-autorefs>=0.1,<0.4",
- "pymdown-extensions>=6.3,<10.0",
- "pytkdocs>=0.2.0,<0.13.0",
+ "Jinja2>=2.11.1",
+ "Markdown>=3.3",
+ "MarkupSafe>=1.1",
+ "mkdocs>=1.2",
+ "mkdocs-autorefs>=0.1",
+ "pymdown-extensions>=6.3",
+ "pytkdocs>=0.14.0",
]
[project.urls]
@@ -40,56 +53,64 @@ Funding = "https://github.com/sponsors/mkdocstrings"
[project.entry-points."mkdocs.plugins"]
mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin"
-
-[project.optional-dependencies]
[tool.pdm]
package-dir = "src"
includes = ["src/mkdocstrings"]
+version = {use_scm = true}
[tool.pdm.dev-dependencies]
-duty = ["duty~=0.6"]
+duty = ["duty>=0.7"]
docs = [
- "mkdocs-coverage~=0.2; python_version >= '3.7'",
- "mkdocs-gen-files~=0.3; python_version >= '3.7'",
- "mkdocs-literate-nav~=0.4; python_version >= '3.7'",
- "mkdocs-material~=7.1; python_version >= '3.7'",
- "mkdocs-section-index~=0.3; python_version >= '3.7'",
- "toml~=0.10; python_version >= '3.7'",
+ "mkdocs-coverage>=0.2; python_version >= '3.7'",
+ "mkdocs-gen-files>=0.3; python_version >= '3.7'",
+ "mkdocs-literate-nav>=0.4; python_version >= '3.7'",
+ "mkdocs-material>=7.3; python_version >= '3.7'",
+ "mkdocs-section-index>=0.3; python_version >= '3.7'",
+ "toml>=0.10; python_version >= '3.7'",
]
format = [
- "autoflake~=1.4",
- "black~=20.8b1",
- "isort~=5.8",
+ "autoflake>=1.4",
+ "black>=21.10b0",
+ "isort>=5.10",
]
maintain = [
# TODO: remove this section when git-changelog is more powerful
- "git-changelog~=0.4",
+ "git-changelog>=0.4",
]
quality = [
- "darglint~=1.7",
- "flake8-bandit~=2.1",
- "flake8-black~=0.2",
- "flake8-bugbear~=21.3",
- "flake8-builtins~=1.5",
- "flake8-comprehensions~=3.4",
- "flake8-docstrings~=1.6",
- "flake8-pytest-style~=1.4",
- "flake8-string-format~=0.3",
- "flake8-tidy-imports~=4.2",
- "flake8-variables-names~=0.0",
- "pep8-naming~=0.11",
- "wps-light~=0.15",
- "curlylint~=0.13",
+ "darglint>=1.8",
+ "flake8-bandit>=2.1",
+ "flake8-black>=0.2",
+ "flake8-bugbear>=21.9",
+ "flake8-builtins>=1.5",
+ "flake8-comprehensions>=3.7",
+ "flake8-docstrings>=1.6",
+ "flake8-pytest-style>=1.5",
+ "flake8-string-format>=0.3",
+ "flake8-tidy-imports>=4.5",
+ "flake8-variables-names>=0.0",
+ "pep8-naming>=0.12",
+ "wps-light>=0.15",
+ "curlylint>=0.13",
]
tests = [
- "pygments~=2.10", # python 3.6
- "pytest~=6.2",
- "pytest-cov~=2.11",
- "pytest-randomly~=3.6",
- "pytest-sugar~=0.9",
- "pytest-xdist~=2.2",
+ "docutils",
+ "pygments>=2.10", # python 3.6
+ "pytest>=6.2",
+ "pytest-cov>=3.0",
+ "pytest-randomly>=3.10",
+ "pytest-sugar>=0.9",
+ "pytest-xdist>=2.4",
+ "sphinx",
+]
+typing = [
+ "mypy>=0.910",
+ "types-docutils",
+ "types-markdown>=3.3",
+ "types-pyyaml",
+ "types-toml>=0.10",
]
-typing = ["mypy~=0.812"]
+security = ["safety>=1.10"]
[tool.black]
line-length = 120
diff --git a/scripts/multirun.sh b/scripts/multirun.sh
index 4ca6e2ce..7b5d9cf2 100755
--- a/scripts/multirun.sh
+++ b/scripts/multirun.sh
@@ -1,15 +1,15 @@
#!/usr/bin/env bash
set -e
-PYTHON_VERSIONS="${PYTHON_VERSIONS-3.6 3.7 3.8 3.9}"
+PYTHON_VERSIONS="${PYTHON_VERSIONS-3.6 3.7 3.8 3.9 3.10 3.11}"
if [ -n "${PYTHON_VERSIONS}" ]; then
for python_version in ${PYTHON_VERSIONS}; do
- if pdm use -f "${python_version}" &>/dev/null; then
+ if pdm use -f "python${python_version}" &>/dev/null; then
echo "> pdm run $@ (Python ${python_version})"
pdm run "$@"
else
- echo "> pdm use -f ${python_version}: Python version not available?" >&2
+ echo "> pdm use -f python${python_version}: Python interpreter not available?" >&2
fi
done
else
diff --git a/scripts/setup.sh b/scripts/setup.sh
index cfddbac7..c5df7b4c 100755
--- a/scripts/setup.sh
+++ b/scripts/setup.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
-PYTHON_VERSIONS="${PYTHON_VERSIONS-3.6 3.7 3.8 3.9}"
+PYTHON_VERSIONS="${PYTHON_VERSIONS-3.6 3.7 3.8 3.9 3.10 3.11}"
install_with_pipx() {
if ! command -v "$1" &>/dev/null; then
@@ -16,11 +16,11 @@ install_with_pipx pdm
if [ -n "${PYTHON_VERSIONS}" ]; then
for python_version in ${PYTHON_VERSIONS}; do
- if pdm use -f "${python_version}" &>/dev/null; then
- echo "> Using Python ${python_version} environment"
+ if pdm use -f "python${python_version}" &>/dev/null; then
+ echo "> Using Python ${python_version} interpreter"
pdm install
else
- echo "> pdm use -f ${python_version}: Python version not available?" >&2
+ echo "> pdm use -f python${python_version}: Python interpreter not available?" >&2
fi
done
else
diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py
index afe59708..d8aae2e5 100644
--- a/src/mkdocstrings/extension.py
+++ b/src/mkdocstrings/extension.py
@@ -23,7 +23,7 @@
"""
import re
from collections import ChainMap
-from typing import Mapping, MutableSequence, Tuple
+from typing import Mapping, MutableMapping, MutableSequence, Tuple
from xml.etree.ElementTree import Element
import yaml
@@ -79,7 +79,7 @@ def __init__(
self._autorefs = autorefs
self._updated_env = False
- def test(self, parent: Element, block: str) -> bool: # type: ignore
+ def test(self, parent: Element, block: str) -> bool:
"""Match our autodoc instructions.
Arguments:
@@ -108,7 +108,7 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None:
if match.start() > 0:
self.parser.parseBlocks(parent, [block[: match.start()]])
# removes the first line
- block = block[match.end() :] # type: ignore
+ block = block[match.end() :]
block, the_rest = self.detab(block)
@@ -117,7 +117,7 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None:
heading_level = match["heading"].count("#")
log.debug(f"Matched '::: {identifier}'")
- html, handler = self._process_block(identifier, block, heading_level)
+ html, handler, data = self._process_block(identifier, block, heading_level)
el = Element("div", {"class": "mkdocstrings"})
# The final HTML is inserted as opaque to subsequent processing, and only revealed at the end.
el.text = self.md.htmlStash.store(html)
@@ -125,17 +125,20 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None:
headings = handler.renderer.get_headings()
el.extend(headings)
- for heading in headings:
- page = self._autorefs.current_page
- anchor = heading.attrib["id"]
+ page = self._autorefs.current_page
+ for anchor in handler.renderer.get_anchors(data):
self._autorefs.register_anchor(page, anchor)
+ for heading in headings:
+ anchor = heading.attrib["id"] # noqa: WPS440
+ self._autorefs.register_anchor(page, anchor) # noqa: WPS441
+
if "data-role" in heading.attrib:
self._handlers.inventory.register(
- name=anchor,
+ name=anchor, # noqa: WPS441
domain=handler.domain,
role=heading.attrib["data-role"],
- uri=f"{page}#{anchor}",
+ uri=f"{page}#{anchor}", # noqa: WPS441
)
parent.append(el)
@@ -146,7 +149,12 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None:
# list for future processing.
blocks.insert(0, the_rest)
- def _process_block(self, identifier: str, yaml_block: str, heading_level: int = 0) -> Tuple[str, BaseHandler]:
+ def _process_block(
+ self,
+ identifier: str,
+ yaml_block: str,
+ heading_level: int = 0,
+ ) -> Tuple[str, BaseHandler, CollectorItem]:
"""Process an autodoc block.
Arguments:
@@ -159,7 +167,7 @@ def _process_block(self, identifier: str, yaml_block: str, heading_level: int =
TemplateNotFound: When a template used for rendering could not be found.
Returns:
- Rendered HTML and the handler that was used.
+ Rendered HTML, the handler that was used, and the collected item.
"""
config = yaml.safe_load(yaml_block) or {}
handler_name = self._handlers.get_handler_name(config)
@@ -196,10 +204,10 @@ def _process_block(self, identifier: str, yaml_block: str, heading_level: int =
)
raise
- return (rendered, handler)
+ return rendered, handler, data
-def get_item_configs(handler_config: dict, config: dict) -> Tuple[Mapping, Mapping]:
+def get_item_configs(handler_config: dict, config: dict) -> Tuple[Mapping, MutableMapping]:
"""Get the selection and rendering configuration merged into the global configuration of the given handler.
Arguments:
diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py
index f496e7ed..32da5a20 100644
--- a/src/mkdocstrings/handlers/base.py
+++ b/src/mkdocstrings/handlers/base.py
@@ -104,9 +104,9 @@ def __init__(self, directory: str, theme: str, custom_templates: Optional[str] =
self.env = Environment(
autoescape=True,
- loader=FileSystemLoader(paths), # type: ignore
+ loader=FileSystemLoader(paths),
auto_reload=False, # Editing a template in the middle of a build is not useful.
- ) # type: ignore
+ )
self.env.filters["any"] = do_any
self.env.globals["log"] = get_template_logger()
@@ -125,18 +125,20 @@ def render(self, data: CollectorItem, config: dict) -> str:
The rendered template as HTML.
""" # noqa: DAR202 (excess return section)
- def get_anchor(self, data: CollectorItem) -> Optional[str]:
- """Return the canonical identifier (HTML anchor) for a collected item.
-
- This must match what the renderer would've actually rendered,
- e.g. if rendering the item contains `
...` then the return value should be "foo".
+ def get_anchors(self, data: CollectorItem) -> Sequence[str]:
+ """Return the possible identifiers (HTML anchors) for a collected item.
Arguments:
data: The collected data.
Returns:
- The HTML anchor (without '#') as a string, or None if this item doesn't have an anchor.
- """ # noqa: DAR202 (excess return section)
+ The HTML anchors (without '#'), or an empty tuple if this item doesn't have an anchor.
+ """
+ # TODO: remove this at some point
+ try:
+ return (self.get_anchor(data),) # type: ignore
+ except AttributeError:
+ return ()
def do_convert_markdown(self, text: str, heading_level: int, html_id: str = "") -> Markup:
"""Render Markdown text; for use inside templates.
@@ -329,23 +331,24 @@ def __init__(self, config: dict) -> None:
self._handlers: Dict[str, BaseHandler] = {}
self.inventory: Inventory = Inventory(project=self._config["site_name"])
- def get_anchor(self, identifier: str) -> Optional[str]:
+ def get_anchors(self, identifier: str) -> Sequence[str]:
"""Return the canonical HTML anchor for the identifier, if any of the seen handlers can collect it.
Arguments:
identifier: The identifier (one that [collect][mkdocstrings.handlers.base.BaseCollector.collect] can accept).
Returns:
- A string - anchor without '#', or None if there isn't any identifier familiar with it.
+ A tuple of strings - anchors without '#', or an empty tuple if there isn't any identifier familiar with it.
"""
for handler in self._handlers.values():
+ fallback_config = getattr(handler.collector, "fallback_config", {})
try:
- anchor = handler.renderer.get_anchor(handler.collector.collect(identifier, {}))
+ anchors = handler.renderer.get_anchors(handler.collector.collect(identifier, fallback_config))
except CollectionError:
continue
- if anchor is not None:
- return anchor
- return None
+ if anchors:
+ return anchors
+ return ()
def get_handler_name(self, config: dict) -> str:
"""Return the handler name defined in an "autodoc" instruction YAML configuration, or the global default handler.
@@ -395,11 +398,11 @@ def get_handler(self, name: str, handler_config: Optional[dict] = None) -> BaseH
if handler_config is None:
handler_config = self.get_handler_config(name)
module = importlib.import_module(f"mkdocstrings.handlers.{name}")
- self._handlers[name] = module.get_handler( # type: ignore
+ self._handlers[name] = module.get_handler(
self._config["theme_name"],
self._config["mkdocstrings"]["custom_templates"],
**handler_config,
- ) # type: ignore
+ )
return self._handlers[name]
@property
diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py
index 0aab09ac..0f85a6de 100644
--- a/src/mkdocstrings/handlers/python.py
+++ b/src/mkdocstrings/handlers/python.py
@@ -10,7 +10,7 @@
import traceback
from collections import ChainMap
from subprocess import PIPE, Popen # noqa: S404 (what other option, more secure that PIPE do we have? sockets?)
-from typing import Any, BinaryIO, Callable, Iterator, List, Optional, Tuple
+from typing import Any, BinaryIO, Callable, Iterator, List, Optional, Sequence, Tuple
from markdown import Markdown
from markupsafe import Markup
@@ -45,6 +45,7 @@ class PythonRenderer(BaseRenderer):
"show_object_full_path": False,
"show_category_heading": False,
"show_if_no_docstring": False,
+ "show_signature": True,
"show_signature_annotations": False,
"show_source": True,
"show_bases": True,
@@ -63,7 +64,8 @@ class PythonRenderer(BaseRenderer):
**`show_root_members_full_path`** | `bool` | Show the full Python path of objects that are children of the root object (for example, classes in a module). When False, `show_object_full_path` overrides. | `False`
**`show_category_heading`** | `bool` | When grouped by categories, show a heading for each category. | `False`
**`show_if_no_docstring`** | `bool` | Show the object heading even if it has no docstring or children with docstrings. | `False`
- **`show_signature_annotations`** | `bool` | Show the type annotations in methods and functions signatures. | `False`
+ **`show_signature`** | `bool` | Show method and function signatures. | `True`
+ **`show_signature_annotations`** | `bool` | Show the type annotations in method and function signatures. | `False`
**`show_source`** | `bool` | Show the source code of this object. | `True`
**`show_bases`** | `bool` | Show the base classes of a class. | `True`
**`group_by_category`** | `bool` | Group the object's children by categories: attributes, classes, functions, methods, and modules. | `True`
@@ -95,8 +97,11 @@ def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignor
**{"config": final_config, data["category"]: data, "heading_level": heading_level, "root": True},
)
- def get_anchor(self, data: CollectorItem) -> str: # noqa: D102 (ignore missing docstring)
- return data.get("path")
+ def get_anchors(self, data: CollectorItem) -> Sequence[str]: # noqa: D102 (ignore missing docstring)
+ try:
+ return (data["path"],)
+ except KeyError:
+ return ()
def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore missing docstring)
super().update_env(md, config)
@@ -141,6 +146,21 @@ class PythonCollector(BaseCollector):
Obviously one could use a single filter instead: `"!^_[^_]"`, which is the default.
"""
+ fallback_config = {"docstring_style": "markdown", "filters": ["!.*"]}
+ """The configuration used when falling back to re-collecting an object to get its anchor.
+
+ This configuration is used in [`Handlers.get_anchors`][mkdocstrings.handlers.base.Handlers.get_anchors].
+
+ When trying to fix (optional) cross-references, the autorefs plugin will try to collect
+ an object with every configured handler until one succeeds. It will then try to get
+ an anchor for it. It's because objects can have multiple identifiers (aliases),
+ for example their definition path and multiple import paths in Python.
+
+ When re-collecting the object, we have no use for its members, or for its docstring being parsed.
+ This is why the fallback configuration filters every member out, and uses the Markdown style,
+ which we know will not generate any warnings.
+ """
+
def __init__(self, setup_commands: Optional[List[str]] = None) -> None:
"""Initialize the object.
diff --git a/src/mkdocstrings/handlers/rendering.py b/src/mkdocstrings/handlers/rendering.py
index e7ff9802..c28beb00 100644
--- a/src/mkdocstrings/handlers/rendering.py
+++ b/src/mkdocstrings/handlers/rendering.py
@@ -63,7 +63,7 @@ def __init__(self, md: Markdown):
md: The Markdown instance to read configs from.
"""
config: Dict[str, Any] = {}
- for ext in md.registeredExtensions: # type: ignore
+ for ext in md.registeredExtensions:
if isinstance(ext, HighlightExtension) and (ext.enabled or not config):
config = ext.getConfigs()
break # This one takes priority, no need to continue looking
diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py
index 4e693473..242d5931 100644
--- a/src/mkdocstrings/plugin.py
+++ b/src/mkdocstrings/plugin.py
@@ -183,7 +183,7 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused
config["plugins"]["autorefs"] = autorefs
log.debug(f"Added a subdued autorefs instance {autorefs!r}")
# Add collector-based fallback in either case.
- autorefs.get_fallback_anchor = self.handlers.get_anchor
+ autorefs.get_fallback_anchor = self.handlers.get_anchors
mkdocstrings_extension = MkdocstringsExtension(extension_config, self.handlers, autorefs)
config["markdown_extensions"].append(mkdocstrings_extension)
diff --git a/src/mkdocstrings/templates/python/material/docstring.html b/src/mkdocstrings/templates/python/material/docstring.html
index 0c32c01d..f1f179b3 100644
--- a/src/mkdocstrings/templates/python/material/docstring.html
+++ b/src/mkdocstrings/templates/python/material/docstring.html
@@ -11,10 +11,18 @@
{% with parameters = section.value %}
{% include "parameters.html" with context %}
{% endwith %}
+ {% elif section.type == "keyword_args" %}
+ {% with kwargs = section.value %}
+ {% include "keyword_args.html" with context %}
+ {% endwith %}
{% elif section.type == "exceptions" %}
{% with exceptions = section.value %}
{% include "exceptions.html" with context %}
{% endwith %}
+ {% elif section.type == "yield" %}
+ {% with yield = section.value %}
+ {% include "yield.html" with context %}
+ {% endwith %}
{% elif section.type == "return" %}
{% with return = section.value %}
{% include "return.html" with context %}
diff --git a/src/mkdocstrings/templates/python/material/keyword_args.html b/src/mkdocstrings/templates/python/material/keyword_args.html
new file mode 100644
index 00000000..3396ffa5
--- /dev/null
+++ b/src/mkdocstrings/templates/python/material/keyword_args.html
@@ -0,0 +1,20 @@
+{{ log.debug() }}
+
Keyword arguments:
+
+
+
+ | Name |
+ Type |
+ Description |
+
+
+
+ {% for kwarg in kwargs %}
+
+ {{ kwarg.name }} |
+ {% if kwarg.annotation %}{{ kwarg.annotation }}{% endif %} |
+ {{ kwarg.description|convert_markdown(heading_level, html_id) }} |
+
+ {% endfor %}
+
+
diff --git a/src/mkdocstrings/templates/python/material/signature.html b/src/mkdocstrings/templates/python/material/signature.html
index e1f815da..90cc4180 100644
--- a/src/mkdocstrings/templates/python/material/signature.html
+++ b/src/mkdocstrings/templates/python/material/signature.html
@@ -1,5 +1,5 @@
{{ log.debug() }}
-{%- if signature -%}
+{%- if signature and config.show_signature -%}
{%- with -%}
{%- set ns = namespace(render_pos_only_separator=True, render_kw_only_separator=True, equal="=") -%}
diff --git a/src/mkdocstrings/templates/python/material/yield.html b/src/mkdocstrings/templates/python/material/yield.html
new file mode 100644
index 00000000..d8a450ca
--- /dev/null
+++ b/src/mkdocstrings/templates/python/material/yield.html
@@ -0,0 +1,16 @@
+{{ log.debug() }}
+Yields:
+
+
+
+ | Type |
+ Description |
+
+
+
+
+ {% if yield.annotation %}{{ yield.annotation }}{% endif %} |
+ {{ yield.description|convert_markdown(heading_level, html_id) }} |
+
+
+
diff --git a/src/mkdocstrings/templates/python/mkdocs/keyword_args.html b/src/mkdocstrings/templates/python/mkdocs/keyword_args.html
new file mode 100644
index 00000000..16af0ea0
--- /dev/null
+++ b/src/mkdocstrings/templates/python/mkdocs/keyword_args.html
@@ -0,0 +1,7 @@
+{{ log.debug() }}
+
+ - Keyword arguments:
+ {% for kwarg in kwargs %}
+ - {{ ("**" + kwarg.name + ":** " + ("`" + kwarg.annotation + "` – " if kwarg.annotation else "") + kwarg.description)|convert_markdown(heading_level, html_id) }}
+ {% endfor %}
+
diff --git a/src/mkdocstrings/templates/python/mkdocs/yield.html b/src/mkdocstrings/templates/python/mkdocs/yield.html
new file mode 100644
index 00000000..dad0f0e9
--- /dev/null
+++ b/src/mkdocstrings/templates/python/mkdocs/yield.html
@@ -0,0 +1,5 @@
+{{ log.debug() }}
+
+ - Yields:
+ - {{ (("`" + yield.annotation + "` – " if yield.annotation else "") + yield.description)|convert_markdown(heading_level, html_id) }}
+
diff --git a/src/mkdocstrings/templates/python/readthedocs/keyword_args.html b/src/mkdocstrings/templates/python/readthedocs/keyword_args.html
new file mode 100644
index 00000000..3c15308e
--- /dev/null
+++ b/src/mkdocstrings/templates/python/readthedocs/keyword_args.html
@@ -0,0 +1,19 @@
+{{ log.debug() }}
+
+
+
+
+
+
+
+ | Keyword arguments: |
+
+
+ {% for kwarg in kwargs %}
+ - {{ ("**" + kwarg.name + "**" + (" (`" + kwarg.annotation + "`)" if kwarg.annotation else "") + " – " + kwarg.description)|convert_markdown(heading_level, html_id) }}
+ {% endfor %}
+
+ |
+
+
+
diff --git a/src/mkdocstrings/templates/python/readthedocs/yield.html b/src/mkdocstrings/templates/python/readthedocs/yield.html
new file mode 100644
index 00000000..7591f62c
--- /dev/null
+++ b/src/mkdocstrings/templates/python/readthedocs/yield.html
@@ -0,0 +1,17 @@
+{{ log.debug() }}
+
+
+
+
+
+
+
+ | Yields: |
+
+
+ - {{ ((("`" + yield.annotation + "` – ") if yield.annotation else "") + yield.description)|convert_markdown(heading_level, html_id) }}
+
+ |
+
+
+