diff --git a/.copier-answers.yml b/.copier-answers.yml index 0e3a987e..90ce2e79 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.3.1 +_commit: 1.1.4 _src_path: gh:mkdocstrings/handler-template author_email: dev@pawamoy.fr author_fullname: Timothée Mazzucotelli diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 01e293ac..a502284a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,5 @@ github: pawamoy ko_fi: pawamoy +polar: pawamoy custom: - https://www.paypal.me/pawamoy diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/1-bug.md similarity index 98% rename from .github/ISSUE_TEMPLATE/bug_report.md rename to .github/ISSUE_TEMPLATE/1-bug.md index ca545c26..0df6e967 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/1-bug.md @@ -53,7 +53,7 @@ PASTE TRACEBACK HERE python -m mkdocstrings_handlers.python.debug # | xclip -selection clipboard ``` -PASTE OUTPUT HERE +PASTE MARKDOWN OUTPUT HERE ### Additional context + +### Relevant code snippets + + +### Link to the relevant documentation section + diff --git a/.github/ISSUE_TEMPLATE/4-change.md b/.github/ISSUE_TEMPLATE/4-change.md new file mode 100644 index 00000000..dc9a8f17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-change.md @@ -0,0 +1,18 @@ +--- +name: Change request +about: Suggest any other kind of change for this project. +title: "change: " +assignees: pawamoy +--- + +### Is your change request related to a problem? Please describe. + + +### Describe the solution you'd like + + +### Describe alternatives you've considered + + +### Additional context + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 384bf7cc..e422aeb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,9 +49,6 @@ jobs: - name: Check if the code is correctly typed run: make check-types - - name: Check for vulnerabilities in dependencies - run: make check-dependencies - - name: Check for breaking changes in the API run: make check-api @@ -69,10 +66,14 @@ jobs: {"python-version": "3.9"}, {"python-version": "3.10"}, {"python-version": "3.11"}, - {"python-version": "3.12"} + {"python-version": "3.12"}, + {"python-version": "3.13"} ]' | tr -d '[:space:]' >> $GITHUB_OUTPUT else - echo 'jobs=[]' >> $GITHUB_OUTPUT + echo 'jobs=[ + {"os": "macos-latest", "resolution": "lowest-direct"}, + {"os": "windows-latest", "resolution": "lowest-direct"} + ]' | tr -d '[:space:]' >> $GITHUB_OUTPUT fi tests: @@ -91,9 +92,13 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" + resolution: + - highest + - lowest-direct exclude: ${{ fromJSON(needs.exclude-test-jobs.outputs.jobs) }} runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.python-version == '3.12' }} + continue-on-error: ${{ matrix.python-version == '3.13' }} steps: - name: Checkout @@ -109,10 +114,9 @@ jobs: run: pip install uv - name: Install dependencies - run: | - uv venv - uv pip install -r devdeps.txt - uv pip install "mkdocstrings-python @ ." + env: + UV_RESOLUTION: ${{ matrix.resolution }} + run: make setup - name: Run the test suite run: make test diff --git a/.gitignore b/.gitignore index 246951cc..41fee62d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,24 @@ +# editors .idea/ .vscode/ -__pycache__/ -*.py[cod] -dist/ + +# python *.egg-info/ -build/ -htmlcov/ +*.py[cod] +.venv/ +.venvs/ +/build/ +/dist/ + +# tools .coverage* -pip-wheel-metadata/ +/.pdm-build/ +/htmlcov/ +/site/ + +# cache +.cache/ .pytest_cache/ .mypy_cache/ .ruff_cache/ -site/ -.venv/ -.venvs/ -.cache/ +__pycache__/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b0e9c966..947cfe99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,110 @@ 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). +## [1.10.9](https://github.com/mkdocstrings/python/releases/tag/1.10.9) - 2024-08-30 + +[Compare with 1.10.8](https://github.com/mkdocstrings/python/compare/1.10.8...1.10.9) + +### Build + +- Explicitly depend on mkdocs-autorefs to be able to specify lower bound ([2299ab5](https://github.com/mkdocstrings/python/commit/2299ab55641585d65babe0e116a6465b4736dcd9) by Timothée Mazzucotelli). + +### Code Refactoring + +- Use new autorefs syntax ([68cb72f](https://github.com/mkdocstrings/python/commit/68cb72f62253f54146ece621345b36c90d712913) by Timothée Mazzucotelli). + +## [1.10.8](https://github.com/mkdocstrings/python/releases/tag/1.10.8) - 2024-08-14 + +[Compare with 1.10.7](https://github.com/mkdocstrings/python/compare/1.10.7...1.10.8) + +### Build + +- Depend on Griffe 0.49 ([a87dcad](https://github.com/mkdocstrings/python/commit/a87dcad36065dc3171512e166ec632ee3e5b0a64) by Timothée Mazzucotelli). + +## [1.10.7](https://github.com/mkdocstrings/python/releases/tag/1.10.7) - 2024-07-25 + +[Compare with 1.10.6](https://github.com/mkdocstrings/python/compare/1.10.6...1.10.7) + +### Packaging + +- Include tests and all relevant files for downstream packaging in source distribution + +## [1.10.6](https://github.com/mkdocstrings/python/releases/tag/1.10.6) - 2024-07-25 + +[Compare with 1.10.5](https://github.com/mkdocstrings/python/compare/1.10.5...1.10.6) + +### Bug Fixes + +- Fix condition to display members (check all members, not just non-inherited ones) ([3d838a9](https://github.com/mkdocstrings/python/commit/3d838a96f77fa128cd6f2afa5ed0cb151ab225fd) by Timothée Mazzucotelli). + +### Code Refactoring + +- Update code for Griffe 0.48 (removing deprecation warnings) ([eff10cc](https://github.com/mkdocstrings/python/commit/eff10ccf0fa1b2e73df912048a15c2d6406a2c8b) by Timothée Mazzucotelli). [Issue-173](https://github.com/mkdocstrings/python/issues/173) + +## [1.10.5](https://github.com/mkdocstrings/python/releases/tag/1.10.5) - 2024-06-19 + +[Compare with 1.10.4](https://github.com/mkdocstrings/python/compare/1.10.4...1.10.5) + +### Bug Fixes + +- Mix both previous checks for displaying objects: not imported or public ([587963b](https://github.com/mkdocstrings/python/commit/587963ba53f765c9d7eefbc2fb80bdbb11164850) by Timothée Mazzucotelli). [Issue-294](https://github.com/mkdocstrings/griffe/issues/294) + +## [1.10.4](https://github.com/mkdocstrings/python/releases/tag/1.10.4) - 2024-06-18 + +[Compare with 1.10.3](https://github.com/mkdocstrings/python/compare/1.10.3...1.10.4) + +### Code Refactoring + +- Only filter out imported objects instead of non-public ones after applying filters ([e2f4b35](https://github.com/mkdocstrings/python/commit/e2f4b35d29eca6f68afbd2e728ef7542a2abc992) by Timothée Mazzucotelli). [Issue-mkdocstrings/griffe-294](https://github.com/mkdocstrings/griffe/issues/294) +- Update code for Griffe 0.46 to avoid deprecation warnings ([321b407](https://github.com/mkdocstrings/python/commit/321b407eb95195c44f3cf34d780784e0d6751998) by Timothée Mazzucotelli). +- Change `load_external_modules` default value to `None` to support new default mode in Griffe ([ae5896c](https://github.com/mkdocstrings/python/commit/ae5896c1604e9089162d0d63ec97a510a6bcef89) by Timothée Mazzucotelli). + +## [1.10.3](https://github.com/mkdocstrings/python/releases/tag/1.10.3) - 2024-05-22 + +[Compare with 1.10.2](https://github.com/mkdocstrings/python/compare/1.10.2...1.10.3) + +### Bug Fixes + +- Don't crash when rendering the source of an object whose lineno is none ([64df00b](https://github.com/mkdocstrings/python/commit/64df00b9b757e9642d65cf425d32f5a2e0d75f38) by Timothée Mazzucotelli). [Issue-163](https://github.com/mkdocstrings/python/issues/163) + +## [1.10.2](https://github.com/mkdocstrings/python/releases/tag/1.10.2) - 2024-05-16 + +[Compare with 1.10.1](https://github.com/mkdocstrings/python/compare/1.10.1...1.10.2) + +### Bug Fixes + +- Actually make use of custom .html.jinja templates ([5668abb](https://github.com/mkdocstrings/python/commit/5668abba15b13b86fe67f70f6b4004b7b1feeb4f) by Timothée Mazzucotelli). + +## [1.10.1](https://github.com/mkdocstrings/python/releases/tag/1.10.1) - 2024-05-14 + +[Compare with 1.10.0](https://github.com/mkdocstrings/python/compare/1.10.0...1.10.1) + +### Build + +- Depend on mkdocstrings 0.25 which adds support for parameter `once` when logging messages ([2bc156b](https://github.com/mkdocstrings/python/commit/2bc156bd6f231ae13066651f4490d1e9c2ce3ca2) by Timothée Mazzucotelli). + +### Code Refactoring + +- Set handler's name ([a71ab12](https://github.com/mkdocstrings/python/commit/a71ab12c8e52efe76e5c0a5e54065926a47cc0d2) by Timothée Mazzucotelli). +- Update `*.html` top-level templates to extend the `*.html.jinja` base templates ([a8c540e](https://github.com/mkdocstrings/python/commit/a8c540e95693e8500da884c32ad159b3bbaaa7ba) by Timothée Mazzucotelli). [Issue-151](https://github.com/mkdocstrings/python/issues/151) +- Update `*.html` base templates to extend their `*.html.jinja` counterpart, while overriding the `logs` block to issue a logging message (info) stating that extending `*.html` templates is deprecated ([e6f1b9c](https://github.com/mkdocstrings/python/commit/e6f1b9caf13754eca9dbb2f112727bef50876ed7) by Timothée Mazzucotelli). [Issue-151](https://github.com/mkdocstrings/python/issues/151) +- Add `*.html.jinja` top-level (overridable) templates, extending their base counterpart ([7c14924](https://github.com/mkdocstrings/python/commit/7c14924c406d7b5f4f1c22d03019d4c566018d2d) by Timothée Mazzucotelli). [Issue-151](https://github.com/mkdocstrings/python/issues/151) +- Add `*.html.jinja` base templates, which are copies of `*.html` templates, with an additional `logs` block, and using the updated `get_template` filter ([eced9a5](https://github.com/mkdocstrings/python/commit/eced9a54fc8a559b686cb1b1180a0d2e04ba452d) by Timothée Mazzucotelli). [Issue-151](https://github.com/mkdocstrings/python/issues/151) +- Update `get_template` filter to support both `*.html` and `*.html.jinja` templates, logging a message (info) when `*.html` templates are overridden by users ([3546fd7](https://github.com/mkdocstrings/python/commit/3546fd70b2d4e45f77b166b2e67c333acc8af0d2) by Timothée Mazzucotelli). [Issue-151](https://github.com/mkdocstrings/python/issues/151) +- Log a warning when base templates are overridden ([26e3d66](https://github.com/mkdocstrings/python/commit/26e3d66f5334a5aaff75bda030afe6dfa1cc94d7) by Timothée Mazzucotelli). [Issue-151](https://github.com/mkdocstrings/python/issues/151) + +## [1.10.0](https://github.com/mkdocstrings/python/releases/tag/1.10.0) - 2024-04-19 + +[Compare with 1.9.2](https://github.com/mkdocstrings/python/compare/1.9.2...1.10.0) + +### Features + +- Add CSS classes `doc-section-title` and `doc-section-item` in docstring sections ([d6e1d68](https://github.com/mkdocstrings/python/commit/d6e1d68c099e61c3bd6d93e583708335d84158f5) by Timothée Mazzucotelli). [Issue-17](https://github.com/mkdocstrings/python/issues/17) + +### Bug Fixes + +- Render enumeration instance name instead of just "value", allowing proper cross-reference ([11d81d8](https://github.com/mkdocstrings/python/commit/11d81d8e056b7c074eb3a1c47606867156a338fa) by Timothée Mazzucotelli). [Issue-124](https://github.com/mkdocstrings/python/issues/124) + ## [1.9.2](https://github.com/mkdocstrings/python/releases/tag/1.9.2) - 2024-04-02 [Compare with 1.9.1](https://github.com/mkdocstrings/python/compare/1.9.1...1.9.2) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6af01962..bbc08404 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,13 +36,11 @@ Run `make help` to see all the available actions! ## Tasks -This project uses [duty](https://github.com/pawamoy/duty) to run tasks. -A Makefile is also provided. The Makefile will try to run certain tasks -on multiple Python versions. If for some reason you don't want to run the task -on multiple Python versions, you run the task directly with `make run duty TASK`. - -The Makefile detects if a virtual environment is activated, -so `make` will work the same with the virtualenv activated or not. +The entry-point to run commands and tasks is the `make` Python script, +located in the `scripts` directory. Try running `make` to show the available commands and tasks. +The *commands* do not need the Python dependencies to be installed, +while the *tasks* do. +The cross-platform tasks are written in Python, thanks to [duty](https://github.com/pawamoy/duty). If you work in VSCode, we provide [an action to configure VSCode](https://pawamoy.github.io/copier-uv/work/#vscode-setup) diff --git a/Makefile b/Makefile index 771b333c..5e88121d 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ # This Makefile is just here to allow auto-completion in the terminal. actions = \ + allrun \ changelog \ check \ check-api \ - check-dependencies \ check-docs \ check-quality \ check-types \ @@ -16,6 +16,7 @@ actions = \ docs-deploy \ format \ help \ + multirun \ release \ run \ setup \ @@ -24,4 +25,4 @@ actions = \ .PHONY: $(actions) $(actions): - @bash scripts/make "$@" + @python scripts/make "$@" diff --git a/README.md b/README.md index 23b6e809..a65bf00c 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@
A Python handler for mkdocstrings.
[](https://github.com/mkdocstrings/python/actions?query=workflow%3Aci) -[](https://mkdocstrings.github.io/python/) +[](https://mkdocstrings.github.io/python/) [](https://pypi.org/project/mkdocstrings-python/) -[](https://gitpod.io/#https://github.com/mkdocstrings/python) +[](https://gitpod.io/#https://github.com/mkdocstrings/python) [](https://app.gitter.im/#/room/#python:gitter.im) --- diff --git a/config/git-changelog.toml b/config/git-changelog.toml index 44e2b1fb..57114e0c 100644 --- a/config/git-changelog.toml +++ b/config/git-changelog.toml @@ -6,3 +6,4 @@ parse-refs = false parse-trailers = true sections = ["build", "deps", "feat", "fix", "refactor"] template = "keepachangelog" +versioning = "pep440" diff --git a/config/pytest.ini b/config/pytest.ini index ebdeb484..9d75f5c6 100644 --- a/config/pytest.ini +++ b/config/pytest.ini @@ -1,8 +1,6 @@ [pytest] python_files = test_*.py - *_test.py - tests.py addopts = --cov --cov-config config/coverage.ini @@ -14,3 +12,6 @@ filterwarnings = error # TODO: remove once pytest-xdist 4 is released ignore:.*rsyncdir:DeprecationWarning:xdist + # TODO: remove once Griffe releases v1 + ignore:.*`get_logger`:DeprecationWarning:_griffe + ignore:.*`name`:DeprecationWarning:_griffe diff --git a/config/ruff.toml b/config/ruff.toml index 751bf595..e3c9ec30 100644 --- a/config/ruff.toml +++ b/config/ruff.toml @@ -3,8 +3,7 @@ line-length = 120 [lint] exclude = [ - "fixtures", - "site", + "tests/fixtures/*.py", ] select = [ "A", "ANN", "ARG", @@ -78,5 +77,8 @@ known-first-party = ["mkdocstrings_handlers.python"] convention = "google" [format] +exclude = [ + "tests/fixtures/*.py", +] docstring-code-format = true docstring-code-line-length = 80 diff --git a/config/vscode/tasks.json b/config/vscode/tasks.json index 30008cf2..73145eec 100644 --- a/config/vscode/tasks.json +++ b/config/vscode/tasks.json @@ -31,12 +31,6 @@ "command": "scripts/make", "args": ["check-docs"] }, - { - "label": "check-dependencies", - "type": "process", - "command": "scripts/make", - "args": ["check-dependencies"] - }, { "label": "check-api", "type": "process", diff --git a/devdeps.txt b/devdeps.txt index 4fe97996..e0afd7e2 100644 --- a/devdeps.txt +++ b/devdeps.txt @@ -1,27 +1,32 @@ -build>=1.0 -duty>=0.10 -black>=23.9 -markdown-callouts>=0.3 -markdown-exec>=1.7 -mkdocs>=1.5 +# dev +editables>=0.5 + +# maintenance +build>=1.2 +git-changelog>=2.5 +twine>=5.0; python_version < '3.13' + +# ci +duty>=1.4 +ruff>=0.4 +pytest>=8.2 +pytest-cov>=5.0 +pytest-randomly>=3.15 +pytest-xdist>=3.6 +mypy>=1.10 +types-markdown>=3.6 +types-pyyaml>=6.0 + +# docs +black>=24.4 +markdown-callouts>=0.4 +markdown-exec>=1.8 +mkdocs>=1.6 mkdocs-coverage>=1.0 mkdocs-gen-files>=0.5 -mkdocs-git-committers-plugin-2>=1.2 +mkdocs-git-committers-plugin-2>=2.3 mkdocs-literate-nav>=0.6 -mkdocs-material>=9.4 -mkdocs-minify-plugin>=0.7 -mkdocstrings[python]>=0.23 +mkdocs-material>=9.5 +mkdocs-minify-plugin>=0.8 +mkdocstrings[python]>=0.25 tomli>=2.0; python_version < '3.11' -black>=23.9 -blacken-docs>=1.16 -git-changelog>=2.3 -ruff>=0.0 -pytest>=7.4 -pytest-cov>=4.1 -pytest-randomly>=3.15 -pytest-xdist>=3.3 -mypy>=1.5 -types-markdown>=3.5 -types-pyyaml>=6.0 -safety>=2.3 -twine>=5.0 diff --git a/docs/.overrides/main.html b/docs/.overrides/main.html index cf8adeb7..1e956857 100644 --- a/docs/.overrides/main.html +++ b/docs/.overrides/main.html @@ -2,17 +2,19 @@ {% block announce %} - Sponsorship - is now available! + Fund this project through + sponsorship {% include ".icons/octicons/heart-fill-16.svg" %} — - For updates follow @pawamoy on + Follow + @pawamoy on {% include ".icons/fontawesome/brands/mastodon.svg" %} Fosstodon + for updates {% endblock %} diff --git a/docs/.overrides/partials/comments.html b/docs/.overrides/partials/comments.html new file mode 100644 index 00000000..0dedc405 --- /dev/null +++ b/docs/.overrides/partials/comments.html @@ -0,0 +1,57 @@ + + + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 612c7a5e..8e6f2fb4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1 +1,6 @@ +--- +hide: +- feedback +--- + --8<-- "README.md" diff --git a/docs/insiders/index.md b/docs/insiders/index.md index 123fe42d..3bc3aa56 100644 --- a/docs/insiders/index.md +++ b/docs/insiders/index.md @@ -99,6 +99,10 @@ with your GitHub account, visit [pawamoy's sponsor profile][github sponsor profi and complete a sponsorship of **$10 a month or more**. You can use your individual or organization GitHub account for sponsoring. +Sponsorships lower than $10 a month are also very much appreciated, and useful. +They won't grant you access to Insiders, but they will be counted towards reaching sponsorship goals. +*Every* sponsorship helps us implementing new features and releasing them to the public. + **Important**: If you're sponsoring **[@pawamoy][github sponsor profile]** through a GitHub organization, please send a short email to insiders@pawamoy.fr with the name of your diff --git a/docs/insiders/installation.md b/docs/insiders/installation.md index 3ebe5dfd..0e4628ca 100644 --- a/docs/insiders/installation.md +++ b/docs/insiders/installation.md @@ -23,6 +23,9 @@ of Insiders projects in the PyPI index of your choice See [how to install it](https://pawamoy.github.io/pypi-insiders/#installation) and [how to use it](https://pawamoy.github.io/pypi-insiders/#usage). +**We kindly ask that you do not upload the distributions to public registries, +as it is against our [Terms of use](index.md#terms).** + ### with pip (ssh/https) *mkdocstrings-python Insiders* can be installed with `pip` [using SSH][using ssh]: @@ -58,130 +61,15 @@ pip install git+https://${GH_TOKEN}@github.com/pawamoy-insiders/mkdocstrings-pyt > token must be kept secret at all times, as it allows the owner to access your > private repositories. -### with pip (self-hosted) - -Self-hosting the Insiders package makes it possible to depend on *mkdocstrings-python* normally, -while transparently downloading and installing the Insiders version locally. -It means that you can specify your dependencies normally, and your contributors without access -to Insiders will get the public version, while you get the Insiders version on your machine. - -WARNING: **Limitation** -With this method, there is no way to force the installation of an Insiders version -rather than a public version. If there is a public version that is more recent -than your self-hosted Insiders version, the public version will take precedence. -Remember to regularly update your self-hosted versions by uploading latest distributions. - -You can build the distributions for Insiders yourself, by cloning the repository -and using [build] to build the distributions, -or you can download them from our [GitHub Releases]. -You can upload these distributions to a private PyPI-like registry -([Artifactory], [Google Cloud], [pypiserver], etc.) -with [Twine]: - - [build]: https://pypi.org/project/build/ - [Artifactory]: https://jfrog.com/help/r/jfrog-artifactory-documentation/pypi-repositories - [Google Cloud]: https://cloud.google.com/artifact-registry/docs/python - [pypiserver]: https://pypi.org/project/pypiserver/ - [Github Releases]: https://github.com/pawamoy-insiders/mkdocstrings-python/releases - [Twine]: https://pypi.org/project/twine/ - -```bash -# download distributions in ~/dists, then upload with: -twine upload --repository-url https://your-private-index.com ~/dists/* -``` - -You might also need to provide a username and password/token to authenticate against the registry. -Please check [Twine's documentation][twine docs]. - - [twine docs]: https://twine.readthedocs.io/en/stable/ - -You can then configure pip (or other tools) to look for packages into your package index. -For example, with pip: - -```bash -pip config set global.extra-index-url https://your-private-index.com/simple -``` - -Note that the URL might differ depending on whether your are uploading a package (with Twine) -or installing a package (with pip), and depending on the registry you are using (Artifactory, Google Cloud, etc.). -Please check the documentation of your registry to learn how to configure your environment. - -**We kindly ask that you do not upload the distributions to public registries, -as it is against our [Terms of use](index.md#terms).** +### with Git ->? TIP: **Full example with `pypiserver`** -> In this example we use [pypiserver] to serve a local PyPI index. -> -> ```bash -> pip install --user pypiserver -> # or pipx install pypiserver -> -> # create a packages directory -> mkdir -p ~/.local/pypiserver/packages -> -> # run the pypi server without authentication -> pypi-server run -p 8080 -a . -P . ~/.local/pypiserver/packages & -> ``` -> -> We can configure the credentials to access the server in [`~/.pypirc`][pypirc]: -> -> [pypirc]: https://packaging.python.org/en/latest/specifications/pypirc/ -> -> ```ini title=".pypirc" -> [distutils] -> index-servers = -> local -> -> [local] -> repository: http://localhost:8080 -> username: -> password: -> ``` -> -> We then clone the Insiders repository, build distributions and upload them to our local server: -> -> ```bash -> # clone the repository -> git clone git@github.com:pawamoy-insiders/mkdocstrings-python -> cd mkdocstrings-python -> -> # install build -> pip install --user build -> # or pipx install build -> -> # checkout latest tag -> git checkout $(git describe --tags --abbrev=0) -> -> # build the distributions -> pyproject-build -> -> # upload them to our local server -> twine upload -r local dist/* --skip-existing -> ``` -> -> Finally, we configure pip, and for example [PDM][pdm], to use our local index to find packages: -> -> ```bash -> pip config set global.extra-index-url http://localhost:8080/simple -> pdm config pypi.extra.url http://localhost:8080/simple -> ``` -> -> [pdm]: https://pdm.fming.dev/latest/ -> -> Now when running `pip install mkdocstrings-python`, -> or resolving dependencies with PDM, -> both tools will look into our local index and find the Insiders version. -> **Remember to update your local index regularly!** - -### with git - -Of course, you can use *mkdocstrings-python Insiders* directly from `git`: +Of course, you can use *mkdocstrings-python Insiders* directly using Git: ``` git clone git@github.com:pawamoy-insiders/mkdocstrings-python ``` -When cloning from `git`, the package must be installed: +When cloning with Git, the package must be installed: ``` pip install -e mkdocstrings-python diff --git a/docs/js/feedback.js b/docs/js/feedback.js new file mode 100644 index 00000000..f97321a5 --- /dev/null +++ b/docs/js/feedback.js @@ -0,0 +1,14 @@ +const feedback = document.forms.feedback; +feedback.hidden = false; + +feedback.addEventListener("submit", function(ev) { + ev.preventDefault(); + const commentElement = document.getElementById("feedback"); + commentElement.style.display = "block"; + feedback.firstElementChild.disabled = true; + const data = ev.submitter.getAttribute("data-md-value"); + const note = feedback.querySelector(".md-feedback__note [data-md-value='" + data + "']"); + if (note) { + note.hidden = false; + } +}) diff --git a/docs/license.md b/docs/license.md index a873d2b5..e81c0edf 100644 --- a/docs/license.md +++ b/docs/license.md @@ -1,3 +1,8 @@ +--- +hide: +- feedback +--- + # License ``` diff --git a/docs/usage/configuration/docstrings.md b/docs/usage/configuration/docstrings.md index fcb9a19a..027ebaed 100644 --- a/docs/usage/configuration/docstrings.md +++ b/docs/usage/configuration/docstrings.md @@ -120,7 +120,7 @@ class PrintOK: """Initialize the instance. Examples: - >>> Class() # doctest: +NORMALIZE_WHITESPACE + >>> PrintOK() # doctest: +NORMALIZE_WHITESPACE ok """ print("ok") @@ -136,7 +136,7 @@ class PrintOK:Examples:
```pycon ->>> Class() +>>> PrintOK() ok ``` //// @@ -149,7 +149,7 @@ okExamples:
```pycon ->>> Class() # doctest: +NORMALIZE_WHITESPACE +>>> PrintOK() # doctest: +NORMALIZE_WHITESPACE ok ``` //// @@ -208,8 +208,8 @@ In that case, the Spacy tables can help. **Type** | **Name** | **Description** | **Default** ---------- | ----------- | ------------------------ | ----------- -list[int \| float] | `gravity_forces` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | *required*
-VacuumType \| Literal["regular"] | `vacuum_type` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | `VacuumType.PLASMA`
+list [int \| float ] | `gravity_forces` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | *required*
+VacuumType \| Literal ["regular"] | `vacuum_type` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. | `VacuumType.PLASMA`
////
//// tab | List
@@ -222,8 +222,8 @@ Lists work well whatever the length of names, type annotations, descriptions, et
**Other Parameters:**
-- `gravity_forces` (list[int \| float]) — Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-- `vacuum_type` (VacuumType \| Literal["regular"]) — Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+- `gravity_forces` (list [int \| float ]) — Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+- `vacuum_type` (VacuumType \| Literal ["regular"]) — Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
////
//// tab | Spacy
@@ -241,8 +241,8 @@ by reserving more horizontal space on the second column.
**Name** | **Description**
----------- | ---------------
-`gravity_forces` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.list[int \| float] DEFAULT: required
-`vacuum_type` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.VacuumType \| Literal["regular"] DEFAULT: VacuumType.PLASMA
+`gravity_forces` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.list [int \| float ] DEFAULT: required
+`vacuum_type` | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.VacuumType \| Literal ["regular"] DEFAULT: VacuumType.PLASMA
////
///
diff --git a/docs/usage/configuration/general.md b/docs/usage/configuration/general.md
index e4ddaec1..d8d7f250 100644
--- a/docs/usage/configuration/general.md
+++ b/docs/usage/configuration/general.md
@@ -139,7 +139,7 @@ def some_function():
## `preload_modules`
-- **:octicons-package-24: Type list[str] | None :material-equal: `None`{ title="default value" }**
+- **:octicons-package-24: Type list [str ] | None :material-equal: `None`{ title="default value" }**
Pre-load modules that are not specified directly in [autodoc instructions][autodoc syntax] (`::: identifier`).
diff --git a/docs/usage/configuration/members.md b/docs/usage/configuration/members.md
index 1e5ff771..119d8294 100644
--- a/docs/usage/configuration/members.md
+++ b/docs/usage/configuration/members.md
@@ -2,8 +2,8 @@
## `members`
-- **:octicons-package-24: Type list[str] |
- bool | None :material-equal: `None`{ title="default value" }**
+- **:octicons-package-24: Type list [str ] |
+ bool | None :material-equal: `None`{ title="default value" }**
An explicit list of members to render.
@@ -97,8 +97,8 @@ INFO: **The default behavior (with unspecified `members` or `members: null`) is
## `inherited_members`
-- **:octicons-package-24: Type list[str] |
- bool :material-equal: `False`{ title="default value" }**
+- **:octicons-package-24: Type list [str ] |
+ bool :material-equal: `False`{ title="default value" }**
An explicit list of inherited members (for classes) to render.
@@ -331,7 +331,7 @@ def function_c():
## `filters`
-- **:octicons-package-24: Type list[str] | None :material-equal: `["!^_[^_]"]`{ title="default value" }**
+- **:octicons-package-24: Type list [str ] | None :material-equal: `["!^_[^_]"]`{ title="default value" }**
A list of filters applied to filter objects based on their name.
@@ -555,7 +555,7 @@ package
[:octicons-heart-fill-24:{ .pulse } Sponsors only](../../insiders/index.md){ .insiders } —
[:octicons-tag-24: Insiders 1.2.0](../../insiders/changelog.md#1.2.0)
-- **:octicons-package-24: Type bool | dict[str, bool] :material-equal: `False`{ title="default value" }**
+- **:octicons-package-24: Type bool | dict [str , bool ] :material-equal: `False`{ title="default value" }**
Whether to render summaries of modules, classes, functions (methods) and attributes.
diff --git a/docs/usage/customization.md b/docs/usage/customization.md
index 2567870e..5e82001e 100644
--- a/docs/usage/customization.md
+++ b/docs/usage/customization.md
@@ -5,6 +5,26 @@ and/or by overriding templates.
## CSS classes
+Our templates add [CSS](https://www.w3schools.com/Css/) classes to many HTML elements
+to make it possible for users to customize the resulting look and feel.
+
+To add CSS rules and style mkdocstrings' output,
+put them in a CSS file in your docs folder, for example in `docs/css/mkdocstrings.css`,
+and reference this file in [MkDocs' `extra_css` configuration option](https://www.mkdocs.org/user-guide/configuration/#extra_css):
+
+```yaml title="mkdocs.yml"
+extra_css:
+- css/mkdocstrings.css
+```
+
+Example:
+
+```css title="docs/css/mkdocstrings.css"
+.doc-section-title {
+ font-weight: bold;
+}
+```
+
The following CSS classes are used in the generated HTML:
- `doc`: on all the following elements
@@ -22,6 +42,8 @@ The following CSS classes are used in the generated HTML:
- `doc-labels`: on `span`s wrapping the object's labels
- `doc-label`: on `small` elements containing a label
- `doc-label-LABEL`: same, where `LABEL` is replaced by the actual label
+- `doc-section-title`: on section titles (depend on the [selected style for section rendering][docstring_style])
+- `doc-section-item`: on section items (depend on the [selected style for section rendering][docstring_style])
- `doc-md-description`: on `div`s containing HTML descriptions converted from Markdown docstrings
- `doc-symbol`: on `code` tags of symbol types
- `doc-symbol-heading`: on symbol types in headings
@@ -280,7 +302,7 @@ and the Jinja context available in their scope.
Available context:
- `config`: The handler configuration (dictionary).
-- `module`: The [Module][griffe.dataclasses.Module] instance.
+- `module`: The [Module][griffe.Module] instance.
#### `class.html`
@@ -297,7 +319,7 @@ Available context:
Available context:
- `config`: The handler configuration (dictionary).
-- `class`: The [Class][griffe.dataclasses.Class] instance.
+- `class`: The [Class][griffe.Class] instance.
#### `function.html`
@@ -311,7 +333,7 @@ Available context:
Available context:
- `config`: The handler configuration (dictionary).
-- `function`: The [Function][griffe.dataclasses.Function] instance.
+- `function`: The [Function][griffe.Function] instance.
#### `attribute.html`
@@ -324,7 +346,7 @@ Available context:
Available context:
- `config`: The handler configuration (dictionary).
-- `attribute`: The [Attribute][griffe.dataclasses.Attribute] instance.
+- `attribute`: The [Attribute][griffe.Attribute] instance.
#### Docstring sections
@@ -346,7 +368,7 @@ and `docstring/yields.html`:
Available context:
-- `section`: The [DocstringSection][griffe.docstrings.dataclasses.DocstringSection] instance (see `DocstringSection*` subclasses).
+- `section`: The [DocstringSection][griffe.DocstringSection] instance (see `DocstringSection*` subclasses).
### Syntax highlight in signatures
diff --git a/docs/usage/index.md b/docs/usage/index.md
index 670f6e57..57b9fdc1 100644
--- a/docs/usage/index.md
+++ b/docs/usage/index.md
@@ -141,8 +141,11 @@ More details at [Finding modules](#finding-modules).
This option allows resolving aliases (imports) to any external module.
Modules are considered external when they are not part
of the package your are injecting documentation for.
-Enabling this option will tell the handler to resolve aliases recursively
+Setting this option to `True` will tell the handler to resolve aliases recursively
when they are made public through the [`__all__`][__all__] variable.
+By default, the handler will only resolve aliases when they point at a private sibling
+of the source package, for example aliases going from `ast` to `_ast`.
+Set `load_external_modules` to `False` to prevent even that.
WARNING: **Use with caution**
This can load a *lot* of modules through [Griffe],
diff --git a/duties.py b/duties.py
index 1c3f6b79..f1909cc1 100644
--- a/duties.py
+++ b/duties.py
@@ -9,8 +9,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, Iterator
-from duty import duty
-from duty.callables import coverage, lazy, mkdocs, mypy, pytest, ruff, safety
+from duty import duty, tools
if TYPE_CHECKING:
from duty.context import Context
@@ -45,143 +44,72 @@ def material_insiders() -> Iterator[bool]: # noqa: D103
@duty
-def changelog(ctx: Context) -> None:
+def changelog(ctx: Context, bump: str = "") -> None:
"""Update the changelog in-place with latest commits.
Parameters:
- ctx: The context instance (passed automatically).
+ bump: Bump option passed to git-changelog.
"""
- from git_changelog.cli import main as git_changelog
+ ctx.run(tools.git_changelog(bump=bump or None), title="Updating changelog")
- ctx.run(git_changelog, args=[[]], title="Updating changelog")
-
-@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies", "check-api"])
-def check(ctx: Context) -> None: # noqa: ARG001
- """Check it all!
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
+@duty(pre=["check_quality", "check_types", "check_docs", "check-api"])
+def check(ctx: Context) -> None:
+ """Check it all!"""
@duty
def check_quality(ctx: Context) -> None:
- """Check the code quality.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
+ """Check the code quality."""
ctx.run(
- ruff.check(*PY_SRC_LIST, config="config/ruff.toml"),
+ tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml"),
title=pyprefix("Checking code quality"),
- command=f"ruff check --config config/ruff.toml {PY_SRC}",
- )
-
-
-@duty
-def check_dependencies(ctx: Context) -> None:
- """Check for vulnerabilities in dependencies.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
- # retrieve the list of dependencies
- requirements = ctx.run(
- ["uv", "pip", "freeze"],
- silent=True,
- allow_overrides=False,
- )
-
- ctx.run(
- safety.check(requirements),
- title="Checking dependencies",
- command="uv pip freeze | safety check --stdin",
)
@duty
def check_docs(ctx: Context) -> None:
- """Check if the documentation builds correctly.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
+ """Check if the documentation builds correctly."""
Path("htmlcov").mkdir(parents=True, exist_ok=True)
Path("htmlcov/index.html").touch(exist_ok=True)
with material_insiders():
ctx.run(
- mkdocs.build(strict=True, verbose=True),
+ tools.mkdocs.build(strict=True, verbose=True),
title=pyprefix("Building documentation"),
- command="mkdocs build -vs",
)
@duty
def check_types(ctx: Context) -> None:
- """Check that the code is correctly typed.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
+ """Check that the code is correctly typed."""
os.environ["MYPYPATH"] = "src"
ctx.run(
- mypy.run(*PY_SRC_LIST, config_file="config/mypy.ini"),
+ tools.mypy(*PY_SRC_LIST, config_file="config/mypy.ini"),
title=pyprefix("Type-checking"),
- command=f"mypy --config-file config/mypy.ini {PY_SRC}",
)
@duty
-def check_api(ctx: Context) -> None:
- """Check for API breaking changes.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
- from griffe.cli import check as g_check
-
- griffe_check = lazy(g_check, name="griffe.check")
+def check_api(ctx: Context, *cli_args: str) -> None:
+ """Check for API breaking changes."""
ctx.run(
- griffe_check("mkdocstrings_handlers.python", search_paths=["src"], color=True),
+ tools.griffe.check("mkdocstrings_handlers.python", search=["src"], color=True).add_args(*cli_args),
title="Checking for API breaking changes",
- command="griffe check -ssrc mkdocstrings_handlers.python",
nofail=True,
)
-@duty(silent=True)
-def clean(ctx: Context) -> None:
- """Delete temporary files.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
- ctx.run("rm -rf .coverage*")
- ctx.run("rm -rf .mypy_cache")
- ctx.run("rm -rf .pytest_cache")
- ctx.run("rm -rf tests/.pytest_cache")
- ctx.run("rm -rf build")
- ctx.run("rm -rf dist")
- ctx.run("rm -rf htmlcov")
- ctx.run("rm -rf pip-wheel-metadata")
- ctx.run("rm -rf site")
- ctx.run("find . -type d -name __pycache__ | xargs rm -rf")
- ctx.run("find . -name '*.rej' -delete")
-
-
@duty
-def docs(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None:
+def docs(ctx: Context, *cli_args: str, host: str = "127.0.0.1", port: int = 8000) -> None:
"""Serve the documentation (localhost:8000).
Parameters:
- ctx: The context instance (passed automatically).
host: The host to serve the docs from.
port: The port to serve the docs on.
"""
with material_insiders():
ctx.run(
- mkdocs.serve(dev_addr=f"{host}:{port}"),
+ tools.mkdocs.serve(dev_addr=f"{host}:{port}").add_args(*cli_args),
title="Serving documentation",
capture=False,
)
@@ -189,11 +117,7 @@ def docs(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None:
@duty
def docs_deploy(ctx: Context) -> None:
- """Deploy the documentation on GitHub pages.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
+ """Deploy the documentation to GitHub pages."""
os.environ["DEPLOY"] = "true"
with material_insiders() as insiders:
if not insiders:
@@ -206,7 +130,7 @@ def docs_deploy(ctx: Context) -> None:
nofail=True,
)
ctx.run(
- mkdocs.gh_deploy(remote_name="upstream", force=True),
+ tools.mkdocs.gh_deploy(remote_name="upstream", force=True),
title="Deploying documentation",
)
else:
@@ -219,24 +143,42 @@ def docs_deploy(ctx: Context) -> None:
@duty
def format(ctx: Context) -> None:
- """Run formatting tools on the code.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
+ """Run formatting tools on the code."""
ctx.run(
- ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True),
+ tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True),
title="Auto-fixing code",
)
- ctx.run(ruff.format(*PY_SRC_LIST, config="config/ruff.toml"), title="Formatting code")
+ ctx.run(tools.ruff.format(*PY_SRC_LIST, config="config/ruff.toml"), title="Formatting code")
+
+
+@duty
+def build(ctx: Context) -> None:
+ """Build source and wheel distributions."""
+ ctx.run(
+ tools.build(),
+ title="Building source and wheel distributions",
+ pty=PTY,
+ )
+
+
+@duty
+def publish(ctx: Context) -> None:
+ """Publish source and wheel distributions to PyPI."""
+ if not Path("dist").exists():
+ ctx.run("false", title="No distribution files found")
+ dists = [str(dist) for dist in Path("dist").iterdir()]
+ ctx.run(
+ tools.twine.upload(*dists, skip_existing=True),
+ title="Publishing source and wheel distributions to PyPI",
+ pty=PTY,
+ )
-@duty(post=["docs-deploy"])
-def release(ctx: Context, version: str) -> None:
+@duty(post=["build", "publish", "docs-deploy"])
+def release(ctx: Context, version: str = "") -> None:
"""Release a new Python package.
Parameters:
- ctx: The context instance (passed automatically).
version: The new version number to use.
"""
origin = ctx.run("git config --get remote.origin.url", silent=True)
@@ -245,64 +187,38 @@ def release(ctx: Context, version: str) -> None:
lambda: False,
title="Not releasing from insiders repository (do that from public repo instead!)",
)
+ if not (version := (version or input("> Version to release: ")).strip()):
+ ctx.run("false", title="A version must be provided")
ctx.run("git add pyproject.toml CHANGELOG.md", title="Staging files", pty=PTY)
ctx.run(["git", "commit", "-m", f"chore: Prepare release {version}"], title="Committing changes", pty=PTY)
ctx.run(f"git tag {version}", title="Tagging commit", pty=PTY)
ctx.run("git push", title="Pushing commits", pty=False)
ctx.run("git push --tags", title="Pushing tags", pty=False)
- ctx.run("pyproject-build", title="Building dist/wheel", pty=PTY)
- ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY)
-@duty(silent=True, aliases=["coverage"])
-def cov(ctx: Context) -> None:
- """Report coverage as text and HTML.
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
- ctx.run(coverage.combine, nofail=True)
- ctx.run(coverage.report(rcfile="config/coverage.ini"), capture=False)
- ctx.run(coverage.html(rcfile="config/coverage.ini"))
+@duty(silent=True, aliases=["cov"])
+def coverage(ctx: Context) -> None:
+ """Report coverage as text and HTML."""
+ ctx.run(tools.coverage.combine(), nofail=True)
+ ctx.run(tools.coverage.report(rcfile="config/coverage.ini"), capture=False)
+ ctx.run(tools.coverage.html(rcfile="config/coverage.ini"))
@duty
-def test(ctx: Context, match: str = "") -> None:
+def test(ctx: Context, *cli_args: str, match: str = "") -> None:
"""Run the test suite.
Parameters:
- ctx: The context instance (passed automatically).
match: A pytest expression to filter selected tests.
"""
py_version = f"{sys.version_info.major}{sys.version_info.minor}"
os.environ["COVERAGE_FILE"] = f".coverage.{py_version}"
ctx.run(
- pytest.run("-n", "auto", "tests", config_file="config/pytest.ini", select=match, color="yes"),
+ tools.pytest(
+ "tests",
+ config_file="config/pytest.ini",
+ select=match,
+ color="yes",
+ ).add_args("-n", "auto", *cli_args),
title=pyprefix("Running tests"),
- command=f"pytest -c config/pytest.ini -n auto -k{match!r} --color=yes tests",
)
-
-
-@duty
-def vscode(ctx: Context) -> None:
- """Configure VSCode.
-
- This task will overwrite the following files,
- so make sure to back them up:
-
- - `.vscode/launch.json`
- - `.vscode/settings.json`
- - `.vscode/tasks.json`
-
- Parameters:
- ctx: The context instance (passed automatically).
- """
-
- def update_config(filename: str) -> None:
- source_file = Path("config", "vscode", filename)
- target_file = Path(".vscode", filename)
- target_file.parent.mkdir(exist_ok=True)
- target_file.write_text(source_file.read_text())
-
- for filename in ("launch.json", "settings.json", "tasks.json"):
- ctx.run(update_config, args=[filename], title=f"Update .vscode/{filename}")
diff --git a/mkdocs.yml b/mkdocs.yml
index 9a739787..040ab89b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -93,6 +93,9 @@ extra_css:
- css/mkdocstrings.css
- css/insiders.css
+extra_javascript:
+- js/feedback.js
+
markdown_extensions:
- abbr
- attr_list
@@ -149,6 +152,7 @@ plugins:
- https://docs.python.org/3/objects.inv
- https://mkdocstrings.github.io/objects.inv
- https://mkdocstrings.github.io/griffe/objects.inv
+ - https://python-markdown.github.io/objects.inv
options:
docstring_options:
ignore_init_summary: true
@@ -157,6 +161,7 @@ plugins:
heading_level: 1
inherited_members: true
merge_init_into_class: true
+ parameter_headings: true
preload_modules: [mkdocstrings]
separate_signature: true
show_root_heading: true
@@ -190,3 +195,15 @@ extra:
link: https://gitter.im/mkdocstrings/python
- icon: fontawesome/brands/python
link: https://pypi.org/project/mkdocstrings-python/
+ analytics:
+ feedback:
+ title: Was this page helpful?
+ ratings:
+ - icon: material/emoticon-happy-outline
+ name: This page was helpful
+ data: 1
+ note: Thanks for your feedback!
+ - icon: material/emoticon-sad-outline
+ name: This page could be improved
+ data: 0
+ note: Let us know how we can improve this page.
diff --git a/pyproject.toml b/pyproject.toml
index 1ce73547..f1ff7334 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,14 +22,16 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Topic :: Documentation",
"Topic :: Software Development",
"Topic :: Utilities",
"Typing :: Typed",
]
dependencies = [
- "mkdocstrings>=0.24.2",
- "griffe>=0.37",
+ "mkdocstrings>=0.25",
+ "mkdocs-autorefs>=1.0",
+ "griffe>=0.49",
]
[project.urls]
@@ -49,3 +51,21 @@ version = {source = "scm"}
package-dir = "src"
includes = ["src/mkdocstrings_handlers"]
editable-backend = "editables"
+excludes = ["**/.pytest_cache"]
+source-includes = [
+ "config",
+ "docs",
+ "scripts",
+ "share",
+ "tests",
+ "devdeps.txt",
+ "duties.py",
+ "mkdocs.yml",
+ "*.md",
+ "LICENSE",
+]
+
+[tool.pdm.build.wheel-data]
+data = [
+ {path = "share/**/*", relative-to = "."},
+]
diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py
index a1115f55..b2f6d3e4 100644
--- a/scripts/gen_credits.py
+++ b/scripts/gen_credits.py
@@ -26,8 +26,8 @@
pyproject = tomllib.load(pyproject_file)
project = pyproject["project"]
project_name = project["name"]
-with open("devdeps.txt") as devdeps_file:
- devdeps = [line.strip() for line in devdeps_file if not line.startswith("-e")]
+with project_dir.joinpath("devdeps.txt").open() as devdeps_file:
+ devdeps = [line.strip() for line in devdeps_file if line.strip() and not line.strip().startswith(("-e", "#"))]
PackageMetadata = Dict[str, Union[str, Iterable[str]]]
Metadata = Dict[str, PackageMetadata]
@@ -47,13 +47,6 @@ def _norm_name(name: str) -> str:
return name.replace("_", "-").replace(".", "-").lower()
-def _norm_spec(spec: str) -> set[str]:
- clean_spec = spec.split("]", 1)[-1].split(";", 1)[0].replace("(", "").replace(")", "").replace(" ", "").strip()
- if clean_spec:
- return set(clean_spec.split(","))
- return set()
-
-
def _requirements(deps: list[str]) -> dict[str, Requirement]:
return {_norm_name((req := Requirement(dep)).name): req for dep in deps}
@@ -74,6 +67,7 @@ def _get_metadata() -> Metadata:
metadata[name] = _merge_fields(pkg.metadata) # type: ignore[arg-type]
metadata[name]["spec"] = set()
metadata[name]["extras"] = set()
+ metadata[name].setdefault("summary", "")
_set_license(metadata[name])
return metadata
@@ -94,7 +88,7 @@ def _set_license(metadata: PackageMetadata) -> None:
def _get_deps(base_deps: dict[str, Requirement], metadata: Metadata) -> Metadata:
deps = {}
for dep_name, dep_req in base_deps.items():
- if dep_name not in metadata:
+ if dep_name not in metadata or dep_name == "mkdocstrings-python":
continue
metadata[dep_name]["spec"] |= {str(spec) for spec in dep_req.specifier} # type: ignore[operator]
metadata[dep_name]["extras"] |= dep_req.extras # type: ignore[operator]
@@ -137,8 +131,8 @@ def _render_credits() -> str:
template_data = {
"project_name": project_name,
- "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: str(dep["name"])),
- "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: str(dep["name"])),
+ "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: str(dep["name"]).lower()),
+ "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: str(dep["name"]).lower()),
"more_credits": "http://pawamoy.github.io/credits/",
}
template_text = dedent(
@@ -147,12 +141,12 @@ def _render_credits() -> str:
These projects were used to build *{{ project_name }}*. **Thank you!**
- [`python`](https://www.python.org/) |
- [`uv`](https://github.com/astral-sh/uv) |
- [`copier-uv`](https://github.com/pawamoy/copier-uv)
+ [Python](https://www.python.org/) |
+ [uv](https://github.com/astral-sh/uv) |
+ [copier-uv](https://github.com/pawamoy/copier-uv)
{% macro dep_line(dep) -%}
- [`{{ dep.name }}`](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec|sort(reverse=True)|join(", ") ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }}
+ [{{ dep.name }}](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec|sort(reverse=True)|join(", ") ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }}
{%- endmacro %}
{% if prod_dependencies -%}
diff --git a/scripts/gen_ref_nav.py b/scripts/gen_ref_nav.py
index b369536c..6939e864 100644
--- a/scripts/gen_ref_nav.py
+++ b/scripts/gen_ref_nav.py
@@ -29,7 +29,7 @@
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts)
- fd.write(f"::: {ident}")
+ fd.write(f"---\ntitle: {ident}\n---\n\n::: {ident}")
mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path.relative_to(root))
diff --git a/scripts/make b/scripts/make
index 4190622e..d898022e 100755
--- a/scripts/make
+++ b/scripts/make
@@ -1,155 +1,210 @@
-#!/usr/bin/env bash
-
-set -e
-export PYTHON_VERSIONS=${PYTHON_VERSIONS-3.8 3.9 3.10 3.11 3.12}
-
-exe=""
-prefix=""
-
-
-# Install runtime and development dependencies,
-# as well as current project in editable mode.
-uv_install() {
- uv pip compile pyproject.toml devdeps.txt | uv pip install -r -
- uv pip install -e .
-}
-
-
-# Setup the development environment by installing dependencies
-# in multiple Python virtual environments with uv:
-# one venv per Python version in `.venvs/$py`,
-# and an additional default venv in `.venv`.
-setup() {
- if ! command -v uv &>/dev/null; then
- echo "make: setup: uv must be installed, see https://github.com/astral-sh/uv" >&2
- return 1
- fi
-
- if [ -n "${PYTHON_VERSIONS}" ]; then
- for version in ${PYTHON_VERSIONS}; do
- if [ ! -d ".venvs/${version}" ]; then
- uv venv --seed --python "${version}" ".venvs/${version}"
- fi
- VIRTUAL_ENV="${PWD}/.venvs/${version}" uv_install
- done
- fi
-
- if [ ! -d .venv ]; then uv venv --seed --python python; fi
- uv_install
-}
-
-
-# Activate a Python virtual environments.
-# The annoying operating system also requires
-# that we set some global variables to help it find commands...
-activate() {
- local path
- if [ -f "$1/bin/activate" ]; then
- source "$1/bin/activate"
+#!/usr/bin/env python3
+"""Management commands."""
+
+from __future__ import annotations
+
+import os
+import shutil
+import subprocess
+import sys
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Any, Iterator
+
+PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.8 3.9 3.10 3.11 3.12 3.13").split()
+
+exe = ""
+prefix = ""
+
+
+def shell(cmd: str, capture_output: bool = False, **kwargs: Any) -> str | None:
+ """Run a shell command."""
+ if capture_output:
+ return subprocess.check_output(cmd, shell=True, text=True, **kwargs) # noqa: S602
+ subprocess.run(cmd, shell=True, check=True, stderr=subprocess.STDOUT, **kwargs) # noqa: S602
+ return None
+
+
+@contextmanager
+def environ(**kwargs: str) -> Iterator[None]:
+ """Temporarily set environment variables."""
+ original = dict(os.environ)
+ os.environ.update(kwargs)
+ try:
+ yield
+ finally:
+ os.environ.clear()
+ os.environ.update(original)
+
+
+def uv_install() -> None:
+ """Install dependencies using uv."""
+ uv_opts = ""
+ if "UV_RESOLUTION" in os.environ:
+ uv_opts = f"--resolution={os.getenv('UV_RESOLUTION')}"
+ requirements = shell(f"uv pip compile {uv_opts} pyproject.toml devdeps.txt", capture_output=True)
+ shell("uv pip install -r -", input=requirements, text=True)
+ if "CI" not in os.environ:
+ shell("uv pip install --no-deps -e .")
+ else:
+ shell("uv pip install --no-deps .")
+
+
+def setup() -> None:
+ """Setup the project."""
+ if not shutil.which("uv"):
+ raise ValueError("make: setup: uv must be installed, see https://github.com/astral-sh/uv")
+
+ print("Installing dependencies (default environment)") # noqa: T201
+ default_venv = Path(".venv")
+ if not default_venv.exists():
+ shell("uv venv --python python")
+ uv_install()
+
+ if PYTHON_VERSIONS:
+ for version in PYTHON_VERSIONS:
+ print(f"\nInstalling dependencies (python{version})") # noqa: T201
+ venv_path = Path(f".venvs/{version}")
+ if not venv_path.exists():
+ shell(f"uv venv --python {version} {venv_path}")
+ with environ(VIRTUAL_ENV=str(venv_path.resolve())):
+ uv_install()
+
+
+def activate(path: str) -> None:
+ """Activate a virtual environment."""
+ global exe, prefix # noqa: PLW0603
+
+ if (bin := Path(path, "bin")).exists():
+ activate_script = bin / "activate_this.py"
+ elif (scripts := Path(path, "Scripts")).exists():
+ activate_script = scripts / "activate_this.py"
+ exe = ".exe"
+ prefix = f"{path}/Scripts/"
+ else:
+ raise ValueError(f"make: activate: Cannot find activation script in {path}")
+
+ if not activate_script.exists():
+ raise ValueError(f"make: activate: Cannot find activation script in {path}")
+
+ exec(activate_script.read_text(), {"__file__": str(activate_script)}) # noqa: S102
+
+
+def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None:
+ """Run a command in a virtual environment."""
+ kwargs = {"check": True, **kwargs}
+ if version == "default":
+ activate(".venv")
+ subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510
+ else:
+ activate(f".venvs/{version}")
+ os.environ["MULTIRUN"] = "1"
+ subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510
+
+
+def multirun(cmd: str, *args: str, **kwargs: Any) -> None:
+ """Run a command for all configured Python versions."""
+ if PYTHON_VERSIONS:
+ for version in PYTHON_VERSIONS:
+ run(version, cmd, *args, **kwargs)
+ else:
+ run("default", cmd, *args, **kwargs)
+
+
+def allrun(cmd: str, *args: str, **kwargs: Any) -> None:
+ """Run a command in all virtual environments."""
+ run("default", cmd, *args, **kwargs)
+ if PYTHON_VERSIONS:
+ multirun(cmd, *args, **kwargs)
+
+
+def clean() -> None:
+ """Delete build artifacts and cache files."""
+ paths_to_clean = ["build", "dist", "htmlcov", "site", ".coverage*", ".pdm-build"]
+ for path in paths_to_clean:
+ shell(f"rm -rf {path}")
+
+ cache_dirs = [".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"]
+ for dirpath in Path(".").rglob("*"):
+ if any(dirpath.match(pattern) for pattern in cache_dirs) and not (dirpath.match(".venv") or dirpath.match(".venvs")):
+ shutil.rmtree(path, ignore_errors=True)
+
+
+def vscode() -> None:
+ """Configure VSCode to work on this project."""
+ Path(".vscode").mkdir(parents=True, exist_ok=True)
+ shell("cp -v config/vscode/* .vscode")
+
+
+def main() -> int:
+ """Main entry point."""
+ args = list(sys.argv[1:])
+ if not args or args[0] == "help":
+ if len(args) > 1:
+ run("default", "duty", "--help", args[1])
+ else:
+ print("Available commands") # noqa: T201
+ print(" help Print this help. Add task name to print help.") # noqa: T201
+ print(" setup Setup all virtual environments (install dependencies).") # noqa: T201
+ print(" run Run a command in the default virtual environment.") # noqa: T201
+ print(" multirun Run a command for all configured Python versions.") # noqa: T201
+ print(" allrun Run a command in all virtual environments.") # noqa: T201
+ print(" 3.x Run a command in the virtual environment for Python 3.x.") # noqa: T201
+ print(" clean Delete build artifacts and cache files.") # noqa: T201
+ print(" vscode Configure VSCode to work on this project.") # noqa: T201
+ try:
+ run("default", "python", "-V", capture_output=True)
+ except (subprocess.CalledProcessError, ValueError):
+ pass
+ else:
+ print("\nAvailable tasks") # noqa: T201
+ run("default", "duty", "--list")
return 0
- fi
- if [ -f "$1/Scripts/activate.bat" ]; then
- "$1/Scripts/activate.bat"
- exe=".exe"
- prefix="$1/Scripts/"
- return 0
- fi
- echo "run: Cannot activate venv $1" >&2
- return 1
-}
-
-
-# Run a command in all configured Python virtual environments.
-# We handle the case when the `PYTHON_VERSIONS` environment variable
-# is unset or empty, for robustness.
-multirun() {
- local cmd="$1"
- shift
-
- if [ -n "${PYTHON_VERSIONS}" ]; then
- for version in ${PYTHON_VERSIONS}; do
- (activate ".venvs/${version}" && MULTIRUN=1 "${prefix}${cmd}${exe}" "$@")
- done
- else
- (activate .venv && "${prefix}${cmd}${exe}" "$@")
- fi
-}
-
-
-# Run a command in the default Python virtual environment.
-# We rely on `multirun`'s handling of empty `PYTHON_VERSIONS`.
-singlerun() {
- PYTHON_VERSIONS= multirun "$@"
-}
-
-
-# Record options following a command name,
-# until a non-option argument is met or there are no more arguments.
-# Output each option on a new line, so the parent caller can store them in an array.
-# Return the number of times the parent caller must shift arguments.
-options() {
- local shift_count=0
- for arg in "$@"; do
- if [[ "${arg}" =~ ^- || "${arg}" =~ ^.+= ]]; then
- echo "${arg}"
- ((shift_count++))
- else
- break
- fi
- done
- return ${shift_count}
-}
-
-
-# Main function.
-main() {
- local cmd
- while [ $# -ne 0 ]; do
- cmd="$1"
- shift
-
- # Handle `run` early to simplify `case` below.
- if [ "${cmd}" = "run" ]; then
- singlerun "$@"
- exit $?
- fi
-
- # Handle `multirun` early to simplify `case` below.
- if [ "${cmd}" = "multirun" ]; then
- multirun "$@"
- exit $?
- fi
-
- # All commands except `run` and `multirun` can be chained on a single line.
- # Some of them accept options in two formats: `-f`, `--flag` and `param=value`.
- # Some of them don't, and will print warnings/errors if options were given.
- opts=($(options "$@")) || shift $?
-
- case "${cmd}" in
- # The following commands require special handling.
- help|"")
- singlerun duty --list ;;
- setup)
- setup ;;
- check)
- multirun duty check-quality check-types check-docs
- singlerun duty check-dependencies check-api
- ;;
-
- # The following commands run in all venvs.
- check-quality|\
- check-docs|\
- check-types|\
- test)
- multirun duty "${cmd}" "${opts[@]}" ;;
-
- # The following commands run in the default venv only.
- *)
- singlerun duty "${cmd}" "${opts[@]}" ;;
- esac
- done
-}
-
-
-# Execute the main function.
-main "$@"
+
+ while args:
+ cmd = args.pop(0)
+
+ if cmd == "run":
+ run("default", *args)
+ return 0
+
+ if cmd == "multirun":
+ multirun(*args)
+ return 0
+
+ if cmd == "allrun":
+ allrun(*args)
+ return 0
+
+ if cmd.startswith("3."):
+ run(cmd, *args)
+ return 0
+
+ opts = []
+ while args and (args[0].startswith("-") or "=" in args[0]):
+ opts.append(args.pop(0))
+
+ if cmd == "clean":
+ clean()
+ elif cmd == "setup":
+ setup()
+ elif cmd == "vscode":
+ vscode()
+ elif cmd == "check":
+ multirun("duty", "check-quality", "check-types", "check-docs")
+ run("default", "duty", "check-api")
+ elif cmd in {"check-quality", "check-docs", "check-types", "test"}:
+ multirun("duty", cmd, *opts)
+ else:
+ run("default", "duty", cmd, *opts)
+
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ sys.exit(main())
+ except subprocess.CalledProcessError as process:
+ if process.output:
+ print(process.output, file=sys.stderr) # noqa: T201
+ sys.exit(process.returncode)
diff --git a/src/mkdocstrings_handlers/python/debug.py b/src/mkdocstrings_handlers/python/debug.py
index 7a4e8791..e44f2be5 100644
--- a/src/mkdocstrings_handlers/python/debug.py
+++ b/src/mkdocstrings_handlers/python/debug.py
@@ -37,6 +37,8 @@ class Environment:
"""Python interpreter name."""
interpreter_version: str
"""Python interpreter version."""
+ interpreter_path: str
+ """Path to Python executable."""
platform: str
"""Operating System."""
packages: list[Package]
@@ -83,6 +85,7 @@ def get_debug_info() -> Environment:
return Environment(
interpreter_name=py_name,
interpreter_version=py_version,
+ interpreter_path=sys.executable,
platform=platform.platform(),
variables=[Variable(var, val) for var in variables if (val := os.getenv(var))],
packages=[Package(pkg, get_version(pkg)) for pkg in packages],
@@ -93,7 +96,7 @@ def print_debug_info() -> None:
"""Print debug/environment information."""
info = get_debug_info()
print(f"- __System__: {info.platform}")
- print(f"- __Python__: {info.interpreter_name} {info.interpreter_version}")
+ print(f"- __Python__: {info.interpreter_name} {info.interpreter_version} ({info.interpreter_path})")
print("- __Environment variables__:")
for var in info.variables:
print(f" - `{var.name}`: `{var.value}`")
diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py
index fa8e384c..a4a5b47c 100644
--- a/src/mkdocstrings_handlers/python/handler.py
+++ b/src/mkdocstrings_handlers/python/handler.py
@@ -9,14 +9,18 @@
import sys
from collections import ChainMap
from contextlib import suppress
+from pathlib import Path
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, Iterator, Mapping, Sequence
-from griffe.collections import LinesCollection, ModulesCollection
-from griffe.docstrings.parsers import Parser
-from griffe.exceptions import AliasResolutionError
-from griffe.extensions import load_extensions
-from griffe.loader import GriffeLoader
-from griffe.logger import patch_loggers
+from griffe import (
+ AliasResolutionError,
+ GriffeLoader,
+ LinesCollection,
+ ModulesCollection,
+ Parser,
+ load_extensions,
+ patch_loggers,
+)
from mkdocstrings.extension import PluginError
from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem
from mkdocstrings.inventory import Inventory
@@ -52,6 +56,8 @@ def chdir(path: str) -> Iterator[None]: # noqa: D103
class PythonHandler(BaseHandler):
"""The Python handler class."""
+ name: str = "python"
+ """The handler's name."""
domain: str = "py" # to match Sphinx's default domain
"""The cross-documentation domain/language for this handler."""
enable_inventory: bool = True
@@ -159,7 +165,7 @@ class PythonHandler(BaseHandler):
Attributes: Docstrings options:
docstring_style (str): The docstring style to use: `google`, `numpy`, `sphinx`, or `None`. Default: `"google"`.
- docstring_options (dict): The options for the docstring parser. See parsers under [`griffe.docstrings`][].
+ docstring_options (dict): The options for the docstring parser. See [docstring parsers](https://mkdocstrings.github.io/griffe/reference/docstrings/) and their options in Griffe docs.
docstring_section_style (str): The style used to render docstring sections. Options: `table`, `list`, `spacy`. Default: `"table"`.
merge_init_into_class (bool): Whether to merge the `__init__` method into the class' signature and docstring. Default: `False`.
show_if_no_docstring (bool): Show the object heading even if it has no docstring or children with docstrings. Default: `False`.
@@ -194,7 +200,7 @@ def __init__(
config_file_path: str | None = None,
paths: list[str] | None = None,
locale: str = "en",
- load_external_modules: bool = False,
+ load_external_modules: bool | None = None,
**kwargs: Any,
) -> None:
"""Initialize the handler.
@@ -208,21 +214,42 @@ def __init__(
**kwargs: Same thing, but with keyword arguments.
"""
super().__init__(*args, **kwargs)
+
+ # Warn if user overrides base templates.
+ if custom_templates := kwargs.get("custom_templates", ()):
+ config_dir = Path(config_file_path or "./mkdocs.yml").parent
+ for theme_dir in config_dir.joinpath(custom_templates, "python").iterdir():
+ if theme_dir.joinpath("_base").is_dir():
+ logger.warning(
+ f"Overriding base template '{theme_dir.name}/_base/.html.jinja' is not supported, "
+ f"override '{theme_dir.name}/.html.jinja' instead",
+ )
+
self._config_file_path = config_file_path
self._load_external_modules = load_external_modules
paths = paths or []
+
+ # Expand paths with glob patterns.
glob_base_dir = os.path.dirname(os.path.abspath(config_file_path)) if config_file_path else "."
with chdir(glob_base_dir):
resolved_globs = [glob.glob(path) for path in paths]
paths = [path for glob_list in resolved_globs for path in glob_list]
+
+ # By default, add the directory of the config file to the search paths.
if not paths and config_file_path:
paths.append(os.path.dirname(config_file_path))
- search_paths = [path for path in sys.path if path] # eliminate empty path
+
+ # Initialize search paths from `sys.path`, eliminating empty paths.
+ search_paths = [path for path in sys.path if path]
+
for path in reversed(paths):
+ # If it's not absolute, make path relative to the config file path, then make it absolute.
if not os.path.isabs(path) and config_file_path:
path = os.path.abspath(os.path.join(os.path.dirname(config_file_path), path)) # noqa: PLW2901
+ # Don't add duplicates.
if path not in search_paths:
search_paths.insert(0, path)
+
self._paths = search_paths
self._modules_collection: ModulesCollection = ModulesCollection()
self._lines_collection: LinesCollection = LinesCollection()
@@ -272,7 +299,7 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem:
if unknown_module:
extensions = self.normalize_extension_paths(final_config.get("extensions", []))
loader = GriffeLoader(
- extensions=load_extensions(extensions),
+ extensions=load_extensions(*extensions),
search_paths=self._paths,
docstring_parser=parser,
docstring_options=parser_options,
@@ -321,7 +348,7 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem:
def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa: D102 (ignore missing docstring)
final_config = ChainMap(config, self.default_config) # type: ignore[arg-type]
- template_name = rendering.do_get_template(data)
+ template_name = rendering.do_get_template(self.env, data)
template = self.env.get_template(template_name)
# Heading level is a "state" variable, that will change at each step
@@ -374,7 +401,13 @@ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa
},
)
- def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore missing docstring)
+ def update_env(self, md: Markdown, config: dict) -> None:
+ """Update the Jinja environment with custom filters and tests.
+
+ Parameters:
+ md: The Markdown instance.
+ config: The configuration dictionary.
+ """
super().update_env(md, config)
self.env.trim_blocks = True
self.env.lstrip_blocks = True
@@ -444,7 +477,7 @@ def get_handler(
config_file_path: str | None = None,
paths: list[str] | None = None,
locale: str = "en",
- load_external_modules: bool = False,
+ load_external_modules: bool | None = None,
**config: Any, # noqa: ARG001
) -> PythonHandler:
"""Simply return an instance of `PythonHandler`.
diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py
index 878b74aa..d5a88699 100644
--- a/src/mkdocstrings_handlers/python/rendering.py
+++ b/src/mkdocstrings_handlers/python/rendering.py
@@ -9,20 +9,24 @@
import sys
import warnings
from functools import lru_cache, partial
+from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Match, Pattern, Sequence
-from griffe.docstrings.dataclasses import (
+from griffe import (
+ Alias,
DocstringSectionAttributes,
DocstringSectionClasses,
DocstringSectionFunctions,
DocstringSectionModules,
+ Object,
)
-from jinja2 import pass_context
+from jinja2 import TemplateNotFound, pass_context, pass_environment
from markupsafe import Markup
from mkdocstrings.loggers import get_logger
if TYPE_CHECKING:
- from griffe.dataclasses import Alias, Attribute, Class, Function, Module, Object
+ from griffe import Attribute, Class, Function, Module
+ from jinja2 import Environment, Template
from jinja2.runtime import Context
from mkdocstrings.handlers.base import CollectorItem
@@ -137,7 +141,8 @@ def do_format_signature(
The same code, formatted.
"""
env = context.environment
- template = env.get_template("signature.html")
+ # TODO: Stop using `do_get_template` when `*.html` templates are removed.
+ template = env.get_template(do_get_template(env, "signature"))
config_annotations = context.parent["config"]["show_signature_annotations"]
old_stash_ref_filter = env.filters["stash_crossref"]
@@ -204,7 +209,8 @@ def do_format_attribute(
The same code, formatted.
"""
env = context.environment
- template = env.get_template("expression.html")
+ # TODO: Stop using `do_get_template` when `*.html` templates are removed.
+ template = env.get_template(do_get_template(env, "expression"))
annotations = context.parent["config"]["show_signature_annotations"]
separate_signature = context.parent["config"]["separate_signature"]
old_stash_ref_filter = env.filters["stash_crossref"]
@@ -289,7 +295,10 @@ def do_crossref(path: str, *, brief: bool = True) -> Markup:
full_path = path
if brief:
path = full_path.split(".")[-1]
- return Markup("{path}").format(full_path=full_path, path=path)
+ return Markup(" '|safe if config.show_symbol_type_toc else '') + attribute.name,
- ) %}
-
- {% block heading scoped %}
- {% if config.show_symbol_type_heading %}{% endif %}
- {% if config.separate_signature %}
- {{ attribute_name }}
- {% else %}
- {%+ filter highlight(language="python", inline=True) %}
- {{ attribute_name }}{% if attribute.annotation %}: {{ attribute.annotation }}{% endif %}
- {% if attribute.value %} = {{ attribute.value }}{% endif %}
- {% endfilter %}
- {% endif %}
- {% endblock heading %}
-
- {% block labels scoped %}
- {% with labels = attribute.labels %}
- {% include "labels.html" with context %}
- {% endwith %}
- {% endblock labels %}
-
- {% endfilter %}
-
- {% block signature scoped %}
- {% if config.separate_signature %}
- {% filter format_attribute(attribute, config.line_length, crossrefs=config.signature_crossrefs) %}
- {{ attribute.name }}
- {% endfilter %}
- {% endif %}
- {% endblock signature %}
-
- {% else %}
-
- {% if config.show_root_toc_entry %}
- {% filter heading(heading_level,
- role="data" if attribute.parent.kind.value == "module" else "attr",
- id=html_id,
- toc_label=(' '|safe if config.show_symbol_type_toc else '') + attribute.name,
- hidden=True,
- ) %}
- {% endfilter %}
- {% endif %}
- {% set heading_level = heading_level - 1 %}
- {% endif %}
-
- '|safe if config.show_symbol_type_toc else '') + attribute.name,
+ ) %}
+
+ {% block heading scoped %}
+ {#- Heading block.
+
+ This block renders the heading for the attribute.
+ -#}
+ {% if config.show_symbol_type_heading %}{% endif %}
+ {% if config.separate_signature %}
+ {{ attribute_name }}
+ {% else %}
+ {%+ filter highlight(language="python", inline=True) %}
+ {{ attribute_name }}{% if attribute.annotation %}: {{ attribute.annotation }}{% endif %}
+ {% if attribute.value %} = {{ attribute.value }}{% endif %}
+ {% endfilter %}
+ {% endif %}
+ {% endblock heading %}
+
+ {% block labels scoped %}
+ {#- Labels block.
+
+ This block renders the labels for the attribute.
+ -#}
+ {% with labels = attribute.labels %}
+ {% include "labels"|get_template with context %}
+ {% endwith %}
+ {% endblock labels %}
+
+ {% endfilter %}
+
+ {% block signature scoped %}
+ {#- Signature block.
+
+ This block renders the signature for the attribute.
+ -#}
+ {% if config.separate_signature %}
+ {% filter format_attribute(attribute, config.line_length, crossrefs=config.signature_crossrefs) %}
+ {{ attribute.name }}
+ {% endfilter %}
+ {% endif %}
+ {% endblock signature %}
+
+ {% else %}
+
+ {% if config.show_root_toc_entry %}
+ {% filter heading(heading_level,
+ role="data" if attribute.parent.kind.value == "module" else "attr",
+ id=html_id,
+ toc_label=(' '|safe if config.show_symbol_type_toc else '') + attribute.name,
+ hidden=True,
+ ) %}
+ {% endfilter %}
+ {% endif %}
+ {% set heading_level = heading_level - 1 %}
+ {% endif %}
+
+ '|safe if config.show_symbol_type_toc else '') + class.name,
- ) %}
-
- {% block heading scoped %}
- {% if config.show_symbol_type_heading %}{% endif %}
- {% if config.separate_signature %}
- {{ class_name }}
- {% elif config.merge_init_into_class and "__init__" in class.all_members %}
- {% with function = class.all_members["__init__"] %}
- {%+ filter highlight(language="python", inline=True) %}
- {{ class_name }}{% include "signature.html" with context %}
- {% endfilter %}
- {% endwith %}
- {% else %}
- {{ class_name }}
- {% endif %}
- {% endblock heading %}
-
- {% block labels scoped %}
- {% with labels = class.labels %}
- {% include "labels.html" with context %}
- {% endwith %}
- {% endblock labels %}
-
- {% endfilter %}
-
- {% block signature scoped %}
- {% if config.separate_signature and config.merge_init_into_class %}
- {% if "__init__" in class.all_members %}
- {% with function = class.all_members["__init__"] %}
- {% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
- {{ class.name }}
- {% endfilter %}
- {% endwith %}
- {% endif %}
- {% endif %}
- {% endblock signature %}
-
- {% else %}
- {% if config.show_root_toc_entry %}
- {% filter heading(heading_level,
- role="class",
- id=html_id,
- toc_label=(' '|safe if config.show_symbol_type_toc else '') + class.name,
- hidden=True,
- ) %}
- {% endfilter %}
- {% endif %}
- {% set heading_level = heading_level - 1 %}
- {% endif %}
-
-
- Bases: {% for expression in class.bases -%}
- {% include "expression.html" with context %}{% if not loop.last %}, {% endif %}
- {% endfor -%}
-
- {%- if init.relative_filepath.is_absolute() -%}
- {{ init.relative_package_filepath }}
- {%- else -%}
- {{ init.relative_filepath }}
- {%- endif -%}
-
- {%- if class.relative_filepath.is_absolute() -%}
- {{ class.relative_package_filepath }}
- {%- else -%}
- {{ class.relative_filepath }}
- {%- endif -%}
- '|safe if config.show_symbol_type_toc else '') + class.name,
+ ) %}
+
+ {% block heading scoped %}
+ {#- Heading block.
+
+ This block renders the heading for the class.
+ -#}
+ {% if config.show_symbol_type_heading %}{% endif %}
+ {% if config.separate_signature %}
+ {{ class_name }}
+ {% elif config.merge_init_into_class and "__init__" in class.all_members %}
+ {% with function = class.all_members["__init__"] %}
+ {%+ filter highlight(language="python", inline=True) %}
+ {{ class_name }}{% include "signature"|get_template with context %}
+ {% endfilter %}
+ {% endwith %}
+ {% else %}
+ {{ class_name }}
+ {% endif %}
+ {% endblock heading %}
+
+ {% block labels scoped %}
+ {#- Labels block.
+
+ This block renders the labels for the class.
+ -#}
+ {% with labels = class.labels %}
+ {% include "labels"|get_template with context %}
+ {% endwith %}
+ {% endblock labels %}
+
+ {% endfilter %}
+
+ {% block signature scoped %}
+ {#- Signature block.
+
+ This block renders the signature for the class.
+ -#}
+ {% if config.separate_signature and config.merge_init_into_class %}
+ {% if "__init__" in class.all_members %}
+ {% with function = class.all_members["__init__"] %}
+ {% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
+ {{ class.name }}
+ {% endfilter %}
+ {% endwith %}
+ {% endif %}
+ {% endif %}
+ {% endblock signature %}
+
+ {% else %}
+ {% if config.show_root_toc_entry %}
+ {% filter heading(heading_level,
+ role="class",
+ id=html_id,
+ toc_label=(' '|safe if config.show_symbol_type_toc else '') + class.name,
+ hidden=True,
+ ) %}
+ {% endfilter %}
+ {% endif %}
+ {% set heading_level = heading_level - 1 %}
+ {% endif %}
+
+
+ Bases: {% for expression in class.bases -%}
+ {% include "expression"|get_template with context %}{% if not loop.last %}, {% endif %}
+ {% endfor -%}
+
+ {%- if init.relative_filepath.is_absolute() -%}
+ {{ init.relative_package_filepath }}
+ {%- else -%}
+ {{ init.relative_filepath }}
+ {%- endif -%}
+
+ {%- if class.relative_filepath.is_absolute() -%}
+ {{ class.relative_package_filepath }}
+ {%- else -%}
+ {{ class.relative_filepath }}
+ {%- endif -%}
+ {{ section.title or lang.t("Attributes:") }}
-| {{ lang.t("Name") }} | -{{ lang.t("Type") }} | -{{ lang.t("Description") }} | -
|---|---|---|
{{ attribute.name }} |
-
- {% if attribute.annotation %}
- {% with expression = attribute.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ attribute.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Attributes:") }}
-{{ attribute.name }}
- {% if attribute.annotation %}
- {% with expression = attribute.annotation %}
- ({% include "expression.html" with context %})
- {% endwith %}
- {% endif %}
- –
- | {{ (section.title or lang.t("ATTRIBUTE")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
{{ attribute.name }} |
-
-
- {{ attribute.description|convert_markdown(heading_level, html_id) }}
-
-
- {% if attribute.annotation %}
-
- TYPE:
- {% with expression = attribute.annotation %}
- |
-
{{ section.title or lang.t("Attributes:") }}
+| {{ lang.t("Name") }} | +{{ lang.t("Type") }} | +{{ lang.t("Description") }} | +
|---|---|---|
|
+
+ {% if attribute.annotation %}
+ {% with expression = attribute.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ attribute.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Attributes:") }}
+{{ attribute.name }}
+ {% if attribute.annotation %}
+ {% with expression = attribute.annotation %}
+ ({% include "expression"|get_template with context %})
+ {% endwith %}
+ {% endif %}
+ –
+ | {{ (section.title or lang.t("ATTRIBUTE")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
|
+
+
+ {{ attribute.description|convert_markdown(heading_level, html_id) }}
+
+
+ {% if attribute.annotation %}
+
+ TYPE:
+ {% with expression = attribute.annotation %}
+ |
+
{{ section.title or lang.t("Classes:") }}
-| {{ lang.t("Name") }} | -{{ lang.t("Description") }} | -
|---|---|
{{ class.name }} |
-
-
- {{ class.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Classes:") }}
-{{ class.name }}
- –
- | {{ (section.title or lang.t("CLASS")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
{{ class.name }} |
-
-
- {{ class.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Classes:") }}
+| {{ lang.t("Name") }} | +{{ lang.t("Description") }} | +
|---|---|
|
+
+
+ {{ class.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Classes:") }}
+{{ class.name }}
+ –
+ | {{ (section.title or lang.t("CLASS")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
|
+
+
+ {{ class.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Examples:") }}
-{% for section_type, sub_section in section.value %} - {% if section_type.value == "text" %} - {{ sub_section|convert_markdown(heading_level, html_id) }} - {% elif section_type.value == "examples" %} - {{ sub_section|highlight(language="pycon", linenums=False) }} - {% endif %} -{% endfor %} +{% block logs scoped %} + {{ super() }} + {# TODO: Switch to a warning after some time. #} + {{ log.info( + "DeprecationWarning: Extending '_base/docstring/examples.html' is deprecated, extend '_base/docstring/examples.html.jinja' instead. " ~ + "After some time, this message will be logged as a warning, causing strict builds to fail.", + once=True, + ) }} +{% endblock logs %} diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/examples.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/examples.html.jinja new file mode 100644 index 00000000..39a90fa0 --- /dev/null +++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/examples.html.jinja @@ -0,0 +1,28 @@ +{#- Template for "Examples" sections in docstrings. + +This template renders a list of documented examples. +It alternates between rendering text and code examples. + +Context: + section (griffe.DocstringSectionAttributes): The section to render. +-#} + +{% block logs scoped %} + {#- Logging block. + + This block can be used to log debug messages, deprecation messages, warnings, etc. + -#} + {{ log.debug("Rendering examples section") }} +{% endblock logs %} + +{% import "language"|get_template as lang with context %} +{#- Language module providing the `t` translation method. -#} + +{{ section.title or lang.t("Examples:") }}
+{% for section_type, sub_section in section.value %} + {% if section_type.value == "text" %} + {{ sub_section|convert_markdown(heading_level, html_id) }} + {% elif section_type.value == "examples" %} + {{ sub_section|highlight(language="pycon", linenums=False) }} + {% endif %} +{% endfor %} diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/functions.html b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/functions.html index ab1939f5..a61c48fb 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/functions.html +++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/functions.html @@ -1,73 +1,11 @@ -{{ log.debug("Rendering functions section") }} +{% extends "_base/docstring/functions.html.jinja" %} -{% import "language.html" as lang with context %} - -{% if config.docstring_section_style == "table" %} - {% block table_style scoped %} -{{ section.title or lang.t("Methods:") if obj.is_class else lang.t("Functions:") }}
-| {{ lang.t("Name") }} | -{{ lang.t("Description") }} | -
|---|---|
{{ function.name }} |
-
-
- {{ function.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Methods:") if obj.is_class else lang.t("Functions:") }}
-{{ function.name }}
- –
- | {{ (section.title or lang.t("METHOD") if obj.is_class else lang.t("FUNCTION")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
{{ function.name }} |
-
-
- {{ function.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Methods:") if obj.is_class else lang.t("Functions:") }}
+| {{ lang.t("Name") }} | +{{ lang.t("Description") }} | +
|---|---|
|
+
+
+ {{ function.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Methods:") if obj.is_class else lang.t("Functions:") }}
+{{ function.name }}
+ –
+ | {{ (section.title or lang.t("METHOD") if obj.is_class else lang.t("FUNCTION")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
|
+
+
+ {{ function.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Modules:") }}
-| {{ lang.t("Name") }} | -{{ lang.t("Description") }} | -
|---|---|
{{ module.name }} |
-
-
- {{ module.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Modules:") }}
-{{ module.name }}
- –
- | {{ (section.title or lang.t("MODULE")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
{{ module.name }} |
-
-
- {{ module.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Modules:") }}
+| {{ lang.t("Name") }} | +{{ lang.t("Description") }} | +
|---|---|
|
+
+
+ {{ module.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Modules:") }}
+{{ module.name }}
+ –
+ | {{ (section.title or lang.t("MODULE")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
|
+
+
+ {{ module.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Other Parameters:") }}
-| {{ lang.t("Name") }} | -{{ lang.t("Type") }} | -{{ lang.t("Description") }} | -
|---|---|---|
{{ parameter.name }} |
-
- {% if parameter.annotation %}
- {% with expression = parameter.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ parameter.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Other Parameters:") }}
-{{ parameter.name }}
- {% if parameter.annotation %}
- {% with expression = parameter.annotation %}
- ({% include "expression.html" with context %})
- {% endwith %}
- {% endif %}
- –
- | {{ (section.title or lang.t("PARAMETER")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
{{ parameter.name }} |
-
-
- {{ parameter.description|convert_markdown(heading_level, html_id) }}
-
-
- {% if parameter.annotation %}
-
- {{ lang.t("TYPE:") }}
- {% with expression = parameter.annotation %}
- |
-
{{ section.title or lang.t("Other Parameters:") }}
+| {{ lang.t("Name") }} | +{{ lang.t("Type") }} | +{{ lang.t("Description") }} | +
|---|---|---|
{{ parameter.name }} |
+
+ {% if parameter.annotation %}
+ {% with expression = parameter.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ parameter.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Other Parameters:") }}
+{{ parameter.name }}
+ {% if parameter.annotation %}
+ {% with expression = parameter.annotation %}
+ ({% include "expression"|get_template with context %})
+ {% endwith %}
+ {% endif %}
+ –
+ | {{ (section.title or lang.t("PARAMETER")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
{{ parameter.name }} |
+
+
+ {{ parameter.description|convert_markdown(heading_level, html_id) }}
+
+
+ {% if parameter.annotation %}
+
+ {{ lang.t("TYPE:") }}
+ {% with expression = parameter.annotation %}
+ |
+
{{ section.title or lang.t("Parameters:") }}
-| {{ lang.t("Name") }} | -{{ lang.t("Type") }} | -{{ lang.t("Description") }} | -{{ lang.t("Default") }} | -
|---|---|---|---|
{{ parameter.name }} |
-
- {% if parameter.annotation %}
- {% with expression = parameter.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ parameter.description|convert_markdown(heading_level, html_id) }}
-
- |
-
- {% if parameter.default %}
- {% with expression = parameter.default %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% else %}
- {{ lang.t("required") }}
- {% endif %}
- |
-
{{ section.title or lang.t("Parameters:") }}
-{{ parameter.name }}
- {% if parameter.annotation %}
- {% with expression = parameter.annotation %}
- ({% include "expression.html" with context %}
- {%- if parameter.default %}, {{ lang.t("default:") }}
- {% with expression = parameter.default %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %})
- {% endwith %}
- {% endif %}
- –
- | {{ (section.title or lang.t("PARAMETER")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
{{ parameter.name }} |
-
-
- {{ parameter.description|convert_markdown(heading_level, html_id) }}
-
-
- {% if parameter.annotation %}
-
- {{ lang.t("TYPE:") }}
- {% with expression = parameter.annotation %}
- |
-
{{ section.title or lang.t("Parameters:") }}
+| {{ lang.t("Name") }} | +{{ lang.t("Type") }} | +{{ lang.t("Description") }} | +{{ lang.t("Default") }} | +
|---|---|---|---|
{{ parameter.name }} |
+
+ {% if parameter.annotation %}
+ {% with expression = parameter.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ parameter.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
+ {% if parameter.default %}
+ {% with expression = parameter.default %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% else %}
+ {{ lang.t("required") }}
+ {% endif %}
+ |
+
{{ section.title or lang.t("Parameters:") }}
+{{ parameter.name }}
+ {% if parameter.annotation %}
+ {% with expression = parameter.annotation %}
+ ({% include "expression"|get_template with context %}
+ {%- if parameter.default %}, {{ lang.t("default:") }}
+ {% with expression = parameter.default %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %})
+ {% endwith %}
+ {% endif %}
+ –
+ | {{ (section.title or lang.t("PARAMETER")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
{{ parameter.name }} |
+
+
+ {{ parameter.description|convert_markdown(heading_level, html_id) }}
+
+
+ {% if parameter.annotation %}
+
+ {{ lang.t("TYPE:") }}
+ {% with expression = parameter.annotation %}
+ |
+
{{ section.title or lang.t("Raises:") }}
-| {{ lang.t("Type") }} | -{{ lang.t("Description") }} | -
|---|---|
- {% if raises.annotation %}
- {% with expression = raises.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ raises.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ lang.t(section.title) or lang.t("Raises:") }}
-{% include "expression.html" with context %}
- {% endwith %}
- –
- {% endif %}
- | {{ (section.title or lang.t("RAISES")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
-
- {% with expression = raises.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
-
- |
-
-
- {{ raises.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Raises:") }}
+| {{ lang.t("Type") }} | +{{ lang.t("Description") }} | +
|---|---|
+ {% if raises.annotation %}
+ {% with expression = raises.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ raises.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ lang.t(section.title) or lang.t("Raises:") }}
+{% include "expression"|get_template with context %}
+ {% endwith %}
+ –
+ {% endif %}
+ | {{ (section.title or lang.t("RAISES")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
+
+ {% with expression = raises.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+
+ |
+
+
+ {{ raises.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Receives:") }}
-| {{ lang.t("Name") }} | {% endif %} -{{ lang.t("Type") }} | -{{ lang.t("Description") }} | -
|---|---|---|
{% if receives.name %}{{ receives.name }}{% endif %} | {% endif %}
-
- {% if receives.annotation %}
- {% with expression = receives.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ receives.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Receives:") }}
-{{ receives.name }}{% endif %}
- {% if receives.annotation %}
- {% with expression = receives.annotation %}
- {% if receives.name %} ({% endif %}
- {% include "expression.html" with context %}
- {% if receives.name %}){% endif %}
- {% endwith %}
- {% endif %}
- –
- | {{ (section.title or lang.t("RECEIVES")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
- {% if receives.name %}
- {{ receives.name }}
- {% elif receives.annotation %}
-
- {% with expression = receives.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
-
- {% endif %}
- |
-
-
- {{ receives.description|convert_markdown(heading_level, html_id) }}
-
- {% if receives.name and receives.annotation %}
-
-
- {{ lang.t("TYPE:") }}
- {% with expression = receives.annotation %}
- |
-
{{ section.title or lang.t("Receives:") }}
+| {{ lang.t("Name") }} | {% endif %} +{{ lang.t("Type") }} | +{{ lang.t("Description") }} | +
|---|---|---|
{% if receives.name %}{{ receives.name }}{% endif %} | {% endif %}
+
+ {% if receives.annotation %}
+ {% with expression = receives.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ receives.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Receives:") }}
+{{ receives.name }}{% endif %}
+ {% if receives.annotation %}
+ {% with expression = receives.annotation %}
+ {% if receives.name %} ({% endif %}
+ {% include "expression"|get_template with context %}
+ {% if receives.name %}){% endif %}
+ {% endwith %}
+ {% endif %}
+ –
+ | {{ (section.title or lang.t("RECEIVES")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
+ {% if receives.name %}
+ {{ receives.name }}
+ {% elif receives.annotation %}
+
+ {% with expression = receives.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+
+ {% endif %}
+ |
+
+
+ {{ receives.description|convert_markdown(heading_level, html_id) }}
+
+ {% if receives.name and receives.annotation %}
+
+
+ {{ lang.t("TYPE:") }}
+ {% with expression = receives.annotation %}
+ |
+
{{ section.title or lang.t("Returns:") }}
-| {{ lang.t("Name") }} | {% endif %} -{{ lang.t("Type") }} | -{{ lang.t("Description") }} | -
|---|---|---|
{% if returns.name %}{{ returns.name }}{% endif %} | {% endif %}
-
- {% if returns.annotation %}
- {% with expression = returns.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ returns.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Returns:") }}
-{{ returns.name }}{% endif %}
- {% if returns.annotation %}
- {% with expression = returns.annotation %}
- {% if returns.name %} ({% endif %}
- {% include "expression.html" with context %}
- {% if returns.name %}){% endif %}
- {% endwith %}
- {% endif %}
- –
- | {{ (section.title or lang.t("RETURNS")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION").upper() }} | -
|---|---|
- {% if returns.name %}
- {{ returns.name }}
- {% elif returns.annotation %}
-
- {% with expression = returns.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
-
- {% endif %}
- |
-
-
- {{ returns.description|convert_markdown(heading_level, html_id) }}
-
- {% if returns.name and returns.annotation %}
-
-
- {{ lang.t("TYPE:") }}
- {% with expression = returns.annotation %}
- |
-
{{ section.title or lang.t("Returns:") }}
+| {{ lang.t("Name") }} | {% endif %} +{{ lang.t("Type") }} | +{{ lang.t("Description") }} | +
|---|---|---|
{% if returns.name %}{{ returns.name }}{% endif %} | {% endif %}
+
+ {% if returns.annotation %}
+ {% with expression = returns.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ returns.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Returns:") }}
+{{ returns.name }}{% endif %}
+ {% if returns.annotation %}
+ {% with expression = returns.annotation %}
+ {% if returns.name %} ({% endif %}
+ {% include "expression"|get_template with context %}
+ {% if returns.name %}){% endif %}
+ {% endwith %}
+ {% endif %}
+ –
+ | {{ (section.title or lang.t("RETURNS")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION").upper() }} | +
|---|---|
+ {% if returns.name %}
+ {{ returns.name }}
+ {% elif returns.annotation %}
+
+ {% with expression = returns.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+
+ {% endif %}
+ |
+
+
+ {{ returns.description|convert_markdown(heading_level, html_id) }}
+
+ {% if returns.name and returns.annotation %}
+
+
+ {{ lang.t("TYPE:") }}
+ {% with expression = returns.annotation %}
+ |
+
{{ section.title or lang.t("Warns:") }}
-| {{ lang.t("Type") }} | -{{ lang.t("Description") }} | -
|---|---|
- {% if warns.annotation %}
- {% with expression = warns.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ warns.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Warns:") }}
-{% include "expression.html" with context %}
- {% endwith %}
- –
- {% endif %}
- | {{ (section.title or lang.t("WARNS")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
-
- {% with expression = warns.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
-
- |
-
-
- {{ warns.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Warns:") }}
+| {{ lang.t("Type") }} | +{{ lang.t("Description") }} | +
|---|---|
+ {% if warns.annotation %}
+ {% with expression = warns.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ warns.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Warns:") }}
+{% include "expression"|get_template with context %}
+ {% endwith %}
+ –
+ {% endif %}
+ | {{ (section.title or lang.t("WARNS")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
+
+ {% with expression = warns.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+
+ |
+
+
+ {{ warns.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Yields:") }}
-| {{ lang.t("Name") }} | {% endif %} -{{ lang.t("Type") }} | -{{ lang.t("Description") }} | -
|---|---|---|
{% if yields.name %}{{ yields.name }}{% endif %} | {% endif %}
-
- {% if yields.annotation %}
- {% with expression = yields.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
- {% endif %}
- |
-
-
- {{ yields.description|convert_markdown(heading_level, html_id) }}
-
- |
-
{{ section.title or lang.t("Yields:") }}
-{{ yields.name }}{% endif %}
- {% if yields.annotation %}
- {% with expression = yields.annotation %}
- {% if yields.name %} ({% endif %}
- {% include "expression.html" with context %}
- {% if yields.name %}){% endif %}
- {% endwith %}
- {% endif %}
- –
- | {{ (section.title or lang.t("YIELDS")).rstrip(":").upper() }} | -{{ lang.t("DESCRIPTION") }} | -
|---|---|
- {% if yields.name %}
- {{ yields.name }}
- {% elif yields.annotation %}
-
- {% with expression = yields.annotation %}
- {% include "expression.html" with context %}
- {% endwith %}
-
- {% endif %}
- |
-
-
- {{ yields.description|convert_markdown(heading_level, html_id) }}
-
- {% if yields.name and yields.annotation %}
-
-
- {{ lang.t("TYPE:") }}:
- {% with expression = yields.annotation %}
- |
-
{{ section.title or lang.t("Yields:") }}
+| {{ lang.t("Name") }} | {% endif %} +{{ lang.t("Type") }} | +{{ lang.t("Description") }} | +
|---|---|---|
{% if yields.name %}{{ yields.name }}{% endif %} | {% endif %}
+
+ {% if yields.annotation %}
+ {% with expression = yields.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+ {% endif %}
+ |
+
+
+ {{ yields.description|convert_markdown(heading_level, html_id) }}
+
+ |
+
{{ section.title or lang.t("Yields:") }}
+{{ yields.name }}{% endif %}
+ {% if yields.annotation %}
+ {% with expression = yields.annotation %}
+ {% if yields.name %} ({% endif %}
+ {% include "expression"|get_template with context %}
+ {% if yields.name %}){% endif %}
+ {% endwith %}
+ {% endif %}
+ –
+ | {{ (section.title or lang.t("YIELDS")).rstrip(":").upper() }} | +{{ lang.t("DESCRIPTION") }} | +
|---|---|
+ {% if yields.name %}
+ {{ yields.name }}
+ {% elif yields.annotation %}
+
+ {% with expression = yields.annotation %}
+ {% include "expression"|get_template with context %}
+ {% endwith %}
+
+ {% endif %}
+ |
+
+
+ {{ yields.description|convert_markdown(heading_level, html_id) }}
+
+ {% if yields.name and yields.annotation %}
+
+
+ {{ lang.t("TYPE:") }}:
+ {% with expression = yields.annotation %}
+ |
+
')|safe if config.show_symbol_type_toc else '') + function.name,
- ) %}
-
- {% block heading scoped %}
- {% if config.show_symbol_type_heading %}{% endif %}
- {% if config.separate_signature %}
- {{ function_name }}
- {% else %}
- {%+ filter highlight(language="python", inline=True) %}
- {{ function_name }}{% include "signature.html" with context %}
- {% endfilter %}
- {% endif %}
- {% endblock heading %}
-
- {% block labels scoped %}
- {% with labels = function.labels %}
- {% include "labels.html" with context %}
- {% endwith %}
- {% endblock labels %}
-
- {% endfilter %}
-
- {% block signature scoped %}
- {% if config.separate_signature %}
- {% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
- {{ function.name }}
- {% endfilter %}
- {% endif %}
- {% endblock signature %}
-
- {% else %}
-
- {% if config.show_root_toc_entry %}
- {% filter heading(
- heading_level,
- role="function",
- id=html_id,
- toc_label=((' ')|safe if config.show_symbol_type_toc else '') + function.name,
- hidden=True,
- ) %}
- {% endfilter %}
- {% endif %}
- {% set heading_level = heading_level - 1 %}
- {% endif %}
-
-
- {%- if function.relative_filepath.is_absolute() -%}
- {{ function.relative_package_filepath }}
- {%- else -%}
- {{ function.relative_filepath }}
- {%- endif -%}
- ')|safe if config.show_symbol_type_toc else '') + function.name,
+ ) %}
+
+ {% block heading scoped %}
+ {#- Heading block.
+
+ This block renders the heading for the function.
+ -#}
+ {% if config.show_symbol_type_heading %}{% endif %}
+ {% if config.separate_signature %}
+ {{ function_name }}
+ {% else %}
+ {%+ filter highlight(language="python", inline=True) %}
+ {{ function_name }}{% include "signature"|get_template with context %}
+ {% endfilter %}
+ {% endif %}
+ {% endblock heading %}
+
+ {% block labels scoped %}
+ {#- Labels block.
+
+ This block renders the labels for the function.
+ -#}
+ {% with labels = function.labels %}
+ {% include "labels"|get_template with context %}
+ {% endwith %}
+ {% endblock labels %}
+
+ {% endfilter %}
+
+ {% block signature scoped %}
+ {#- Signature block.
+
+ This block renders the signature for the function.
+ -#}
+ {% if config.separate_signature %}
+ {% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %}
+ {{ function.name }}
+ {% endfilter %}
+ {% endif %}
+ {% endblock signature %}
+
+ {% else %}
+
+ {% if config.show_root_toc_entry %}
+ {% filter heading(
+ heading_level,
+ role="function",
+ id=html_id,
+ toc_label=((' ')|safe if config.show_symbol_type_toc else '') + function.name,
+ hidden=True,
+ ) %}
+ {% endfilter %}
+ {% endif %}
+ {% set heading_level = heading_level - 1 %}
+ {% endif %}
+
+
+ {%- if function.relative_filepath.is_absolute() -%}
+ {{ function.relative_package_filepath }}
+ {%- else -%}
+ {{ function.relative_filepath }}
+ {%- endif -%}
+ {{ label }}
- {% endfor %}
-
-{% endif %}
+{% extends "_base/labels.html.jinja" %}
+
+{% block logs scoped %}
+ {{ super() }}
+ {# TODO: Switch to a warning after some time. #}
+ {{ log.info(
+ "DeprecationWarning: Extending '_base/labels.html' is deprecated, extend '_base/labels.html.jinja' instead. " ~
+ "After some time, this message will be logged as a warning, causing strict builds to fail.",
+ once=True,
+ ) }}
+{% endblock logs %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/labels.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/labels.html.jinja
new file mode 100644
index 00000000..dced4913
--- /dev/null
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/labels.html.jinja
@@ -0,0 +1,25 @@
+{#- Template for object labels.
+
+Labels are additional information that can be displayed alongside an object.
+Example labels include "property", "writable" or "cached" for properties,
+"classmethod" or "staticmethod" for methods, etc.
+
+Context:
+ labels (list): The list of labels to render.
+ config (dict): The configuration options.
+-#}
+
+{% if config.show_labels and labels %}
+ {% block logs scoped %}
+ {#- Logging block.
+
+ This block can be used to log debug messages, deprecation messages, warnings, etc.
+ -#}
+ {{ log.debug("Rendering labels") }}
+ {% endblock logs %}
+
+ {% for label in labels|sort %}
+ {{ label }}
+ {% endfor %}
+
+{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/language.html b/src/mkdocstrings_handlers/python/templates/material/_base/language.html
new file mode 100644
index 00000000..c97d0c31
--- /dev/null
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/language.html
@@ -0,0 +1,11 @@
+{% extends "_base/language.html.jinja" %}
+
+{% block logs scoped %}
+ {{ super() }}
+ {# TODO: Switch to a warning after some time. #}
+ {{ log.info(
+ "DeprecationWarning: Extending '_base/language.html' is deprecated, extend '_base/language.html.jinja' instead. " ~
+ "After some time, this message will be logged as a warning, causing strict builds to fail.",
+ once=True,
+ ) }}
+{% endblock logs %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/language.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/language.html.jinja
new file mode 100644
index 00000000..5b643726
--- /dev/null
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/language.html.jinja
@@ -0,0 +1,18 @@
+{#- Import translation macros for the given language and fallback language. -#}
+
+{% block logs scoped %}
+ {#- Logging block.
+
+ This block can be used to log debug messages, deprecation messages, warnings, etc.
+ -#}
+{% endblock logs %}
+
+{% set lang_pth = "languages/" ~ locale | get_template %}
+{% if lang_pth is existing_template %}
+ {% import lang_pth as lang %}
+ {% import "languages/en"|get_template as fallback %}
+ {% macro t(key) %}{{ lang.t(key) or fallback.t(key) }}{% endmacro %}
+{% else %}
+ {% import "languages/en"|get_template as lang %}
+ {% macro t(key) %}{{ lang.t(key) }}{% endmacro %}
+{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/languages/en.html b/src/mkdocstrings_handlers/python/templates/material/_base/languages/en.html
index 1f76e059..eab87415 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/languages/en.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/languages/en.html
@@ -1,37 +1,11 @@
-
-{% macro t(key) %}{{ {
- "ATTRIBUTE": "ATTRIBUTE",
- "Attributes:": "Attributes:",
- "Classes:": "Classes:",
- "CLASS": "CLASS",
- "DEFAULT:": "DEFAULT:",
- "Default": "Default",
- "default:": "default:",
- "DESCRIPTION": "DESCRIPTION",
- "Description": "Description",
- "Examples:": "Examples:",
- "Functions:": "Functions:",
- "FUNCTION": "FUNCTION",
- "Methods:": "Methods:",
- "METHOD": "METHOD",
- "Modules:": "Modules:",
- "MODULE": "MODULE",
- "Name": "Name",
- "Other Parameters:": "Other Parameters:",
- "PARAMETER": "PARAMETER",
- "Parameters:": "Parameters:",
- "RAISES": "RAISES",
- "Raises:" : "Raises:",
- "RECEIVES": "RECEIVES",
- "Receives:": "Receives:",
- "required": "required",
- "RETURNS": "RETURNS",
- "Returns:": "Returns:",
- "Source code in": "Source code in",
- "TYPE:": "TYPE:",
- "Type": "Type",
- "WARNS": "WARNS",
- "Warns:": "Warns:",
- "YIELDS": "YIELDS",
- "Yields:": "Yields:",
-}[key] }}{% endmacro %}
\ No newline at end of file
+{% extends "_base/languages/en.html.jinja" %}
+
+{% block logs scoped %}
+ {{ super() }}
+ {# TODO: Switch to a warning after some time. #}
+ {{ log.info(
+ "DeprecationWarning: Extending '_base/languages/en.html' is deprecated, extend '_base/languages/en.html.jinja' instead. " ~
+ "After some time, this message will be logged as a warning, causing strict builds to fail.",
+ once=True,
+ ) }}
+{% endblock logs %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/languages/en.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/languages/en.html.jinja
new file mode 100644
index 00000000..d988b6ab
--- /dev/null
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/languages/en.html.jinja
@@ -0,0 +1,45 @@
+{#- Macro for English translations. -#}
+
+{% block logs scoped %}
+ {#- Logging block.
+
+ This block can be used to log debug messages, deprecation messages, warnings, etc.
+ -#}
+{% endblock logs %}
+
+{% macro t(key) %}{{ {
+ "ATTRIBUTE": "ATTRIBUTE",
+ "Attributes:": "Attributes:",
+ "Classes:": "Classes:",
+ "CLASS": "CLASS",
+ "DEFAULT:": "DEFAULT:",
+ "Default": "Default",
+ "default:": "default:",
+ "DESCRIPTION": "DESCRIPTION",
+ "Description": "Description",
+ "Examples:": "Examples:",
+ "Functions:": "Functions:",
+ "FUNCTION": "FUNCTION",
+ "Methods:": "Methods:",
+ "METHOD": "METHOD",
+ "Modules:": "Modules:",
+ "MODULE": "MODULE",
+ "Name": "Name",
+ "Other Parameters:": "Other Parameters:",
+ "PARAMETER": "PARAMETER",
+ "Parameters:": "Parameters:",
+ "RAISES": "RAISES",
+ "Raises:" : "Raises:",
+ "RECEIVES": "RECEIVES",
+ "Receives:": "Receives:",
+ "required": "required",
+ "RETURNS": "RETURNS",
+ "Returns:": "Returns:",
+ "Source code in": "Source code in",
+ "TYPE:": "TYPE:",
+ "Type": "Type",
+ "WARNS": "WARNS",
+ "Warns:": "Warns:",
+ "YIELDS": "YIELDS",
+ "Yields:": "Yields:",
+}[key] }}{% endmacro %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/languages/ja.html b/src/mkdocstrings_handlers/python/templates/material/_base/languages/ja.html
index 456e1170..14319499 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/languages/ja.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/languages/ja.html
@@ -1,37 +1,11 @@
-
-{% macro t(key) %}{{ {
- "ATTRIBUTE": "属性",
- "Attributes:": "属性:",
- "Classes:": "",
- "CLASS": "",
- "DEFAULT:": "デフォルト:",
- "Default": "デフォルト",
- "default:": "デフォルト:",
- "DESCRIPTION": "デスクリプション",
- "Description": "デスクリプション",
- "Examples:": "例:",
- "Functions:": "",
- "FUNCTION": "",
- "Methods:": "",
- "METHOD": "",
- "Modules:": "",
- "MODULE": "",
- "Name": "名前",
- "Other Parameters:": "他の引数:",
- "PARAMETER": "引数",
- "Parameters:": "引数:",
- "RAISES": "発生",
- "Raises:" : "発生:",
- "RECEIVES": "取得",
- "Receives:": "取得:",
- "required": "必須",
- "RETURNS": "戻り値",
- "Returns:": "戻り値:",
- "Source code in": "ソースコード位置:",
- "TYPE:": "タイプ:",
- "Type": "タイプ",
- "WARNS": "警告",
- "Warns:": "警告:",
- "YIELDS": "返す",
- "Yields:": "返す:",
-}[key] }}{% endmacro %}
\ No newline at end of file
+{% extends "_base/languages/ja.html.jinja" %}
+
+{% block logs scoped %}
+ {{ super() }}
+ {# TODO: Switch to a warning after some time. #}
+ {{ log.info(
+ "DeprecationWarning: Extending '_base/languages/ja.html' is deprecated, extend '_base/languages/ja.html.jinja' instead. " ~
+ "After some time, this message will be logged as a warning, causing strict builds to fail.",
+ once=True,
+ ) }}
+{% endblock logs %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/languages/ja.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/languages/ja.html.jinja
new file mode 100644
index 00000000..a6b7728b
--- /dev/null
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/languages/ja.html.jinja
@@ -0,0 +1,45 @@
+{#- Macro for Japanese translations. -#}
+
+{% block logs scoped %}
+ {#- Logging block.
+
+ This block can be used to log debug messages, deprecation messages, warnings, etc.
+ -#}
+{% endblock logs %}
+
+{% macro t(key) %}{{ {
+ "ATTRIBUTE": "属性",
+ "Attributes:": "属性:",
+ "Classes:": "",
+ "CLASS": "",
+ "DEFAULT:": "デフォルト:",
+ "Default": "デフォルト",
+ "default:": "デフォルト:",
+ "DESCRIPTION": "デスクリプション",
+ "Description": "デスクリプション",
+ "Examples:": "例:",
+ "Functions:": "",
+ "FUNCTION": "",
+ "Methods:": "",
+ "METHOD": "",
+ "Modules:": "",
+ "MODULE": "",
+ "Name": "名前",
+ "Other Parameters:": "他の引数:",
+ "PARAMETER": "引数",
+ "Parameters:": "引数:",
+ "RAISES": "発生",
+ "Raises:" : "発生:",
+ "RECEIVES": "取得",
+ "Receives:": "取得:",
+ "required": "必須",
+ "RETURNS": "戻り値",
+ "Returns:": "戻り値:",
+ "Source code in": "ソースコード位置:",
+ "TYPE:": "タイプ:",
+ "Type": "タイプ",
+ "WARNS": "警告",
+ "Warns:": "警告:",
+ "YIELDS": "返す",
+ "Yields:": "返す:",
+}[key] }}{% endmacro %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/languages/zh.html b/src/mkdocstrings_handlers/python/templates/material/_base/languages/zh.html
index 9c018f27..0b281195 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/languages/zh.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/languages/zh.html
@@ -1,37 +1,11 @@
-
-{% macro t(key) %}{{ {
- "ATTRIBUTE": "属性",
- "Attributes:": "属性:",
- "Classes:": "",
- "CLASS": "",
- "DEFAULT:": "默认:",
- "Default": "默认",
- "default:": "默认:",
- "DESCRIPTION": "描述",
- "Description": "描述",
- "Examples:": "示例:",
- "Functions:": "",
- "FUNCTION": "",
- "Methods:": "",
- "METHOD": "",
- "Modules:": "",
- "MODULE": "",
- "Name": "名称",
- "Other Parameters:": "其他参数:",
- "PARAMETER": "参数",
- "Parameters:": "参数:",
- "RAISES": "引发",
- "Raises:" : "引发:",
- "Receives:": "接收:",
- "RECEIVES": "接收",
- "required": "必需",
- "RETURNS": "返回",
- "Returns:": "返回:",
- "Source code in": "源代码位于:",
- "TYPE:": "类型:",
- "Type": "类型",
- "Warns:": "警告:",
- "WARNS": "警告",
- "YIELDS": "产生",
- "Yields:": "产生:",
-}[key] }}{% endmacro %}
\ No newline at end of file
+{% extends "_base/languages/zh.html.jinja" %}
+
+{% block logs scoped %}
+ {{ super() }}
+ {# TODO: Switch to a warning after some time. #}
+ {{ log.info(
+ "DeprecationWarning: Extending '_base/languages/zh.html' is deprecated, extend '_base/languages/zh.html.jinja' instead. " ~
+ "After some time, this message will be logged as a warning, causing strict builds to fail.",
+ once=True,
+ ) }}
+{% endblock logs %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/languages/zh.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/languages/zh.html.jinja
new file mode 100644
index 00000000..f748b83c
--- /dev/null
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/languages/zh.html.jinja
@@ -0,0 +1,45 @@
+{#- Macro for Chinese translations. -#}
+
+{% block logs scoped %}
+ {#- Logging block.
+
+ This block can be used to log debug messages, deprecation messages, warnings, etc.
+ -#}
+{% endblock logs %}
+
+{% macro t(key) %}{{ {
+ "ATTRIBUTE": "属性",
+ "Attributes:": "属性:",
+ "Classes:": "",
+ "CLASS": "",
+ "DEFAULT:": "默认:",
+ "Default": "默认",
+ "default:": "默认:",
+ "DESCRIPTION": "描述",
+ "Description": "描述",
+ "Examples:": "示例:",
+ "Functions:": "",
+ "FUNCTION": "",
+ "Methods:": "",
+ "METHOD": "",
+ "Modules:": "",
+ "MODULE": "",
+ "Name": "名称",
+ "Other Parameters:": "其他参数:",
+ "PARAMETER": "参数",
+ "Parameters:": "参数:",
+ "RAISES": "引发",
+ "Raises:" : "引发:",
+ "Receives:": "接收:",
+ "RECEIVES": "接收",
+ "required": "必需",
+ "RETURNS": "返回",
+ "Returns:": "返回:",
+ "Source code in": "源代码位于:",
+ "TYPE:": "类型:",
+ "Type": "类型",
+ "Warns:": "警告:",
+ "WARNS": "警告",
+ "YIELDS": "产生",
+ "Yields:": "产生:",
+}[key] }}{% endmacro %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/module.html b/src/mkdocstrings_handlers/python/templates/material/_base/module.html
index 7d45e321..918ab6d0 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/module.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/module.html
@@ -1,74 +1,11 @@
-{{ log.debug("Rendering " + module.path) }}
-
- '|safe if config.show_symbol_type_toc else '') + module.name,
- ) %}
-
- {% block heading scoped %}
- {% if config.show_symbol_type_heading %}{% endif %}
- {% if config.separate_signature %}
- {{ module_name }}
- {% else %}
- {{ module_name }}
- {% endif %}
- {% endblock heading %}
-
- {% block labels scoped %}
- {% with labels = module.labels %}
- {% include "labels.html" with context %}
- {% endwith %}
- {% endblock labels %}
-
- {% endfilter %}
-
- {% else %}
- {% if config.show_root_toc_entry %}
- {% filter heading(heading_level,
- role="module",
- id=html_id,
- toc_label=(' '|safe if config.show_symbol_type_toc else '') + module.name,
- hidden=True,
- ) %}
- {% endfilter %}
- {% endif %}
- {% set heading_level = heading_level - 1 %}
- {% endif %}
-
- '|safe if config.show_symbol_type_toc else '') + module.name,
+ ) %}
+
+ {% block heading scoped %}
+ {#- Heading block.
+
+ This block renders the heading for the module.
+ -#}
+ {% if config.show_symbol_type_heading %}{% endif %}
+ {% if config.separate_signature %}
+ {{ module_name }}
+ {% else %}
+ {{ module_name }}
+ {% endif %}
+ {% endblock heading %}
+
+ {% block labels scoped %}
+ {#- Labels block.
+
+ This block renders the labels for the module.
+ -#}
+ {% with labels = module.labels %}
+ {% include "labels"|get_template with context %}
+ {% endwith %}
+ {% endblock labels %}
+
+ {% endfilter %}
+
+ {% else %}
+ {% if config.show_root_toc_entry %}
+ {% filter heading(heading_level,
+ role="module",
+ id=html_id,
+ toc_label=(' '|safe if config.show_symbol_type_toc else '') + module.name,
+ hidden=True,
+ ) %}
+ {% endfilter %}
+ {% endif %}
+ {% set heading_level = heading_level - 1 %}
+ {% endif %}
+
+ | {{ section.title or lang.t("Attributes:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Other parameters:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Parameters:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Raises:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Receives:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Returns:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Warns:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Yields:") }} | +
+
|
+
|---|
| {{ section.title or lang.t("Attributes:") }} | -
-
|
-
|---|
| {{ section.title or lang.t("Other parameters:") }} | -
-
|
-
|---|
| {{ section.title or lang.t("Parameters:") }} | -
-
|
-
|---|
| {{ section.title or lang.t("Raises:") }} | -
-
|
-
|---|
| {{ section.title or lang.t("Receives:") }} | -
-
|
-
|---|
| {{ section.title or lang.t("Returns:") }} | -
-
|
-
|---|
| {{ section.title or lang.t("Warns:") }} | -
-
|
-
|---|
| {{ section.title or lang.t("Yields:") }} | -
-
|
-
|---|
This is an example.
" in rendered @@ -145,3 +146,29 @@ def test_extension_paths(tmp_path: Path, expect_change: bool, extension: str | d raise ValueError("Normalization must not change extension items type") else: assert normalized == extension + + +def test_rendering_object_source_without_lineno(handler: PythonHandler) -> None: + """Test rendering objects without a line number.""" + code = dedent( + """ + '''Module docstring.''' + + class Class: + '''Class docstring.''' + + def function(self): + '''Function docstring.''' + + attribute = 0 + '''Attribute docstring.''' + """, + ) + with temporary_visited_module(code) as module: + # TODO: Remove once Griffe does that automatically. + module.lines_collection[module.filepath] = code.splitlines() # type: ignore[index] + + module["Class"].lineno = None + module["Class.function"].lineno = None + module["attribute"].lineno = None + assert handler.render(module, {"show_source": True}) diff --git a/tests/test_rendering.py b/tests/test_rendering.py index 38d81dbb..1bab29d7 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -7,8 +7,7 @@ from typing import TYPE_CHECKING, Any import pytest -from griffe.collections import ModulesCollection -from griffe.tests import temporary_visited_module +from griffe import ModulesCollection, temporary_visited_module from mkdocstrings_handlers.python import rendering