From 71485b0be8a5010c0a93e8bcdaf448880f2dcffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 28 Feb 2021 13:11:34 +0100 Subject: [PATCH 01/50] chore: Fix docs-deploy task --- duties.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/duties.py b/duties.py index be0f8d90..3ba18caa 100644 --- a/duties.py +++ b/duties.py @@ -253,7 +253,7 @@ def docs_deploy(ctx): Arguments: ctx: The context instance (passed automatically). """ - ctx.run("git remote set-url org-pages git@github.com:mkdocstrings/mkdocstrings.github.io", silent=True) + ctx.run("git remote add org-pages git@github.com:mkdocstrings/mkdocstrings.github.io", silent=True, nofail=True) ctx.run("mkdocs gh-deploy --remote-name org-pages", title="Deploying documentation") From 0d86150f80385e210ae5ed6cd31ccd61f492b0e7 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sun, 28 Feb 2021 14:50:13 +0100 Subject: [PATCH 02/50] docs: More details about changes in 0.15.0 PR 250: https://github.com/mkdocstrings/mkdocstrings/pull/250 --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b60b3fcc..c4416344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,11 @@ The following two items are *possible* breaking changes: - Cross-linking to arbitrary headings now requires to opt-in to the *autorefs* plugin, which is installed as a dependency of *mkdocstrings*. See [Cross-references to any Markdown heading](https://mkdocstrings.github.io/usage/#cross-references-to-any-markdown-heading). -- *mkdocstrings* now respects your code highlighting configured method, - so if you are using CodeHilite, the `highlight` CSS classes in the rendered HTML will be replaced by `codehilite`. - In that case make sure to replace `.highlight` by `.codehilite` in any extra CSS rule of yours. +- *mkdocstrings* now respects your configured code highlighting method, + so if you are using the CodeHilite extension, the `.highlight` CSS class in the rendered HTML will become `.codehilite`. + So make sure to adapt your extra CSS accordingly. Or just switch to using [pymdownx.highlight](https://facelessuser.github.io/pymdown-extensions/extensions/highlight/), it's better supported by *mkdocstrings* anyway. See [Syntax highlighting](https://mkdocstrings.github.io/theming/#syntax-highlighting). +- Most of the [CSS rules that *mkdocstrings* used to recommend](https://mkdocstrings.github.io/handlers/python/#recommended-style-material) for manual addition, now become mandatory (auto-injected into the site). This shouldn't *break* any of your styles, but you are welcome to remove the now-redundant lines that you had copied into `extra_css`, [similarly to this diff](https://github.com/mkdocstrings/mkdocstrings/pull/218/files#diff-7889a1924c66ff9318f1d81c4a3b75658d09bebf0db3b2e4023ba3e40294eb73). ### Features - Nicer-looking error outputs - no tracebacks from mkdocstrings ([6baf720](https://github.com/mkdocstrings/mkdocstrings/commit/6baf720850d359ddb55713553a757fe7b2283e10) by Oleh Prypin). [PR #230](https://github.com/mkdocstrings/mkdocstrings/pull/230) From e111f12014cfebd6becc7823fbf503babe318f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 28 Feb 2021 14:52:47 +0100 Subject: [PATCH 03/50] docs: Remove 'two' from breaking changes section --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4416344..9afdd62a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Breaking Changes -The following two items are *possible* breaking changes: +The following items are *possible* breaking changes: - Cross-linking to arbitrary headings now requires to opt-in to the *autorefs* plugin, which is installed as a dependency of *mkdocstrings*. From fb59df289723a3673a7f13990d1944c442a128cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 5 Mar 2021 17:24:38 +0100 Subject: [PATCH 04/50] docs: Use system preference for colors scheme --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 80dbc607..ff238b34 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,7 +38,7 @@ nav: theme: name: material palette: - scheme: slate + scheme: preference primary: teal accent: purple From bd8c2bc1918f237a2ae2d00d27ba074071c7e594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 23 Mar 2021 19:03:01 +0100 Subject: [PATCH 05/50] docs: Document Numpy-style support --- README.md | 65 ++++++++++++++++++----------------------- docs/handlers/python.md | 9 ++++-- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 67ea0f29..c23dd84c 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,9 @@ Automatic documentation from sources, for [MkDocs](https://mkdocs.org/). --- -![mkdocstrings_gif1](https://user-images.githubusercontent.com/3999221/77157604-fb807480-6aa1-11ea-99e0-d092371d4de0.gif) - ---- +**[Features](#features)** - **[Python handler](#python-handler)** - **[Requirements](#requirements)** - **[Installation](#installation)** - **[Quick usage](#quick-usage)** -- [Features](#features) - - [Python handler features](#python-handler-features) -- [Requirements](#requirements) -- [Installation](#installation) -- [Quick usage](#quick-usage) +![mkdocstrings_gif1](https://user-images.githubusercontent.com/3999221/77157604-fb807480-6aa1-11ea-99e0-d092371d4de0.gif) ## Features @@ -61,42 +55,41 @@ Automatic documentation from sources, for [MkDocs](https://mkdocs.org/). - **Reasonable defaults:** you should be able to just drop the plugin in your configuration and enjoy your auto-generated docs. -### Python handler features +### Python handler + +![mkdocstrings_gif2](https://user-images.githubusercontent.com/3999221/77157838-7184db80-6aa2-11ea-9f9a-fe77405202de.gif) - **Data collection from source code**: collection of the object-tree and the docstrings is done by - [`pytkdocs`](https://github.com/pawamoy/pytkdocs). The following features are possible thanks to it: - - **Support for type annotations:** `pytkdocs` collects your type annotations and *mkdocstrings* uses them - to display parameters types or return types. - - **Recursive documentation of Python objects:** just use the module dotted-path as identifier, and you get the full - module docs. You don't need to inject documentation for each class, function, etc. - - **Support for documented attribute:** attributes (variables) followed by a docstring (triple-quoted string) will - be recognized by `pytkdocs` in modules, classes and even in `__init__` methods. - - **Support for objects properties:** `pytkdocs` detects if a method is a `staticmethod`, a `classmethod`, etc., - it also detects if a property is read-only or writable, and more! These properties will be displayed - next to the object signature by *mkdocstrings*. - - **Google-style sections support in docstrings:** `pytkdocs` understands `Arguments:`, `Raises:` - and `Returns:` sections, and returns structured data for *mkdocstrings* to render them. - - **reStructuredText-style sections support in docstrings:** `pytkdocs` understands all the - [reStructuredText fields](https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html?highlight=python%20domain#info-field-lists), - and returns structured data for *mkdocstrings* to render them. - *Note: only RST **style** is supported, not the whole markup.* - - **Admonition support in docstrings:** blocks like `Note: ` or `Warning: ` will be transformed - to their [admonition](https://squidfunk.github.io/mkdocs-material/extensions/admonition/) equivalent. - *We do not support nested admonitions in docstrings!* - - **Support for reStructuredText in docstrings:** `pytkdocs` can parse simple RST. + [`pytkdocs`](https://github.com/pawamoy/pytkdocs). + +- **Support for type annotations:** `pytkdocs` collects your type annotations and *mkdocstrings* uses them + to display parameters types or return types. + +- **Recursive documentation of Python objects:** just use the module dotted-path as identifier, and you get the full + module docs. You don't need to inject documentation for each class, function, etc. + +- **Support for documented attributes:** attributes (variables) followed by a docstring (triple-quoted string) will + be recognized by `pytkdocs` in modules, classes and even in `__init__` methods. + +- **Support for objects properties:** `pytkdocs` detects if a method is a `staticmethod`, a `classmethod`, etc., + it also detects if a property is read-only or writable, and more! These properties will be displayed + next to the object signature by *mkdocstrings*. + +- **Multiple docstring-styles support:** almost complete support for Google-style, Numpy-style, + and reStructuredText-style docstrings. *Note: only RST **style** is supported, not the whole markup.* + +- **Admonition support in docstrings:** blocks like `Note:` or `Warning:` will be transformed + to their [admonition](https://squidfunk.github.io/mkdocs-material/extensions/admonition/) equivalent. + *We do not support nested admonitions in docstrings!* + - **Every object has a TOC entry:** we render a heading for each object, meaning *MkDocs* picks them into the Table of Contents, which is nicely display by the Material theme. Thanks to *mkdocstrings* cross-reference ability, - you can even reference other objects within your docstrings, with the classic Markdown syntax: + you can reference other objects within your docstrings, with the classic Markdown syntax: `[this object][package.module.object]` or directly with `[package.module.object][]` + - **Source code display:** *mkdocstrings* can add a collapsible div containing the highlighted source code of the Python object. -To get an example of what is possible, check *mkdocstrings*' -own [documentation](https://mkdocstrings.github.io/), auto-generated from sources by itself of course, -and the following GIF: - -![mkdocstrings_gif2](https://user-images.githubusercontent.com/3999221/77157838-7184db80-6aa2-11ea-9f9a-fe77405202de.gif) - ## Roadmap See the [Feature Roadmap issue](https://github.com/mkdocstrings/mkdocstrings/issues/183) on the bugtracker. diff --git a/docs/handlers/python.md b/docs/handlers/python.md index 4ad1077b..6d0300dc 100644 --- a/docs/handlers/python.md +++ b/docs/handlers/python.md @@ -44,7 +44,7 @@ Option | Type | Description | Default **`filters`** | `list of str` | List of filtering regular expressions. Prefix with `!` to exclude objects whose name match. The default means *exclude private members*. | `["!^_[^_]"]` **`members`** | `bool`, or `list of str` | Explicitly select members. True means *all*, false means *none*. | `True` **`inherited_members`** | `bool` | Also select members inherited from parent classes. | `False` -**`docstring_style`** | `str` | Docstring style to parse. `pytkdocs` supports `google` and `restructured-text`. | `"google"` +**`docstring_style`** | `str` | Docstring style to parse. `pytkdocs` supports `google`, `numpy` and `restructured-text`. *Note: Numpy-style requires the `numpy-style` extra of `pytkdocs`.* | `"google"` **`docstring_options`** | `dict` | Options to pass to the docstring parser. See [Collector: pytkdocs](#collector-pytkdocs) | `{}` **`new_path_syntax`** | `bool` | Whether to use the new "colon" path syntax when importing objects. | `False` @@ -107,7 +107,7 @@ It stands for *(Python) Take Docs*, and is supposed to be a pun on MkDocs (*Make ### Supported docstrings styles -Right now, `pytkdocs` supports the Google-style and reStructuredText-style docstring formats. +Right now, `pytkdocs` supports the Google-style, Numpy-style and reStructuredText-style docstring formats. #### Google-style @@ -206,6 +206,11 @@ Type annotations are read both in the code and in the docstrings. show_root_heading: no show_root_toc_entry: no +#### Numpy-style + +You can see examples of Numpy-style docstrings +in [numpydoc's documentation](https://numpydoc.readthedocs.io/en/latest/format.html). + #### reStructuredText-style !!! warning "Partial support" From 92418c4b3e80b67d5116efa73931fc113daa60e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20V=C3=AEjial=C4=83?= Date: Thu, 1 Apr 2021 21:53:24 +0300 Subject: [PATCH 06/50] fix: Don't hide `setup_commands` errors There seems to be no problem in propagating the `stderr` outwards, and without this there is no feedback as to what went wrong in the `setup_commands`. PR #258: https://github.com/mkdocstrings/mkdocstrings/pull/258 --- src/mkdocstrings/handlers/python.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py index a9e63786..2a23de56 100644 --- a/src/mkdocstrings/handlers/python.py +++ b/src/mkdocstrings/handlers/python.py @@ -152,7 +152,6 @@ def __init__(self, setup_commands: Optional[List[str]] = None) -> None: self.process = Popen( # noqa: S603,S607 (we trust the input, and we don't want to use the absolute path) cmd, universal_newlines=True, - stderr=PIPE, stdout=PIPE, stdin=PIPE, bufsize=-1, From 4abaca80585e6d1204cff7a0dabcf4327b54606a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Apr 2021 13:04:19 +0200 Subject: [PATCH 07/50] chore: Template upgrade --- .copier-answers.yml | 7 ++++--- .github/workflows/ci.yml | 39 ++++++++++++++++++++-------------- Makefile | 2 +- config/flake8.ini | 45 ++++++++++++++++++++-------------------- config/mypy.ini | 1 + config/pytest.ini | 3 ++- duties.py | 22 ++++++++++++-------- pyproject.toml | 41 ++++++++++++++++++------------------ scripts/multirun.sh | 2 +- scripts/setup.sh | 2 +- 10 files changed, 89 insertions(+), 75 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 022b9006..057f9978 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,10 +1,10 @@ # Changes here will be overwritten by Copier -_commit: 0.2.1 +_commit: 0.3.1 _src_path: gh:pawamoy/copier-poetry author_email: pawamoy@pm.me author_fullname: "Timoth\xE9e Mazzucotelli" author_username: pawamoy -copyright_date: '2020' +copyright_date: '2019' copyright_holder: "Timoth\xE9e Mazzucotelli" copyright_holder_email: pawamoy@pm.me copyright_license: ISC License @@ -14,5 +14,6 @@ python_package_command_line_name: mkdocstrings python_package_distribution_name: mkdocstrings python_package_import_name: mkdocstrings repository_name: mkdocstrings -repository_namespace: pawamoy +repository_namespace: mkdocstrings repository_provider: github.com +use_precommit: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4aa606c8..385ddd4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,12 +11,19 @@ defaults: shell: bash env: - LANG: "en_US.utf-8" - LC_ALL: "en_US.utf-8" - POETRY_VIRTUALENVS_IN_PROJECT: "true" - PYTHONIOENCODING: "UTF-8" + LANG: en_US.utf-8 + LC_ALL: en_US.utf-8 + PYTHONIOENCODING: UTF-8 PYTHONPATH: docs + # To fix an error when running Poetry on Windows + # (https://github.com/python-poetry/poetry/issues/2629), + # we set Poetry's cache directory to .poetry_cache in the current directory. + # It makes it easier to later remove the virtualenv when it's broken. + # Absolute path is necessary to avoid this issue: + # https://github.com/python-poetry/poetry/issues/3049 + POETRY_CACHE_DIR: ${{ github.workspace }}/.poetry_cache + jobs: quality: @@ -32,17 +39,17 @@ jobs: with: python-version: 3.8 + - name: Set up Poetry + run: pip install poetry + - name: Set up the cache uses: actions/cache@v1 with: - path: .venv - key: quality-venv-cache-2 + path: .poetry_cache + key: quality-poetry-cache - name: Set up the project - run: | - pip install poetry - poetry install -v || { rm -rf .venv; poetry install -v; } - poetry update + run: poetry install -vv - name: Check if the documentation builds correctly run: | @@ -79,17 +86,17 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Set up Poetry + run: pip install poetry + - name: Set up the cache uses: actions/cache@v1 with: - path: .venv - key: tests-venv-cache-${{ matrix.os }}-py${{ matrix.python-version }} + path: .poetry_cache + key: tests-poetry-cache-${{ matrix.os }}-py${{ matrix.python-version }} - name: Set up the project - run: | - pip install poetry - poetry install -v || { rm -rf .venv; poetry install -v; } - poetry update + run: poetry install -vv || { rm -rf .poetry_cache/virtualenvs/*; poetry install -vv; } - name: Run the test suite run: poetry run duty test diff --git a/Makefile b/Makefile index 28074e01..56c7b5f0 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ args = $(foreach a,$($(subst -,_,$1)_args),$(if $(value $a),$a="$($a)")) check_code_quality_args = files docs_serve_args = host port release_args = version -test_args = cleancov match +test_args = match BASIC_DUTIES = \ changelog \ diff --git a/config/flake8.ini b/config/flake8.ini index 3e559fd2..1db65393 100644 --- a/config/flake8.ini +++ b/config/flake8.ini @@ -3,48 +3,47 @@ exclude = fixtures,docs,site max-line-length = 132 strictness = long docstring-convention = google +ban-relative-imports = true ignore = - # we write docstrings in markdown, not rst - RST*, # redundant with W0622 (builtin override), which is more precise about line number - A001, + A001 # missing docstring in magic method - D105, - # whitespace before ‘:’ (incompatible with Black) - E203, + D105 + # whitespace before ':' (incompatible with Black) + E203 # redundant with E0602 (undefined variable) - F821, + F821 # black already deals with quoting - Q000, + Q000 # use of assert - S101, + S101 # we are not parsing XML - S405, + S405 # line break before binary operator (incompatible with Black) - W503, + W503 # two-lowercase-letters variable DO conform to snake_case naming style - C0103, + C0103 # redunant with D102 (missing docstring) - C0116, + C0116 # line too long - C0301, + C0301 # too many instance attributes - R0902, + R0902 # too few public methods - R0903, + R0903 # too many public methods - R0904, + R0904 # too many branches - R0912, + R0912 # too many methods - R0913, + R0913 # too many local variables - R0914, + R0914 # too many statements - R0915, + R0915 # redundant with F401 (unused import) - W0611, + W0611 # lazy formatting for logging calls - W1203, + W1203 # short name VNE001 diff --git a/config/mypy.ini b/config/mypy.ini index 3d1cd433..98efbae5 100644 --- a/config/mypy.ini +++ b/config/mypy.ini @@ -1,2 +1,3 @@ [mypy] ignore_missing_imports = true +exclude = tests/fixtures/ diff --git a/config/pytest.ini b/config/pytest.ini index 08aef81e..ad72bbe6 100644 --- a/config/pytest.ini +++ b/config/pytest.ini @@ -11,5 +11,6 @@ python_files = tests.py addopts = --cov - --cov-append --cov-config config/coverage.ini +testpaths = + tests diff --git a/duties.py b/duties.py index 3ba18caa..c2b91e64 100644 --- a/duties.py +++ b/duties.py @@ -3,6 +3,7 @@ import os import re import sys +from pathlib import Path from shutil import which from typing import List, Optional, Pattern @@ -94,7 +95,7 @@ def update_changelog( """ env = SandboxedEnvironment(autoescape=False) template = env.from_string(httpx.get(template_url).text) - changelog = Changelog(".", style=commit_style) # noqa: W0621 (shadowing changelog) + changelog = Changelog(".", style=commit_style) if len(changelog.versions_list) == 1: last_version = changelog.versions_list[0] @@ -135,12 +136,12 @@ def changelog(ctx): @duty(pre=["check_code_quality", "check_types", "check_docs", "check_dependencies"]) -def check(ctx): # noqa: W0613 (no use for the context argument) +def check(ctx): """Check it all! Arguments: ctx: The context instance (passed automatically). - """ # noqa: D400 (exclamation mark is funnier) + """ @duty @@ -193,6 +194,8 @@ def check_docs(ctx): """ # mkdocs-gen-files works on 3.7+ only nofail = sys.version_info < (3, 7) + Path("build/coverage").mkdir(parents=True, exist_ok=True) + Path("build/coverage/index.html").touch(exist_ok=True) ctx.run("mkdocs build -s", title="Building documentation", pty=PTY, nofail=nofail, quiet=nofail) @@ -216,6 +219,7 @@ def clean(ctx): 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 pip-wheel-metadata") @@ -258,7 +262,7 @@ def docs_deploy(ctx): @duty -def format(ctx): # noqa: W0622 (we don't mind shadowing the format builtin) +def format(ctx): """Run formatting tools on the code. Arguments: @@ -290,7 +294,7 @@ def release(ctx, version): ctx.run("git push --tags", title="Pushing tags", pty=False) ctx.run("poetry build", title="Building dist/wheel", pty=PTY) ctx.run("poetry publish", title="Publishing version", pty=PTY) - docs_deploy.run() + docs_deploy.run() # type: ignore @duty(silent=True) @@ -300,21 +304,21 @@ def coverage(ctx): Arguments: ctx: The context instance (passed automatically). """ + ctx.run("coverage combine .coverage-*", nofail=True) ctx.run("coverage report --rcfile=config/coverage.ini", capture=False) ctx.run("coverage html --rcfile=config/coverage.ini") @duty -def test(ctx, cleancov: bool = True, match: str = ""): +def test(ctx, match: str = ""): """Run the test suite. Arguments: ctx: The context instance (passed automatically). - cleancov: Whether to remove the `.coverage` file before running the tests. match: A pytest expression to filter selected tests. """ - if cleancov: - ctx.run("rm -f .coverage", silent=True) + py_version = f"{sys.version_info.major}{sys.version_info.minor}" + os.environ["COVERAGE_FILE"] = f".coverage-{py_version}" ctx.run( ["pytest", "-c", "config/pytest.ini", "-n", "auto", "-k", match, "tests"], title="Running tests", diff --git a/pyproject.toml b/pyproject.toml index cf5c7286..0bb52e12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,10 +13,6 @@ repository = "https://github.com/mkdocstrings/mkdocstrings" homepage = "https://github.com/mkdocstrings/mkdocstrings" keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"] packages = [ { include = "mkdocstrings", from = "src" } ] -include = [ - "README.md", - "pyproject.toml" -] [tool.poetry.dependencies] python = "^3.6" @@ -29,38 +25,43 @@ pymdown-extensions = ">=6.3, <9.0" pytkdocs = ">=0.2.0, <0.12.0" [tool.poetry.dev-dependencies] +# formatting, quality, tests autoflake = "^1.4" black = "^20.8b1" -duty = "^0.6.0" -flakehell = "^0.9.0" -flake8-black = "^0.2.1" -flake8-builtins = "^1.5.3" -flake8-tidy-imports = "^4.2.1" -flake8-variables-names = "^0.0.4" -flake8-pytest-style = "^1.3.0" -git-changelog = "^0.4.2" -httpx = "^0.16.1" -isort = {version = "^5.7.0", extras = ["pyproject"]} -jinja2-cli = "^0.7.0" -mkdocs-coverage = "^0.2.1" -mkdocs-gen-files = {version = "^0.3.0", markers = "python_version>='3.7'"} -mkdocs-material = "^6.2.7" -mkdocs-section-index = "^0.2.3" -mypy = "^0.782" +isort = "^5.7.0" +mypy = "^0.812" pytest = "^6.2.2" pytest-cov = "^2.11.1" pytest-randomly = "^3.5.0" pytest-sugar = "^0.9.4" pytest-xdist = "^2.2.0" + +# tasks +duty = "^0.6.0" +git-changelog = "^0.4.2" +httpx = "^0.16.1" toml = "^0.10.2" + +# flake8 plugins darglint = "^1.5.8" flake8-bandit = "^2.1.2" +flake8-black = "^0.2.1" flake8-bugbear = "^20.11.1" +flake8-builtins = "^1.5.3" flake8-comprehensions = "^3.3.1" flake8-docstrings = "^1.5.0" +flake8-pytest-style = "^1.3.0" flake8-string-format = "^0.3.0" +flake8-tidy-imports = "^4.2.1" +flake8-variables-names = "^0.0.4" pep8-naming = "^0.11.1" +# docs +mkdocs-coverage = "^0.2.1" +mkdocs-material = "^6.2.7" +mkdocs-gen-files = {version = "^0.3.0", markers = "python_version>='3.7'"} +mkdocs-section-index = "^0.2.3" + [tool.poetry.plugins."mkdocs.plugins"] mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" diff --git a/scripts/multirun.sh b/scripts/multirun.sh index b483defa..3cacf958 100755 --- a/scripts/multirun.sh +++ b/scripts/multirun.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -PYTHON_VERSIONS="${PYTHON_VERSIONS:-3.6 3.7 3.8 3.9}" +PYTHON_VERSIONS="${PYTHON_VERSIONS-3.6 3.7 3.8 3.9}" if [ -n "${PYTHON_VERSIONS}" ]; then for python_version in ${PYTHON_VERSIONS}; do diff --git a/scripts/setup.sh b/scripts/setup.sh index a626b257..d7b15786 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -PYTHON_VERSIONS="${PYTHON_VERSIONS:-3.6 3.7 3.8 3.9}" +PYTHON_VERSIONS="${PYTHON_VERSIONS-3.6 3.7 3.8 3.9}" install_with_pipx() { if ! command -v "$1" &>/dev/null; then From 2895152a4b5852c579248f9633593a00887318b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Apr 2021 13:04:32 +0200 Subject: [PATCH 08/50] ci: Fix typing warnings --- src/mkdocstrings/extension.py | 2 +- src/mkdocstrings/handlers/base.py | 12 ++++++------ src/mkdocstrings/handlers/rendering.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py index c9f42e6d..0c89a65e 100644 --- a/src/mkdocstrings/extension.py +++ b/src/mkdocstrings/extension.py @@ -79,7 +79,7 @@ def __init__( self._autorefs = autorefs self._updated_env = False - def test(self, parent: Element, block: str) -> bool: + def test(self, parent: Element, block: str) -> bool: # type: ignore """Match our autodoc instructions. Arguments: diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py index 2a95e775..7017ec00 100644 --- a/src/mkdocstrings/handlers/base.py +++ b/src/mkdocstrings/handlers/base.py @@ -11,7 +11,7 @@ import importlib from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Dict, Iterable, Optional, Sequence +from typing import Any, Dict, Iterable, List, Optional, Sequence from xml.etree.ElementTree import Element, tostring from jinja2 import Environment, FileSystemLoader @@ -103,14 +103,14 @@ def __init__(self, directory: str, theme: str, custom_templates: Optional[str] = self.env = Environment( autoescape=True, - loader=FileSystemLoader(paths), + loader=FileSystemLoader(paths), # type: ignore auto_reload=False, # Editing a template in the middle of a build is not useful. ) # type: ignore self.env.filters["any"] = do_any self.env.globals["log"] = get_template_logger() - self._headings = [] - self._md = None # To be populated in `update_env`. + self._headings: List[Element] = [] + self._md: Markdown = None # type: ignore # To be populated in `update_env`. @abstractmethod def render(self, data: CollectorItem, config: dict) -> str: @@ -182,7 +182,7 @@ def do_heading( # First, produce the "fake" heading, for ToC only. el = Element(f"h{heading_level}", attributes) if toc_label is None: - toc_label = content.unescape() if isinstance(el, Markup) else content + toc_label = content.unescape() if isinstance(el, Markup) else content # type: ignore el.set("data-toc-label", toc_label) self._headings.append(el) @@ -382,7 +382,7 @@ def get_handler(self, name: str, handler_config: Optional[dict] = None) -> BaseH if handler_config is None: handler_config = self.get_handler_config(name) module = importlib.import_module(f"mkdocstrings.handlers.{name}") - self._handlers[name] = module.get_handler( + self._handlers[name] = module.get_handler( # type: ignore self._config["theme_name"], self._config["mkdocstrings"]["custom_templates"], **handler_config, diff --git a/src/mkdocstrings/handlers/rendering.py b/src/mkdocstrings/handlers/rendering.py index ed9049ba..77c15a13 100644 --- a/src/mkdocstrings/handlers/rendering.py +++ b/src/mkdocstrings/handlers/rendering.py @@ -3,7 +3,7 @@ import copy import re import textwrap -from typing import List, Optional +from typing import Any, Dict, List, Optional from xml.etree.ElementTree import Element from markdown import Markdown @@ -43,8 +43,8 @@ def __init__(self, md: Markdown): Arguments: md: The Markdown instance to read configs from. """ - config = {} - for ext in md.registeredExtensions: + config: Dict[str, Any] = {} + for ext in md.registeredExtensions: # type: ignore if isinstance(ext, HighlightExtension) and (ext.enabled or not config): config = ext.getConfigs() break # This one takes priority, no need to continue looking @@ -83,7 +83,7 @@ def highlight( # noqa: W0221 (intentionally different params, we're extending t src = textwrap.dedent(src) kwargs.setdefault("css_class", self._css_class) - old_linenums = self.linenums + old_linenums = self.linenums # type: ignore if linenums is not None: self.linenums = linenums try: From 2ae9cf230fb4e1a62736235893e0ec6ad0900490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Apr 2021 13:15:18 +0200 Subject: [PATCH 09/50] chore: Pin pytest-randomly --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0bb52e12..ffb3a06d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,9 @@ isort = "^5.7.0" mypy = "^0.812" pytest = "^6.2.2" pytest-cov = "^2.11.1" -pytest-randomly = "^3.5.0" +# TODO: remove constraint when this issue is resolved in Poetry: +# https://github.com/pytest-dev/pytest-randomly/issues/335 +pytest-randomly = "<3.6.0" pytest-sugar = "^0.9.4" pytest-xdist = "^2.2.0" From 6dcf342fb83b19e385d56d37235f2b23e8c8c767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 2 Apr 2021 15:56:53 +0200 Subject: [PATCH 10/50] fix: Fix custom handler not being used Custom handlers configured in individual autodoc instructions were not picked up by mkdocstrings. Issue #259: https://github.com/mkdocstrings/mkdocstrings/issues/259 PR #263: https://github.com/mkdocstrings/mkdocstrings/pull/263 --- src/mkdocstrings/handlers/base.py | 4 ++-- tests/test_extension.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py index 7017ec00..ddb0907e 100644 --- a/src/mkdocstrings/handlers/base.py +++ b/src/mkdocstrings/handlers/base.py @@ -343,10 +343,10 @@ def get_handler_name(self, config: dict) -> str: Returns: The name of the handler to use. """ - config = self._config["mkdocstrings"] + global_config = self._config["mkdocstrings"] if "handler" in config: return config["handler"] - return config["default_handler"] + return global_config["default_handler"] def get_handler_config(self, name: str) -> dict: """Return the global configuration of the given handler. diff --git a/tests/test_extension.py b/tests/test_extension.py index e2e92903..8c1693d4 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -152,3 +152,9 @@ def test_no_double_toc(ext_markdown, expect_permalink): }, {"level": 1, "id": "bb", "name": "bb", "children": []}, ] + + +def test_use_custom_handler(ext_markdown): + """Assert that we use the custom handler declared in an individual autodoc instruction.""" + with pytest.raises(ModuleNotFoundError): + ext_markdown.convert("::: tests.fixtures.headings\n handler: not_here") From 14ed959860a784a835cd71f911081f2026d66c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 4 Apr 2021 11:23:58 +0200 Subject: [PATCH 11/50] feat: Generate objects inventory This approach uses `do_heading`: - implemented `Inventory` and `InventoryItem` classes with format methods - an inventory item has a name, a domain, a role, an URI, a priority and a display name, just like Sphinx's inventories - upon rendering an object's title in a template, we pass an additional role parameter to the `do_heading` special filter - `do_heading` renders that title to HTML and sets a data-role attribute if a role was given - it allows us to get it back in the main extension's run method - `_process_block` returns the handler directly instead of its renderer collected headings, so we can both get the headings in the run method as well as the domain on the handler (which is "py" on the python handler) - we have everything we need to then register that heading/object in the inventory instance that is attached to the Handlers unique instance - in the `post_build` hook, if a handler requested the inventory, or the user enabled it in the global config, we format that inventory to Sphinx's `objects.inv` and add it to the site files This implementation prevents registering headings that are not bound to objects. It will also allow extensibility if we ever want to generate inventories in other formats. I reused Sphinx's attributes for an inventory item (name, domain, role, etc.) because I think it makes sense in the context of a language-agnostic documentation tool. --- Issue #251: https://github.com/mkdocstrings/mkdocstrings/issues/251 PR #253: https://github.com/mkdocstrings/mkdocstrings/pull/253 --- duties.py | 6 ++ mkdocs.yml | 3 +- src/mkdocstrings/extension.py | 25 +++-- src/mkdocstrings/handlers/base.py | 14 +++ src/mkdocstrings/handlers/python.py | 11 ++- src/mkdocstrings/inventory.py | 94 +++++++++++++++++++ src/mkdocstrings/plugin.py | 27 +++++- .../templates/python/material/attribute.html | 2 + .../templates/python/material/class.html | 2 + .../templates/python/material/function.html | 2 + .../templates/python/material/method.html | 2 + .../templates/python/material/module.html | 2 + tests/test_inventory.py | 34 +++++++ 13 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 src/mkdocstrings/inventory.py create mode 100644 tests/test_inventory.py diff --git a/duties.py b/duties.py index c2b91e64..32a8d7ba 100644 --- a/duties.py +++ b/duties.py @@ -317,6 +317,12 @@ def test(ctx, match: str = ""): ctx: The context instance (passed automatically). match: A pytest expression to filter selected tests. """ + try: + import sphinx # isort:skip # noqa: F401 + import docutils # isort:skip # noqa: F401 + except ImportError: + ctx.run("pip install sphinx docutils --no-deps", title="Installing additional test dependencies") + py_version = f"{sys.version_info.major}{sys.version_info.minor}" os.environ["COVERAGE_FILE"] = f".coverage-{py_version}" ctx.run( diff --git a/mkdocs.yml b/mkdocs.yml index ff238b34..8a59f9e7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,8 +22,9 @@ nav: - rendering.py: reference/handlers/rendering.md - python.py: reference/handlers/python.md - extension.py: reference/extension.md - - plugin.py: reference/plugin.md + - inventory.py: reference/inventory.md - loggers.py: reference/loggers.md + - plugin.py: reference/plugin.md - mkdocs_autorefs: - references.py: reference/autorefs/references.md - plugin.py: reference/autorefs/plugin.md diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py index 0c89a65e..0e6c36ff 100644 --- a/src/mkdocstrings/extension.py +++ b/src/mkdocstrings/extension.py @@ -23,7 +23,7 @@ """ import re from collections import ChainMap -from typing import Mapping, MutableSequence, Sequence, Tuple +from typing import Mapping, MutableSequence, Tuple from xml.etree.ElementTree import Element import yaml @@ -35,7 +35,7 @@ from markdown.treeprocessors import Treeprocessor from mkdocs_autorefs.plugin import AutorefsPlugin -from mkdocstrings.handlers.base import CollectionError, CollectorItem, Handlers +from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem, Handlers from mkdocstrings.loggers import get_logger try: @@ -117,15 +117,26 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None: heading_level = match["heading"].count("#") log.debug(f"Matched '::: {identifier}'") - html, headings = self._process_block(identifier, block, heading_level) + html, handler = self._process_block(identifier, block, heading_level) el = Element("div", {"class": "mkdocstrings"}) # The final HTML is inserted as opaque to subsequent processing, and only revealed at the end. el.text = self.md.htmlStash.store(html) # So we need to duplicate the headings directly (and delete later), just so 'toc' can pick them up. + headings = handler.renderer.get_headings() el.extend(headings) for heading in headings: - self._autorefs.register_anchor(self._autorefs.current_page, heading.attrib["id"]) + page = self._autorefs.current_page + anchor = heading.attrib["id"] + self._autorefs.register_anchor(page, anchor) + + if "data-role" in heading.attrib: + self._handlers.inventory.register( + name=anchor, + domain=handler.domain, + role=heading.attrib["data-role"], + uri=f"{page}#{anchor}", + ) parent.append(el) @@ -135,7 +146,7 @@ def run(self, parent: Element, blocks: MutableSequence[str]) -> None: # list for future processing. blocks.insert(0, the_rest) - def _process_block(self, identifier: str, yaml_block: str, heading_level: int = 0) -> Tuple[str, Sequence[Element]]: + def _process_block(self, identifier: str, yaml_block: str, heading_level: int = 0) -> Tuple[str, BaseHandler]: """Process an autodoc block. Arguments: @@ -148,7 +159,7 @@ def _process_block(self, identifier: str, yaml_block: str, heading_level: int = TemplateNotFound: When a template used for rendering could not be found. Returns: - Rendered HTML and the list of heading elements encoutered. + Rendered HTML and the handler that was used. """ config = yaml.safe_load(yaml_block) or {} handler_name = self._handlers.get_handler_name(config) @@ -185,7 +196,7 @@ def _process_block(self, identifier: str, yaml_block: str, heading_level: int = ) raise - return (rendered, handler.renderer.get_headings()) + return (rendered, handler) def get_item_configs(handler_config: dict, config: dict) -> Tuple[Mapping, Mapping]: diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py index ddb0907e..a21c18c2 100644 --- a/src/mkdocstrings/handlers/base.py +++ b/src/mkdocstrings/handlers/base.py @@ -24,6 +24,7 @@ IdPrependingTreeprocessor, MkdocstringsInnerExtension, ) +from mkdocstrings.inventory import Inventory from mkdocstrings.loggers import get_template_logger CollectorItem = Any @@ -163,6 +164,7 @@ def do_heading( content: str, heading_level: int, *, + role: Optional[str] = None, hidden: bool = False, toc_label: Optional[str] = None, **attributes: str, @@ -172,6 +174,7 @@ def do_heading( Arguments: content: The HTML within the heading. heading_level: The level of heading (e.g. 3 -> `h3`). + role: An optional role for the object bound to this heading. hidden: If True, only register it for the table of contents, don't render anything. toc_label: The title to use in the table of contents ('data-toc-label' attribute). attributes: Any extra HTML attributes of the heading. @@ -184,6 +187,8 @@ def do_heading( if toc_label is None: toc_label = content.unescape() if isinstance(el, Markup) else content # type: ignore el.set("data-toc-label", toc_label) + if role: + el.set("data-role", role) self._headings.append(el) if hidden: @@ -285,8 +290,16 @@ class BaseHandler: Inherit from this class to implement a handler. It's usually just a combination of a collector and a renderer, but you can make it as complex as you need. + + Attributes: + domain: The cross-documentation domain/language for this handler. + enable_inventory: Whether this handler is interested in enabling the creation + of the `objects.inv` Sphinx inventory file. """ + domain: str = "default" + enable_inventory: bool = False + def __init__(self, collector: BaseCollector, renderer: BaseRenderer) -> None: """Initialize the object. @@ -314,6 +327,7 @@ def __init__(self, config: dict) -> None: """ self._config = config self._handlers: Dict[str, BaseHandler] = {} + self.inventory: Inventory = Inventory(project=self._config["site_name"]) def get_anchor(self, identifier: str) -> Optional[str]: """Return the canonical HTML anchor for the identifier, if any of the seen handlers can collect it. diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py index 2a23de56..fc63138a 100644 --- a/src/mkdocstrings/handlers/python.py +++ b/src/mkdocstrings/handlers/python.py @@ -235,7 +235,16 @@ def teardown(self) -> None: class PythonHandler(BaseHandler): - """The Python handler class, nothing specific here.""" + """The Python handler class. + + Attributes: + domain: The cross-documentation domain/language for this handler. + enable_inventory: Whether this handler is interested in enabling the creation + of the `objects.inv` Sphinx inventory file. + """ + + domain: str = "py" # to match Sphinx's default domain + enable_inventory: bool = True def get_handler( diff --git a/src/mkdocstrings/inventory.py b/src/mkdocstrings/inventory.py new file mode 100644 index 00000000..359c3b1d --- /dev/null +++ b/src/mkdocstrings/inventory.py @@ -0,0 +1,94 @@ +"""Module responsible for the objects inventory.""" + +# Credits to Brian Skinn and the sphobjinv project: +# https://github.com/bskinn/sphobjinv + +import zlib +from textwrap import dedent +from typing import List, Optional + + +class InventoryItem: + """Inventory item.""" + + def __init__(self, name: str, domain: str, role: str, uri: str, priority: str = "1", dispname: str = "-"): + """Initialize the object. + + Arguments: + name: The item name. + domain: The item domain, like 'python' or 'crystal'. + role: The item role, like 'class' or 'method'. + uri: The item URI. + priority: The item priority. It can help for inventory suggestions. + dispname: The item display name. + """ + self.name: str = name + self.domain: str = domain + self.role: str = role + self.uri: str = uri + self.priority: str = priority + self.dispname: str = dispname + + def format_sphinx(self) -> str: + """Format this item as a Sphinx inventory line. + + Returns: + A line formatted for an `objects.inv` file. + """ + uri = self.uri + name_length = len(self.name) + if uri[-name_length - 1 :] == "#" + self.name: + uri = uri[:-name_length] + "$" + return f"{self.name} {self.domain}:{self.role} {self.priority} {uri} {self.dispname}" + + +class Inventory(dict): + """Inventory of collected and rendered objects.""" + + def __init__(self, items: Optional[List[InventoryItem]] = None, project: str = "project", version: str = "0.0.0"): + """Initialize the object. + + Arguments: + items: A list of items. + project: The project name. + version: The project version. + """ + super().__init__() + items = items or [] + for item in items: + self[item.name] = item + self.project = project + self.version = version + + def register(self, *args, **kwargs): + """Create and register an item. + + Arguments: + *args: Arguments passed to [InventoryItem][mkdocstrings.inventory.InventoryItem]. + **kwargs: Keyword arguments passed to [InventoryItem][mkdocstrings.inventory.InventoryItem]. + """ + item = InventoryItem(*args, **kwargs) + self[item.name] = item + + def format_sphinx(self) -> bytes: + """Format this inventory as a Sphinx `objects.inv` file. + + Returns: + The inventory as bytes. + """ + header = ( + dedent( + f""" + # Sphinx inventory version 2 + # Project: {self.project} + # Version: {self.version} + # The remainder of this file is compressed using zlib. + """ + ) + .lstrip() + .encode("utf8") + ) + + lines = [item.format_sphinx().encode("utf8") for item in self.values()] + # we use compression level 0 to make the file readable by humans + return header + zlib.compress(b"\n".join(lines), 0) diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index 054cd2e0..7fd67300 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -52,6 +52,7 @@ class MkdocstringsPlugin(BasePlugin): ("handlers", MkType(dict, default={})), ("default_handler", MkType(str, default="python")), ("custom_templates", MkType(str, default=None)), + ("enable_inventory", MkType(bool, default=None)), ) """ The configuration options of `mkdocstrings`, written in `mkdocs.yml`. @@ -156,6 +157,7 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused theme_name = config["theme"].name extension_config = { + "site_name": config["site_name"], "theme_name": theme_name, "mdx": config["markdown_extensions"], "mdx_configs": config["mdx_configs"], @@ -174,15 +176,27 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused config["plugins"]["autorefs"] = autorefs log.debug(f"Added a subdued autorefs instance {autorefs!r}") # Add collector-based fallback in either case. - autorefs.get_fallback_anchor = self._handlers.get_anchor + autorefs.get_fallback_anchor = self.handlers.get_anchor - mkdocstrings_extension = MkdocstringsExtension(extension_config, self._handlers, autorefs) + mkdocstrings_extension = MkdocstringsExtension(extension_config, self.handlers, autorefs) config["markdown_extensions"].append(mkdocstrings_extension) config["extra_css"].insert(0, self.css_filename) # So that it has lower priority than user files. return config + @property + def inventory_enabled(self) -> bool: + """Tell if the inventory is enabled or not. + + Returns: + Whether the inventory is enabled. + """ + inventory_enabled = self.config["enable_inventory"] + if inventory_enabled is None: + inventory_enabled = any(handler.enable_inventory for handler in self.handlers.seen_handlers) + return inventory_enabled + def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 (unused arguments, cannot be static) """Teardown the handlers. @@ -199,12 +213,17 @@ def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 config: The MkDocs config object. kwargs: Additional arguments passed by MkDocs. """ - if self._handlers: + if self.handlers: css_content = "\n".join(handler.renderer.extra_css for handler in self.handlers.seen_handlers) write_file(css_content.encode("utf-8"), os.path.join(config["site_dir"], self.css_filename)) + if self.inventory_enabled: + log.debug("Creating inventory file objects.inv") + inv_contents = self.handlers.inventory.format_sphinx() + write_file(inv_contents, os.path.join(config["site_dir"], "objects.inv")) + log.debug("Tearing handlers down") - self._handlers.teardown() + self.handlers.teardown() def get_handler(self, handler_name: str) -> BaseHandler: """Get a handler by its name. See [mkdocstrings.handlers.base.Handlers.get_handler][]. diff --git a/src/mkdocstrings/templates/python/material/attribute.html b/src/mkdocstrings/templates/python/material/attribute.html index 4b742509..b40518a9 100644 --- a/src/mkdocstrings/templates/python/material/attribute.html +++ b/src/mkdocstrings/templates/python/material/attribute.html @@ -17,6 +17,7 @@ {% endif %} {% filter heading(heading_level, + role="data" if obj == module else "attr", id=html_id, class="doc doc-heading", toc_label=attribute.name) %} @@ -35,6 +36,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, + role="data" if obj == module else "attr", id=html_id, toc_label=attribute.path, hidden=True) %} diff --git a/src/mkdocstrings/templates/python/material/class.html b/src/mkdocstrings/templates/python/material/class.html index b3e33f84..cdf45484 100644 --- a/src/mkdocstrings/templates/python/material/class.html +++ b/src/mkdocstrings/templates/python/material/class.html @@ -17,6 +17,7 @@ {% endif %} {% filter heading(heading_level, + role="class", id=html_id, class="doc doc-heading", toc_label=class.name) %} @@ -32,6 +33,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, + role="class", id=html_id, toc_label=class.path, hidden=True) %} diff --git a/src/mkdocstrings/templates/python/material/function.html b/src/mkdocstrings/templates/python/material/function.html index 5ac592b9..a0dc3ff0 100644 --- a/src/mkdocstrings/templates/python/material/function.html +++ b/src/mkdocstrings/templates/python/material/function.html @@ -17,6 +17,7 @@ {% endif %} {% filter heading(heading_level, + role="func", id=html_id, class="doc doc-heading", toc_label=function.name ~ "()") %} @@ -35,6 +36,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, + role="func", id=html_id, toc_label=function.path, hidden=True) %} diff --git a/src/mkdocstrings/templates/python/material/method.html b/src/mkdocstrings/templates/python/material/method.html index 19e9a530..46b79b96 100644 --- a/src/mkdocstrings/templates/python/material/method.html +++ b/src/mkdocstrings/templates/python/material/method.html @@ -17,6 +17,7 @@ {% endif %} {% filter heading(heading_level, + role="meth", id=html_id, class="doc doc-heading", toc_label=method.name ~ "()") %} @@ -35,6 +36,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, + role="meth", id=html_id, toc_label=method.path, hidden=True) %} diff --git a/src/mkdocstrings/templates/python/material/module.html b/src/mkdocstrings/templates/python/material/module.html index bff6fdcf..68d86b71 100644 --- a/src/mkdocstrings/templates/python/material/module.html +++ b/src/mkdocstrings/templates/python/material/module.html @@ -17,6 +17,7 @@ {% endif %} {% filter heading(heading_level, + role="mod", id=html_id, class="doc doc-heading", toc_label=module.name) %} @@ -32,6 +33,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, + role="mod", id=html_id, toc_label=module.path, hidden=True) %} diff --git a/tests/test_inventory.py b/tests/test_inventory.py new file mode 100644 index 00000000..c1faf99e --- /dev/null +++ b/tests/test_inventory.py @@ -0,0 +1,34 @@ +"""Tests for the inventory module.""" + +from io import BytesIO +from os.path import join +from pathlib import Path + +import pytest + +from mkdocstrings.inventory import Inventory, InventoryItem + +sphinx_inventory = pytest.importorskip("sphinx.util.inventory", reason="Sphinx is not installed") +MKDOCSTRINGS_OBJECTS_INV = Path("site/objects.inv") + + +@pytest.mark.parametrize( + "inventory", + [ + Inventory(), + Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url")]), + Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#object_path")]), + Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#other_anchor")]), + ], +) +def test_sphinx_load_inventory_file(inventory): + """Perform the 'live' inventory load test.""" + buffer = BytesIO(inventory.format_sphinx()) + sphinx_inventory.InventoryFile.load(buffer, "", join) + + +@pytest.mark.skipif(not MKDOCSTRINGS_OBJECTS_INV.exists(), reason="site/objects.inv does not exist") +def test_sphinx_load_mkdocstrings_inventory_file(): + """Perform the 'live' inventory load test on mkdocstrings own inventory.""" + with MKDOCSTRINGS_OBJECTS_INV.open("rb") as fp: + sphinx_inventory.InventoryFile.load(fp, "", join) From bbd85a92fa70bddfe10a907a4d63b8daf0810cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Wed, 21 Apr 2021 20:57:19 +0200 Subject: [PATCH 12/50] fix: Fix Sphinx inventory generation Issue #265: https://github.com/mkdocstrings/mkdocstrings/issues/265 PR #267: https://github.com/mkdocstrings/mkdocstrings/pull/267 --- src/mkdocstrings/inventory.py | 3 +- .../templates/python/material/function.html | 4 +- .../templates/python/material/method.html | 4 +- .../templates/python/material/module.html | 4 +- tests/test_inventory.py | 37 ++++++++++++++----- tests/test_plugin.py | 16 -------- 6 files changed, 34 insertions(+), 34 deletions(-) delete mode 100644 tests/test_plugin.py diff --git a/src/mkdocstrings/inventory.py b/src/mkdocstrings/inventory.py index 359c3b1d..2d12fa75 100644 --- a/src/mkdocstrings/inventory.py +++ b/src/mkdocstrings/inventory.py @@ -90,5 +90,4 @@ def format_sphinx(self) -> bytes: ) lines = [item.format_sphinx().encode("utf8") for item in self.values()] - # we use compression level 0 to make the file readable by humans - return header + zlib.compress(b"\n".join(lines), 0) + return header + zlib.compress(b"\n".join(lines) + b"\n", 9) diff --git a/src/mkdocstrings/templates/python/material/function.html b/src/mkdocstrings/templates/python/material/function.html index a0dc3ff0..6f8e6c77 100644 --- a/src/mkdocstrings/templates/python/material/function.html +++ b/src/mkdocstrings/templates/python/material/function.html @@ -17,7 +17,7 @@ {% endif %} {% filter heading(heading_level, - role="func", + role="function", id=html_id, class="doc doc-heading", toc_label=function.name ~ "()") %} @@ -36,7 +36,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, - role="func", + role="function", id=html_id, toc_label=function.path, hidden=True) %} diff --git a/src/mkdocstrings/templates/python/material/method.html b/src/mkdocstrings/templates/python/material/method.html index 46b79b96..807009e5 100644 --- a/src/mkdocstrings/templates/python/material/method.html +++ b/src/mkdocstrings/templates/python/material/method.html @@ -17,7 +17,7 @@ {% endif %} {% filter heading(heading_level, - role="meth", + role="method", id=html_id, class="doc doc-heading", toc_label=method.name ~ "()") %} @@ -36,7 +36,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, - role="meth", + role="method", id=html_id, toc_label=method.path, hidden=True) %} diff --git a/src/mkdocstrings/templates/python/material/module.html b/src/mkdocstrings/templates/python/material/module.html index 68d86b71..ba8f4eac 100644 --- a/src/mkdocstrings/templates/python/material/module.html +++ b/src/mkdocstrings/templates/python/material/module.html @@ -17,7 +17,7 @@ {% endif %} {% filter heading(heading_level, - role="mod", + role="module", id=html_id, class="doc doc-heading", toc_label=module.name) %} @@ -33,7 +33,7 @@ {% else %} {% if config.show_root_toc_entry %} {% filter heading(heading_level, - role="mod", + role="module", id=html_id, toc_label=module.path, hidden=True) %} diff --git a/tests/test_inventory.py b/tests/test_inventory.py index c1faf99e..471ed941 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -1,19 +1,20 @@ """Tests for the inventory module.""" +import sys from io import BytesIO from os.path import join -from pathlib import Path import pytest +from mkdocs.commands.build import build +from mkdocs.config import load_config from mkdocstrings.inventory import Inventory, InventoryItem -sphinx_inventory = pytest.importorskip("sphinx.util.inventory", reason="Sphinx is not installed") -MKDOCSTRINGS_OBJECTS_INV = Path("site/objects.inv") +sphinx = pytest.importorskip("sphinx.util.inventory", reason="Sphinx is not installed") @pytest.mark.parametrize( - "inventory", + "our_inv", [ Inventory(), Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url")]), @@ -21,14 +22,30 @@ Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#other_anchor")]), ], ) -def test_sphinx_load_inventory_file(inventory): +def test_sphinx_load_inventory_file(our_inv): """Perform the 'live' inventory load test.""" - buffer = BytesIO(inventory.format_sphinx()) - sphinx_inventory.InventoryFile.load(buffer, "", join) + buffer = BytesIO(our_inv.format_sphinx()) + sphinx_inv = sphinx.InventoryFile.load(buffer, "", join) + sphinx_inv_length = sum(len(sphinx_inv[key]) for key in sphinx_inv) + assert sphinx_inv_length == len(our_inv.values()) -@pytest.mark.skipif(not MKDOCSTRINGS_OBJECTS_INV.exists(), reason="site/objects.inv does not exist") + for item in our_inv.values(): + assert item.name in sphinx_inv[f"{item.domain}:{item.role}"] + + +@pytest.mark.skipif(sys.version_info < (3, 7), reason="using plugins that require Python 3.7") def test_sphinx_load_mkdocstrings_inventory_file(): """Perform the 'live' inventory load test on mkdocstrings own inventory.""" - with MKDOCSTRINGS_OBJECTS_INV.open("rb") as fp: - sphinx_inventory.InventoryFile.load(fp, "", join) + mkdocs_config = load_config() + build(mkdocs_config) + own_inv = mkdocs_config["plugins"]["mkdocstrings"].handlers.inventory + + with open("site/objects.inv", "rb") as fp: + sphinx_inv = sphinx.InventoryFile.load(fp, "", join) + + sphinx_inv_length = sum(len(sphinx_inv[key]) for key in sphinx_inv) + assert sphinx_inv_length == len(own_inv.values()) + + for item in own_inv.values(): + assert item.name in sphinx_inv[f"{item.domain}:{item.role}"] diff --git a/tests/test_plugin.py b/tests/test_plugin.py deleted file mode 100644 index 3bdad73c..00000000 --- a/tests/test_plugin.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Tests for the plugin module.""" - -import sys - -import pytest -from mkdocs.commands.build import build -from mkdocs.config.base import load_config - - -@pytest.mark.skipif(sys.version_info < (3, 7), reason="using plugins that require Python 3.7") -@pytest.mark.xfail(sys.version_info >= (3, 9), reason="pytkdocs is failing on Python 3.9") -def test_plugin(tmp_path): - """Build our own documentation.""" - config = load_config() - config["site_dir"] = tmp_path - build(config) From d2e9e1ef3cf304081b07f763843a9722bf9b117e Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Fri, 30 Apr 2021 22:54:32 +0200 Subject: [PATCH 13/50] fix: Don't render empty code blocks for missing type annotations --- src/mkdocstrings/templates/python/material/attributes.html | 2 +- src/mkdocstrings/templates/python/material/parameters.html | 2 +- src/mkdocstrings/templates/python/material/return.html | 2 +- src/mkdocstrings/templates/python/readthedocs/parameters.html | 2 +- src/mkdocstrings/templates/python/readthedocs/return.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mkdocstrings/templates/python/material/attributes.html b/src/mkdocstrings/templates/python/material/attributes.html index 0c935472..02a2935b 100644 --- a/src/mkdocstrings/templates/python/material/attributes.html +++ b/src/mkdocstrings/templates/python/material/attributes.html @@ -12,7 +12,7 @@ {% for attribute in attributes %} {{ attribute.name }} - {{ attribute.annotation }} + {% if attribute.annotation %}{{ attribute.annotation }}{% endif %} {{ attribute.description|convert_markdown(heading_level, html_id) }} {% endfor %} diff --git a/src/mkdocstrings/templates/python/material/parameters.html b/src/mkdocstrings/templates/python/material/parameters.html index 8ff69147..321318e0 100644 --- a/src/mkdocstrings/templates/python/material/parameters.html +++ b/src/mkdocstrings/templates/python/material/parameters.html @@ -13,7 +13,7 @@ {% for parameter in parameters %} {{ parameter.name }} - {{ parameter.annotation }} + {% if parameter.annotation %}{{ parameter.annotation }}{% endif %} {{ parameter.description|convert_markdown(heading_level, html_id) }} {% if parameter.default %}{{ parameter.default }}{% else %}required{% endif %} diff --git a/src/mkdocstrings/templates/python/material/return.html b/src/mkdocstrings/templates/python/material/return.html index cb108a1b..f4282491 100644 --- a/src/mkdocstrings/templates/python/material/return.html +++ b/src/mkdocstrings/templates/python/material/return.html @@ -9,7 +9,7 @@ - {{ return.annotation }} + {% if return.annotation %}{{ return.annotation }}{% endif %} {{ return.description|convert_markdown(heading_level, html_id) }} diff --git a/src/mkdocstrings/templates/python/readthedocs/parameters.html b/src/mkdocstrings/templates/python/readthedocs/parameters.html index 5ae18219..197a411e 100644 --- a/src/mkdocstrings/templates/python/readthedocs/parameters.html +++ b/src/mkdocstrings/templates/python/readthedocs/parameters.html @@ -10,7 +10,7 @@
    {% for parameter in parameters %} -
  • {{ ("**" + parameter.name + "** (`" + parameter.annotation + "`) – " + parameter.description)|convert_markdown(heading_level, html_id) }}
  • +
  • {{ ("**" + parameter.name + "**" + (" (`" + parameter.annotation + "`)" if parameter.annotation else "") + " – " + parameter.description)|convert_markdown(heading_level, html_id) }}
  • {% endfor %}
diff --git a/src/mkdocstrings/templates/python/readthedocs/return.html b/src/mkdocstrings/templates/python/readthedocs/return.html index f30b9a25..7e45ecaf 100644 --- a/src/mkdocstrings/templates/python/readthedocs/return.html +++ b/src/mkdocstrings/templates/python/readthedocs/return.html @@ -9,7 +9,7 @@ Returns:
    -
  • {{ ("`" + return.annotation + "` – " + return.description)|convert_markdown(heading_level, html_id) }}
  • +
  • {{ (("`" + return.annotation + "` – ") if return.annotation else "") + return.description)|convert_markdown(heading_level, html_id) }}
From 974ca9010efca1b8279767faf8efcd2470a8371d Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Fri, 30 Apr 2021 22:49:02 +0200 Subject: [PATCH 14/50] feat: Very basic support for MkDocs theme It mimicks the look and the elements applied on this page: https://www.mkdocs.org/user-guide/plugins/ --- .../templates/python/mkdocs/exceptions.html | 7 +++++++ .../templates/python/mkdocs/parameters.html | 7 +++++++ src/mkdocstrings/templates/python/mkdocs/return.html | 5 +++++ src/mkdocstrings/templates/python/mkdocs/style.css | 11 +++++++++++ 4 files changed, 30 insertions(+) create mode 100644 src/mkdocstrings/templates/python/mkdocs/exceptions.html create mode 100644 src/mkdocstrings/templates/python/mkdocs/parameters.html create mode 100644 src/mkdocstrings/templates/python/mkdocs/return.html create mode 100644 src/mkdocstrings/templates/python/mkdocs/style.css diff --git a/src/mkdocstrings/templates/python/mkdocs/exceptions.html b/src/mkdocstrings/templates/python/mkdocs/exceptions.html new file mode 100644 index 00000000..f5b592f5 --- /dev/null +++ b/src/mkdocstrings/templates/python/mkdocs/exceptions.html @@ -0,0 +1,7 @@ +{{ log.debug() }} +
+
Exceptions: + {% for exception in exceptions %} +
{{ ("`" + exception.annotation + "`: " + exception.description)|convert_markdown(heading_level, html_id) }}
+ {% endfor %} +
diff --git a/src/mkdocstrings/templates/python/mkdocs/parameters.html b/src/mkdocstrings/templates/python/mkdocs/parameters.html new file mode 100644 index 00000000..39db7ea3 --- /dev/null +++ b/src/mkdocstrings/templates/python/mkdocs/parameters.html @@ -0,0 +1,7 @@ +{{ log.debug() }} +
+
Parameters: + {% for parameter in parameters %} +
{{ ("**" + parameter.name + ":** " + ("`" + parameter.annotation + "` – " if parameter.annotation else "") + parameter.description)|convert_markdown(heading_level, html_id) }}
+ {% endfor %} +
diff --git a/src/mkdocstrings/templates/python/mkdocs/return.html b/src/mkdocstrings/templates/python/mkdocs/return.html new file mode 100644 index 00000000..270823c4 --- /dev/null +++ b/src/mkdocstrings/templates/python/mkdocs/return.html @@ -0,0 +1,5 @@ +{{ log.debug() }} +
+
Returns: +
{{ (("`" + return.annotation + "` – " if return.annotation else "") + return.description)|convert_markdown(heading_level, html_id) }}
+
diff --git a/src/mkdocstrings/templates/python/mkdocs/style.css b/src/mkdocstrings/templates/python/mkdocs/style.css new file mode 100644 index 00000000..9db45032 --- /dev/null +++ b/src/mkdocstrings/templates/python/mkdocs/style.css @@ -0,0 +1,11 @@ +.doc-contents { + padding-left: 20px; +} + +.doc-contents dd>p { + margin-bottom: 0.5rem; +} + +.doc-contents dl+dl { + margin-top: -0.5rem; +} From 3cffe5603f3e9809618bdab3c18c0584baaec9db Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Fri, 30 Apr 2021 23:14:55 +0200 Subject: [PATCH 15/50] chore: MkDocs default schema needs to be obtained differently now PR #273: https://github.com/mkdocstrings/mkdocstrings/pull/273 --- tests/test_extension.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_extension.py b/tests/test_extension.py index 8c1693d4..ed43a07b 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -7,11 +7,19 @@ from markdown import Markdown from mkdocs import config +try: + from mkdocs.config.defaults import get_schema +except ImportError: + + def get_schema(): + """Fallback for old versions of MkDocs.""" + return config.DEFAULT_SCHEMA + @pytest.fixture(name="ext_markdown") def fixture_ext_markdown(request, tmp_path): """Yield a Markdown instance with MkdocstringsExtension, with config adjustments.""" - conf = config.Config(schema=config.DEFAULT_SCHEMA) + conf = config.Config(schema=get_schema()) conf_dict = { "site_name": "foo", From f9e1a5e8742b5d950fa6407675ee52969ac194d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 1 May 2021 10:50:05 +0200 Subject: [PATCH 16/50] docs: Use literate nav to generate reference sidebar Co-authored-by: Oleh Prypin Issue #179: https://github.com/mkdocstrings/mkdocstrings/issues/179 Issue #268: https://github.com/mkdocstrings/mkdocstrings/issues/268 PR #271: https://github.com/mkdocstrings/mkdocstrings/pull/271 --- docs/SUMMARY.md | 14 ++++++++++++++ docs/gen_doc_stubs.py | 22 +++++++++++++++++----- mkdocs.yml | 33 ++------------------------------- pyproject.toml | 3 ++- 4 files changed, 35 insertions(+), 37 deletions(-) create mode 100644 docs/SUMMARY.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 00000000..ae424f83 --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,14 @@ +- [Overview](index.md) +- [Usage](usage.md) + - [Theming](theming.md) + - [Handlers](handlers/overview.md) + - [Python](handlers/python.md) + - [Crystal](https://mkdocstrings.github.io/crystal/) + - [Troubleshooting](troubleshooting.md) +- [Code Reference](reference/) +- [Contributing](contributing.md) + - [Code of Conduct](code_of_conduct.md) + - [Coverage report](coverage.md) +- [Changelog](changelog.md) +- [Credits](credits.md) +- [License](license.md) diff --git a/docs/gen_doc_stubs.py b/docs/gen_doc_stubs.py index ccbbdc86..359878d2 100644 --- a/docs/gen_doc_stubs.py +++ b/docs/gen_doc_stubs.py @@ -4,11 +4,23 @@ import mkdocs_gen_files -for path in Path("src", "mkdocstrings").glob("**/*.py"): - doc_path = Path("reference", path.relative_to("src", "mkdocstrings")).with_suffix(".md") +nav = mkdocs_gen_files.Nav() - with mkdocs_gen_files.open(doc_path, "w") as f: - ident = ".".join(path.relative_to("src").with_suffix("").parts) +for path in sorted(Path("src").glob("**/*.py")): + module_path = path.relative_to("src").with_suffix("") + doc_path = path.relative_to("src", "mkdocstrings").with_suffix(".md") + full_doc_path = Path("reference", doc_path) + + nav[module_path.parts] = doc_path + + with mkdocs_gen_files.open(full_doc_path, "w") as f: + ident = ".".join(module_path.parts) print("::: " + ident, file=f) - mkdocs_gen_files.set_edit_path(doc_path, Path("..", path)) + mkdocs_gen_files.set_edit_path(full_doc_path, path) + +nav["mkdocs_autorefs", "references"] = "autorefs/references.md" +nav["mkdocs_autorefs", "plugin"] = "autorefs/plugin.md" + +with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: + nav_file.writelines(nav.build_literate_nav()) diff --git a/mkdocs.yml b/mkdocs.yml index 8a59f9e7..f3cbe277 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,37 +5,6 @@ repo_url: "https://github.com/mkdocstrings/mkdocstrings" edit_uri: "blob/master/docs/" repo_name: "mkdocstrings/mkdocstrings" -nav: -- Overview: index.md -- Usage: - - usage.md - - Theming: theming.md - - Handlers: - - handlers/overview.md - - Python: handlers/python.md - - Crystal: https://mkdocstrings.github.io/crystal/ - - Troubleshooting: troubleshooting.md -- Code Reference: - - mkdocstrings: - - handlers: - - base.py: reference/handlers/base.md - - rendering.py: reference/handlers/rendering.md - - python.py: reference/handlers/python.md - - extension.py: reference/extension.md - - inventory.py: reference/inventory.md - - loggers.py: reference/loggers.md - - plugin.py: reference/plugin.md - - mkdocs_autorefs: - - references.py: reference/autorefs/references.md - - plugin.py: reference/autorefs/plugin.md -- Contributing: - - contributing.md - - Code of Conduct: code_of_conduct.md - - Coverage report: coverage.md -- Changelog: changelog.md -- Credits: credits.md -- License: license.md - theme: name: material palette: @@ -64,6 +33,8 @@ plugins: scripts: - docs/gen_credits.py - docs/gen_doc_stubs.py +- literate-nav: + nav_file: SUMMARY.md - section-index - coverage: html_report_dir: build/coverage diff --git a/pyproject.toml b/pyproject.toml index ffb3a06d..bd2469b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,8 +61,9 @@ pep8-naming = "^0.11.1" # docs mkdocs-coverage = "^0.2.1" mkdocs-material = "^6.2.7" -mkdocs-gen-files = {version = "^0.3.0", markers = "python_version>='3.7'"} +mkdocs-gen-files = {version = "^0.3.2", markers = "python_version>='3.7'"} mkdocs-section-index = "^0.2.3" +mkdocs-literate-nav = "^0.3.1" [tool.poetry.plugins."mkdocs.plugins"] mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" From fb8c3037f4937de49527a9a146e209df0cdfb9eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 3 May 2021 23:56:46 +0200 Subject: [PATCH 17/50] docs: Add docstring style configuration instructions in 'Supported styles' section Issue #278: https://github.com/mkdocstrings/mkdocstrings/issues/278 PR #279: https://github.com/mkdocstrings/mkdocstrings/pull/279 --- docs/handlers/python.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/handlers/python.md b/docs/handlers/python.md index 6d0300dc..183142ae 100644 --- a/docs/handlers/python.md +++ b/docs/handlers/python.md @@ -108,6 +108,10 @@ It stands for *(Python) Take Docs*, and is supposed to be a pun on MkDocs (*Make ### Supported docstrings styles Right now, `pytkdocs` supports the Google-style, Numpy-style and reStructuredText-style docstring formats. +The style used by default is the Google-style. +You can configure what style you want to use with +the `docstring_style` and `docstring_options` [selection options](#selection), +both globally or per autodoc instruction. #### Google-style From 3838ba045532e4fe9ed360c492bd947601db7e00 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sat, 8 May 2021 20:52:17 +0200 Subject: [PATCH 18/50] tests: Adapt to changes in the link syntax in mkdocs-autorefs For PR https://github.com/mkdocstrings/autorefs/pull/7 PR #276: https://github.com/mkdocstrings/mkdocstrings/pull/276 --- tests/test_extension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_extension.py b/tests/test_extension.py index ed43a07b..fefd48ae 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -1,4 +1,5 @@ """Tests for the extension module.""" +import re import sys from collections import ChainMap from textwrap import dedent @@ -90,8 +91,7 @@ def test_keeps_preceding_text(ext_markdown): def test_reference_inside_autodoc(ext_markdown): """Assert cross-reference Markdown extension works correctly.""" output = ext_markdown.convert("::: tests.fixtures.cross_reference") - snippet = 'Link to something.Else.' - assert snippet in output + assert re.search(r"Link to <.*something\.Else.*>something\.Else<.*>\.", output) @pytest.mark.skipif(sys.version_info < (3, 8), reason="typing.Literal requires Python 3.8") From 3890ab597692e56d7ece576c166373b66ff4e615 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sat, 8 May 2021 21:04:03 +0200 Subject: [PATCH 19/50] refactor: Move writing extra files to an earlier stage in the build This step does not depend on anything that happens the HTML-rendering steps, only on the Markdown-rendering steps, so it's fine to move earlier. An example of why it's bad that the files do not exist during the HTML-rendering steps can be seen in a hypothetical commit https://github.com/oprypin/mkdocs/commit/a677dd7f591d3f6587cde0409a2b2d43cb889ade - basing the HTML content on the content of those files fails. PR #275: https://github.com/mkdocstrings/mkdocstrings/pull/275 --- src/mkdocstrings/plugin.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index 7fd67300..e88179b8 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -197,6 +197,20 @@ def inventory_enabled(self) -> bool: inventory_enabled = any(handler.enable_inventory for handler in self.handlers.seen_handlers) return inventory_enabled + def on_env(self, env, config: Config, **kwargs): + """Write mkdocstrings' extra files into the site dir. + + This is done in `on_env` because it's the only event that happens right after all Markdown rendering. + """ + if self._handlers: + css_content = "\n".join(handler.renderer.extra_css for handler in self.handlers.seen_handlers) + write_file(css_content.encode("utf-8"), os.path.join(config["site_dir"], self.css_filename)) + + if self.inventory_enabled: + log.debug("Creating inventory file objects.inv") + inv_contents = self.handlers.inventory.format_sphinx() + write_file(inv_contents, os.path.join(config["site_dir"], "objects.inv")) + def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 (unused arguments, cannot be static) """Teardown the handlers. @@ -213,15 +227,7 @@ def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 config: The MkDocs config object. kwargs: Additional arguments passed by MkDocs. """ - if self.handlers: - css_content = "\n".join(handler.renderer.extra_css for handler in self.handlers.seen_handlers) - write_file(css_content.encode("utf-8"), os.path.join(config["site_dir"], self.css_filename)) - - if self.inventory_enabled: - log.debug("Creating inventory file objects.inv") - inv_contents = self.handlers.inventory.format_sphinx() - write_file(inv_contents, os.path.join(config["site_dir"], "objects.inv")) - + if self._handlers: log.debug("Tearing handlers down") self.handlers.teardown() From fba729826c03475c7796eb0c89b9b22fd988ad69 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sat, 8 May 2021 21:05:10 +0200 Subject: [PATCH 20/50] chore: Also accept a higher version of mkdocs-autorefs PR #282: https://github.com/mkdocstrings/mkdocstrings/pull/282 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd2469b1..c52fcd5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ Jinja2 = "^2.11" Markdown = "^3.3" MarkupSafe = "^1.1" mkdocs = "^1.1" -mkdocs-autorefs = "^0.1" +mkdocs-autorefs = ">=0.1, <0.3" pymdown-extensions = ">=6.3, <9.0" pytkdocs = ">=0.2.0, <0.12.0" From e7301acfec5b43aa858df1f3088a3dae8c6afb6b Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sun, 9 May 2021 18:29:49 +0200 Subject: [PATCH 21/50] docs: Don't need the suggestion to upgrade Jinja if we ensure it (as a project dependency) PR #284: https://github.com/mkdocstrings/mkdocstrings/pull/284 --- docs/troubleshooting.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 20e88195..7bfa2163 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -135,29 +135,6 @@ Make sure the referenced object was both collected and rendered: verify your sel For false-positives, you can wrap the text in backticks (\`) to prevent `mkdocstrings` from trying to process it. -## WindowsPath object is not iterable - -If you get a traceback like this one: - -``` -... -File "c:\users\me\appdata\local\continuum\anaconda3\lib\site-packages\mkdocstrings\handlers\python.py", line 244, in get_handler - return PythonHandler(collector=PythonCollector(), renderer=PythonRenderer("python", theme)) -File "c:\users\me\appdata\local\continuum\anaconda3\lib\site-packages\mkdocstrings\handlers\__init__.py", line 124, in __init__ - self.env = Environment(autoescape=True, loader=FileSystemLoader(theme_dir)) -File "c:\users\me\appdata\local\continuum\anaconda3\lib\site-packages\jinja2\loaders.py", line 163, in __init__ - self.searchpath = list(searchpath) -TypeError: 'WindowsPath' object is not iterable -``` - -Try upgrading your installed version of Jinja2: - -``` -pip install -U jinja2 -``` - -Version 2.11.1 seems to be working fine. - --- ## Python specifics From 52bc0c359ae9f06a3d4f9645ebace237f752d57d Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sun, 9 May 2021 18:45:10 +0200 Subject: [PATCH 22/50] build: Support the upcoming major Jinja and MarkupSafe releases They mainly just drop Python 2. * https://jinja.palletsprojects.com/en/master/changes/#version-3-0-0 * https://markupsafe.palletsprojects.com/en/master/changes/#version-2-0-0 PR #283: https://github.com/mkdocstrings/mkdocstrings/pull/283 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c52fcd5b..bc8d1723 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,9 +16,9 @@ packages = [ { include = "mkdocstrings", from = "src" } ] [tool.poetry.dependencies] python = "^3.6" -Jinja2 = "^2.11" +Jinja2 = ">=2.11.1, <4.0" Markdown = "^3.3" -MarkupSafe = "^1.1" +MarkupSafe = ">=1.1, <3.0" mkdocs = "^1.1" mkdocs-autorefs = ">=0.1, <0.3" pymdown-extensions = ">=6.3, <9.0" From 572e9fa49513123c025917d27b7f8e73b47df1e9 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Wed, 12 May 2021 23:14:36 +0200 Subject: [PATCH 23/50] ci: Appease a type check (#286) --- docs/gen_credits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gen_credits.py b/docs/gen_credits.py index d626e220..29825601 100644 --- a/docs/gen_credits.py +++ b/docs/gen_credits.py @@ -29,7 +29,7 @@ def get_credits_data() -> dict: dependencies = direct_dependencies | indirect_dependencies packages = {} - for pkg in search_packages_info(dependencies): + for pkg in search_packages_info(sorted(dependencies)): pkg = {_: pkg[_] for _ in ("name", "home-page")} packages[pkg["name"].lower()] = pkg From 8b675f4671f8bbfd2f337ed043e3682b0a0ad0f6 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Thu, 13 May 2021 12:29:20 +0200 Subject: [PATCH 24/50] feat: Support loading external inventories and linking to them PR #277: https://github.com/mkdocstrings/mkdocstrings/pull/277 --- src/mkdocstrings/plugin.py | 62 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index e88179b8..f33f5bc8 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -12,8 +12,13 @@ during the [`on_serve` event hook](https://www.mkdocs.org/user-guide/plugins/#on_serve). """ +import collections +import concurrent.futures +import functools +import gzip import os -from typing import Callable, Optional, Tuple +import urllib.request +from typing import Any, BinaryIO, Callable, Iterable, List, Mapping, Optional, Tuple from livereload import Server from mkdocs.config import Config @@ -156,6 +161,13 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused else: theme_name = config["theme"].name + to_import: List[Tuple[str, Mapping[str, Any]]] = [] + for handler_name, conf in self.config["handlers"].items(): + for import_item in conf.pop("import", ()): + if isinstance(import_item, str): + import_item = {"url": import_item} + to_import.append((handler_name, import_item)) + extension_config = { "site_name": config["site_name"], "theme_name": theme_name, @@ -183,6 +195,16 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused config["extra_css"].insert(0, self.css_filename) # So that it has lower priority than user files. + self._inv_futures = [] + if to_import: + inv_loader = concurrent.futures.ThreadPoolExecutor(4) + for handler_name, import_item in to_import: + future = inv_loader.submit( + self._load_inventory, self.get_handler(handler_name).load_inventory, **import_item + ) + self._inv_futures.append(future) + inv_loader.shutdown(wait=False) + return config @property @@ -198,9 +220,10 @@ def inventory_enabled(self) -> bool: return inventory_enabled def on_env(self, env, config: Config, **kwargs): - """Write mkdocstrings' extra files into the site dir. + """Extra actions that need to happen after all Markdown rendering and before HTML rendering. - This is done in `on_env` because it's the only event that happens right after all Markdown rendering. + - Write mkdocstrings' extra files into the site dir. + - Gather results from background inventory download tasks. """ if self._handlers: css_content = "\n".join(handler.renderer.extra_css for handler in self.handlers.seen_handlers) @@ -211,6 +234,13 @@ def on_env(self, env, config: Config, **kwargs): inv_contents = self.handlers.inventory.format_sphinx() write_file(inv_contents, os.path.join(config["site_dir"], "objects.inv")) + if self._inv_futures: + log.debug(f"Waiting for {len(self._inv_futures)} inventory download(s)") + concurrent.futures.wait(self._inv_futures, timeout=30) + for k, v in collections.ChainMap(*(f.result() for f in self._inv_futures)).items(): + config["plugins"]["autorefs"].register_url(k, v) + self._inv_futures = [] + def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 (unused arguments, cannot be static) """Teardown the handlers. @@ -227,6 +257,9 @@ def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 config: The MkDocs config object. kwargs: Additional arguments passed by MkDocs. """ + for f in self._inv_futures: + f.cancel() + if self._handlers: log.debug("Tearing handlers down") self.handlers.teardown() @@ -241,3 +274,26 @@ def get_handler(self, handler_name: str) -> BaseHandler: An instance of a subclass of [`BaseHandler`][mkdocstrings.handlers.base.BaseHandler]. """ return self.handlers.get_handler(handler_name) + + @classmethod + @functools.lru_cache(maxsize=None) + def _load_inventory(cls, loader: Callable[..., Iterable[Tuple[str, str]]], url: str, **kwargs) -> Mapping[str, str]: + """Download and process inventory files using a handler. + + Arguments: + loader: A function returning a sequence of pairs (identifier, url). + url: The URL to download and process. + kwargs: Extra arguments to pass to the loader. + + Returns: + A mapping from identifier to absolute URL. + """ + log.debug(f"Downloading inventory from {url!r}") + req = urllib.request.Request(url, headers={"Accept-Encoding": "gzip"}) + with urllib.request.urlopen(req) as resp: # noqa: S310 (URL audit OK: comes from a checked-in config) + content: BinaryIO = resp + if "gzip" in resp.headers.get("content-encoding", ""): + content = gzip.GzipFile(fileobj=resp) # type: ignore[assignment] + result = dict(loader(content, url=url, **kwargs)) + log.debug(f"Loaded inventory from {url!r}: {len(result)} items") + return result From 5802b1ef5ad9bf6077974f777bd55f32ce2bc219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 16 May 2021 17:56:29 +0200 Subject: [PATCH 25/50] chore: Prepare release 0.15.1 --- CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9afdd62a..c20db246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.15.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.1) - 2021-05-16 + +[Compare with 0.15.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.0...0.15.1) + +### Bug Fixes +- Prevent error during parallel installations ([fac2c71](https://github.com/mkdocstrings/mkdocstrings/commit/fac2c711351f7b62bf5308f19cfc612a3944588a) by Timothée Mazzucotelli). + +### Packaging +- Support the upcoming major Jinja and MarkupSafe releases ([bb4f9de](https://github.com/mkdocstrings/mkdocstrings/commit/bb4f9de08a77bef85e550d70deb0db13e6aa0c96) by Oleh Prypin). [PR #283](https://github.com/mkdocstrings/mkdocstrings/pull/283) +- Accept a higher version of mkdocs-autorefs ([c8de08e](https://github.com/mkdocstrings/mkdocstrings/commit/c8de08e177f78290d3baaca2716d1ec64c9059b6) by Oleh Prypin). [PR #282](https://github.com/mkdocstrings/mkdocstrings/pull/282) + + ## [0.15.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.0) - 2021-02-28 [Compare with 0.14.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.14.0...0.15.0) diff --git a/pyproject.toml b/pyproject.toml index bc8d1723..a01a9993 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "mkdocstrings" -version = "0.15.0" +version = "0.15.1" description = "Automatic documentation from sources, for MkDocs." authors = ["Timothée Mazzucotelli "] license = "ISC License" From a8418cb4c6193d35cdc72508b118a712cf0334e1 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Thu, 27 May 2021 19:57:51 +0200 Subject: [PATCH 26/50] feat: Support loading external Python inventories in Sphinx format PR #287: https://github.com/mkdocstrings/mkdocstrings/pull/287 --- src/mkdocstrings/handlers/python.py | 27 ++++++++++++++- src/mkdocstrings/inventory.py | 53 +++++++++++++++++++++++++---- src/mkdocstrings/plugin.py | 2 +- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py index fc63138a..3c575c79 100644 --- a/src/mkdocstrings/handlers/python.py +++ b/src/mkdocstrings/handlers/python.py @@ -5,15 +5,17 @@ import json import os +import posixpath import sys import traceback from collections import ChainMap from subprocess import PIPE, Popen # noqa: S404 (what other option, more secure that PIPE do we have? sockets?) -from typing import Any, List, Optional +from typing import Any, BinaryIO, Iterator, List, Optional, Tuple from markdown import Markdown from mkdocstrings.handlers.base import BaseCollector, BaseHandler, BaseRenderer, CollectionError, CollectorItem +from mkdocstrings.inventory import Inventory from mkdocstrings.loggers import get_logger log = get_logger(__name__) @@ -246,6 +248,29 @@ class PythonHandler(BaseHandler): domain: str = "py" # to match Sphinx's default domain enable_inventory: bool = True + @classmethod + def load_inventory( + cls, in_file: BinaryIO, url: str, base_url: Optional[str] = None, **kwargs + ) -> Iterator[Tuple[str, str]]: + """Yield items and their URLs from an inventory file streamed from `in_file`. + + This implements mkdocstrings' `load_inventory` "protocal" (see plugin.py). + + Arguments: + in_file: The binary file-like object to read the inventory from. + url: The URL that this file is being streamed from (used to guess `base_url`). + base_url: The URL that this inventory's sub-paths are relative to. + **kwargs: Ignore additional arguments passed from the config. + + Yields: + Tuples of (item identifier, item URL). + """ + if base_url is None: + base_url = posixpath.dirname(url) + + for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values(): + yield item.name, posixpath.join(base_url, item.uri) + def get_handler( theme: str, # noqa: W0613 (unused argument config) diff --git a/src/mkdocstrings/inventory.py b/src/mkdocstrings/inventory.py index 2d12fa75..cfcb5676 100644 --- a/src/mkdocstrings/inventory.py +++ b/src/mkdocstrings/inventory.py @@ -3,15 +3,18 @@ # Credits to Brian Skinn and the sphobjinv project: # https://github.com/bskinn/sphobjinv +import re import zlib from textwrap import dedent -from typing import List, Optional +from typing import BinaryIO, Collection, List, Optional class InventoryItem: """Inventory item.""" - def __init__(self, name: str, domain: str, role: str, uri: str, priority: str = "1", dispname: str = "-"): + def __init__( + self, name: str, domain: str, role: str, uri: str, priority: str = "1", dispname: Optional[str] = None + ): """Initialize the object. Arguments: @@ -27,7 +30,7 @@ def __init__(self, name: str, domain: str, role: str, uri: str, priority: str = self.role: str = role self.uri: str = uri self.priority: str = priority - self.dispname: str = dispname + self.dispname: str = dispname or name def format_sphinx(self) -> str: """Format this item as a Sphinx inventory line. @@ -35,11 +38,28 @@ def format_sphinx(self) -> str: Returns: A line formatted for an `objects.inv` file. """ + dispname = self.dispname + if dispname == self.name: + dispname = "-" uri = self.uri - name_length = len(self.name) - if uri[-name_length - 1 :] == "#" + self.name: - uri = uri[:-name_length] + "$" - return f"{self.name} {self.domain}:{self.role} {self.priority} {uri} {self.dispname}" + if uri.endswith(self.name): + uri = uri[: -len(self.name)] + "$" + return f"{self.name} {self.domain}:{self.role} {self.priority} {uri} {dispname}" + + sphinx_item_regex = re.compile(r"^(.+?)\s+(\S+):(\S+)\s+(-?\d+)\s+(\S+)\s+(.*)$") + + @classmethod + def parse_sphinx(cls, line: str) -> "InventoryItem": + """Parse a line from a Sphinx v2 inventory file and return an `InventoryItem` from it.""" + m = cls.sphinx_item_regex.search(line) + if not m: + raise ValueError(line) + name, domain, role, priority, uri, dispname = m.groups() + if uri.endswith("$"): + uri = uri[:-1] + name + if dispname == "-": + dispname = name + return cls(name, domain, role, uri, priority, dispname) class Inventory(dict): @@ -91,3 +111,22 @@ def format_sphinx(self) -> bytes: lines = [item.format_sphinx().encode("utf8") for item in self.values()] return header + zlib.compress(b"\n".join(lines) + b"\n", 9) + + @classmethod + def parse_sphinx(cls, in_file: BinaryIO, *, domain_filter: Collection[str] = ()) -> "Inventory": + """Parse a Sphinx v2 inventory file and return an `Inventory` from it. + + Arguments: + in_file: The binary file-like object to read from. + domain_filter: A collection of domain values to allow (and filter out all other ones). + + Returns: + An `Inventory` containing the collected `InventoryItem`s. + """ + for _ in range(4): + in_file.readline() + lines = zlib.decompress(in_file.read()).splitlines() + items = [InventoryItem.parse_sphinx(line.decode("utf8")) for line in lines] + if domain_filter: + items = [item for item in items if item.domain in domain_filter] + return cls(items) diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index f33f5bc8..09abd62c 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -289,7 +289,7 @@ def _load_inventory(cls, loader: Callable[..., Iterable[Tuple[str, str]]], url: A mapping from identifier to absolute URL. """ log.debug(f"Downloading inventory from {url!r}") - req = urllib.request.Request(url, headers={"Accept-Encoding": "gzip"}) + req = urllib.request.Request(url, headers={"Accept-Encoding": "gzip", "User-Agent": "mkdocstrings/0.15.0"}) with urllib.request.urlopen(req) as resp: # noqa: S310 (URL audit OK: comes from a checked-in config) content: BinaryIO = resp if "gzip" in resp.headers.get("content-encoding", ""): From e8d657352aec2e519971ef70903c0cdb72bb4418 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Wed, 9 Jun 2021 21:58:05 +0200 Subject: [PATCH 27/50] refactor: Compatibility with MkDocs 1.2: livereload isn't guaranteed now MkDocs doesn't depend on 'livereload' library, and neither should we, but we import it for the type annotation, which is now also wrong. Also fix tests: `site_url` value is now required for MkDocs. PR #294: https://github.com/mkdocstrings/mkdocstrings/pull/294 --- pyproject.toml | 2 +- src/mkdocstrings/plugin.py | 11 +---------- tests/test_extension.py | 1 + 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a01a9993..4803e308 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ python = "^3.6" Jinja2 = ">=2.11.1, <4.0" Markdown = "^3.3" MarkupSafe = ">=1.1, <3.0" -mkdocs = "^1.1" +mkdocs = "^1.1.1" mkdocs-autorefs = ">=0.1, <0.3" pymdown-extensions = ">=6.3, <9.0" pytkdocs = ">=0.2.0, <0.12.0" diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index 09abd62c..0f192555 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -20,7 +20,6 @@ import urllib.request from typing import Any, BinaryIO, Callable, Iterable, List, Mapping, Optional, Tuple -from livereload import Server from mkdocs.config import Config from mkdocs.config.config_options import Type as MkType from mkdocs.plugins import BasePlugin @@ -111,7 +110,7 @@ def handlers(self) -> Handlers: raise RuntimeError("The plugin hasn't been initialized with a config yet") return self._handlers - def on_serve(self, server: Server, builder: Callable = None, **kwargs) -> Server: # noqa: W0613 (unused arguments) + def on_serve(self, server, builder: Callable, **kwargs): # noqa: W0613 (unused arguments) """Watch directories. Hook for the [`on_serve` event](https://www.mkdocs.org/user-guide/plugins/#on_serve). @@ -123,18 +122,10 @@ def on_serve(self, server: Server, builder: Callable = None, **kwargs) -> Server server: The `livereload` server instance. builder: The function to build the site. kwargs: Additional arguments passed by MkDocs. - - Returns: - The server instance. """ - if builder is None: - # The builder parameter was added in mkdocs v1.1.1. - # See issue https://github.com/mkdocs/mkdocs/issues/1952. - builder = list(server.watcher._tasks.values())[0]["func"] # noqa: W0212 (protected member) for element in self.config["watch"]: log.debug(f"Adding directory '{element}' to watcher") server.watch(element, builder) - return server def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused arguments) """Instantiate our Markdown extension. diff --git a/tests/test_extension.py b/tests/test_extension.py index fefd48ae..82d18001 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -24,6 +24,7 @@ def fixture_ext_markdown(request, tmp_path): conf_dict = { "site_name": "foo", + "site_url": "https://example.org/", "site_dir": str(tmp_path), "plugins": [{"mkdocstrings": {"default_handler": "python"}}], **getattr(request, "param", {}), From 87c55fb07cb54e9899c4dee7070da38d6a9ce797 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Wed, 9 Jun 2021 23:25:32 +0200 Subject: [PATCH 28/50] chore: Prepare release 0.15.2 (#296) --- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c20db246..f5ad7112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.15.2](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.2) - 2021-06-09 + +[Compare with 0.15.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.1...0.15.2) + +### Packaging +- MkDocs default schema needs to be obtained differently now ([b3e122b](https://github.com/mkdocstrings/mkdocstrings/commit/b3e122b36d586632738ddedaed7d3df8d5dead44) by Oleh Prypin). [PR #273](https://github.com/mkdocstrings/mkdocstrings/pull/273) +- Compatibility with MkDocs 1.2: livereload isn't guaranteed now ([36e8024](https://github.com/mkdocstrings/mkdocstrings/commit/36e80248d2ab9e61975f6c83ae517115c9410fc1) by Oleh Prypin). [PR #294](https://github.com/mkdocstrings/mkdocstrings/pull/294) + + ## [0.15.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.1) - 2021-05-16 [Compare with 0.15.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.0...0.15.1) diff --git a/pyproject.toml b/pyproject.toml index 4803e308..5ac03582 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "mkdocstrings" -version = "0.15.1" +version = "0.15.2" description = "Automatic documentation from sources, for MkDocs." authors = ["Timothée Mazzucotelli "] license = "ISC License" From 83c320f1cfe6ca19e1350e3b5340fa56e733ff6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 20 Aug 2021 11:13:16 +0200 Subject: [PATCH 29/50] docs: Fix credits building, stop using HTTPX --- docs/gen_credits.py | 21 ++++----------------- duties.py | 5 +++-- pyproject.toml | 1 - 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/docs/gen_credits.py b/docs/gen_credits.py index 29825601..3aa4f22a 100644 --- a/docs/gen_credits.py +++ b/docs/gen_credits.py @@ -1,13 +1,12 @@ import functools from itertools import chain from pathlib import Path +from urllib import request -import httpx import mkdocs_gen_files import toml from jinja2 import StrictUndefined from jinja2.sandbox import SandboxedEnvironment -from pip._internal.commands.show import search_packages_info # noqa: WPS436 (no other way?) def get_credits_data() -> dict: @@ -26,24 +25,12 @@ def get_credits_data() -> dict: direct_dependencies.remove("python") indirect_dependencies = {pkg["name"].lower() for pkg in lock_data["package"]} indirect_dependencies -= direct_dependencies - dependencies = direct_dependencies | indirect_dependencies - - packages = {} - for pkg in search_packages_info(sorted(dependencies)): - pkg = {_: pkg[_] for _ in ("name", "home-page")} - packages[pkg["name"].lower()] = pkg - - # all packages might not be credited, - # like the ones that are now part of the standard library - # or the ones that are only used on other operating systems, - # and therefore are not installed, - # but it's not that important return { "project_name": project_name, "direct_dependencies": sorted(direct_dependencies), "indirect_dependencies": sorted(indirect_dependencies), - "package_info": packages, + "more_credits": "http://pawamoy.github.io/credits/", } @@ -55,10 +42,10 @@ def get_credits(): The credits page Markdown. """ jinja_env = SandboxedEnvironment(undefined=StrictUndefined) - commit = "166758a98d5e544aaa94fda698128e00733497f4" + commit = "c78c29caa345b6ace19494a98b1544253cbaf8c1" template_url = f"https://raw.githubusercontent.com/pawamoy/jinja-templates/{commit}/credits.md" template_data = get_credits_data() - template_text = httpx.get(template_url).text + template_text = request.urlopen(template_url).read().decode("utf8") # noqa: S310 return jinja_env.from_string(template_text).render(**template_data) diff --git a/duties.py b/duties.py index 32a8d7ba..1e42fc65 100644 --- a/duties.py +++ b/duties.py @@ -6,8 +6,8 @@ from pathlib import Path from shutil import which from typing import List, Optional, Pattern +from urllib import request -import httpx from duty import duty from git_changelog.build import Changelog, Version from jinja2.sandbox import SandboxedEnvironment @@ -94,7 +94,8 @@ def update_changelog( commit_style: The style of commit messages to parse. """ env = SandboxedEnvironment(autoescape=False) - template = env.from_string(httpx.get(template_url).text) + template_text = request.urlopen(template_url).read().decode("utf8") # noqa: S310 + template = env.from_string(template_text) changelog = Changelog(".", style=commit_style) if len(changelog.versions_list) == 1: diff --git a/pyproject.toml b/pyproject.toml index 5ac03582..06e08c36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ pytest-xdist = "^2.2.0" # tasks duty = "^0.6.0" git-changelog = "^0.4.2" -httpx = "^0.16.1" toml = "^0.10.2" # flake8 plugins From 436f5504ad72ab6d1f5b4303e6b68bc84562c32b Mon Sep 17 00:00:00 2001 From: Brian Koropoff Date: Fri, 20 Aug 2021 02:34:11 -0700 Subject: [PATCH 30/50] feat: Add option to show Python base classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timothée Mazzucotelli Issue #269: https://github.com/mkdocstrings/mkdocstrings/issues/269 PR #297: https://github.com/mkdocstrings/mkdocstrings/pull/297 --- pyproject.toml | 4 ++-- src/mkdocstrings/handlers/python.py | 9 +++++++++ src/mkdocstrings/templates/python/material/class.html | 9 ++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 06e08c36..f1f345bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,9 +20,9 @@ Jinja2 = ">=2.11.1, <4.0" Markdown = "^3.3" MarkupSafe = ">=1.1, <3.0" mkdocs = "^1.1.1" -mkdocs-autorefs = ">=0.1, <0.3" +mkdocs-autorefs = ">=0.1, <0.4" pymdown-extensions = ">=6.3, <9.0" -pytkdocs = ">=0.2.0, <0.12.0" +pytkdocs = ">=0.2.0, <0.13.0" [tool.poetry.dev-dependencies] # formatting, quality, tests diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py index 3c575c79..50a8bc06 100644 --- a/src/mkdocstrings/handlers/python.py +++ b/src/mkdocstrings/handlers/python.py @@ -13,6 +13,7 @@ from typing import Any, BinaryIO, Iterator, List, Optional, Tuple from markdown import Markdown +from markupsafe import Markup from mkdocstrings.handlers.base import BaseCollector, BaseHandler, BaseRenderer, CollectionError, CollectorItem from mkdocstrings.inventory import Inventory @@ -45,6 +46,7 @@ class PythonRenderer(BaseRenderer): "show_if_no_docstring": False, "show_signature_annotations": False, "show_source": True, + "show_bases": True, "group_by_category": True, "heading_level": 2, } @@ -61,6 +63,7 @@ class PythonRenderer(BaseRenderer): **`show_if_no_docstring`** | `bool` | Show the object heading even if it has no docstring or children with docstrings. | `False` **`show_signature_annotations`** | `bool` | Show the type annotations in methods and functions signatures. | `False` **`show_source`** | `bool` | Show the source code of this object. | `True` + **`show_bases`** | `bool` | Show the base classes of a class. | `True` **`group_by_category`** | `bool` | Group the object's children by categories: attributes, classes, functions, methods, and modules. | `True` **`heading_level`** | `int` | The initial heading level to use. | `2` """ # noqa: E501 @@ -87,6 +90,12 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore self.env.trim_blocks = True self.env.lstrip_blocks = True self.env.keep_trailing_newline = False + self.env.filters["brief_xref"] = self.do_brief_xref + + def do_brief_xref(self, path: str) -> Markup: + """Filter to create cross-reference with brief text and full identifier as hover text.""" + brief = path.split(".")[-1] + return Markup("{brief}").format(path=path, brief=brief) class PythonCollector(BaseCollector): diff --git a/src/mkdocstrings/templates/python/material/class.html b/src/mkdocstrings/templates/python/material/class.html index cdf45484..414b6eb7 100644 --- a/src/mkdocstrings/templates/python/material/class.html +++ b/src/mkdocstrings/templates/python/material/class.html @@ -22,7 +22,14 @@ class="doc doc-heading", toc_label=class.name) %} - {% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %} + + {% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %} + {% if config.show_bases and class.bases != ['object'] %} + ({% for base in class.bases -%} + {{ base|brief_xref() }}{% if not loop.last %}, {% endif %} + {% endfor %}) + {% endif %} + {% with properties = class.properties %} {% include "properties.html" with context %} From b1fff8b8ef4d6d77417fc43ed8be4b578d6437e4 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Fri, 20 Aug 2021 15:42:33 +0100 Subject: [PATCH 31/50] feat: Add a rendering option to change the sorting of members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #114: https://github.com/mkdocstrings/mkdocstrings/issues/114 PR #274: https://github.com/mkdocstrings/mkdocstrings/pull/274 Co-authored-by: Timothée Mazzucotelli --- src/mkdocstrings/handlers/python.py | 48 ++++++++++- .../templates/python/material/children.html | 12 +-- tests/test_python_handler.py | 81 +++++++++++++++++++ 3 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 tests/test_python_handler.py diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py index 50a8bc06..73061b91 100644 --- a/src/mkdocstrings/handlers/python.py +++ b/src/mkdocstrings/handlers/python.py @@ -10,11 +10,12 @@ import traceback from collections import ChainMap from subprocess import PIPE, Popen # noqa: S404 (what other option, more secure that PIPE do we have? sockets?) -from typing import Any, BinaryIO, Iterator, List, Optional, Tuple +from typing import Any, BinaryIO, Callable, Iterator, List, Optional, Tuple from markdown import Markdown from markupsafe import Markup +from mkdocstrings.extension import PluginError from mkdocstrings.handlers.base import BaseCollector, BaseHandler, BaseRenderer, CollectionError, CollectorItem from mkdocstrings.inventory import Inventory from mkdocstrings.loggers import get_logger @@ -49,6 +50,7 @@ class PythonRenderer(BaseRenderer): "show_bases": True, "group_by_category": True, "heading_level": 2, + "members_order": "alphabetical", } """The default rendering options. @@ -66,6 +68,7 @@ class PythonRenderer(BaseRenderer): **`show_bases`** | `bool` | Show the base classes of a class. | `True` **`group_by_category`** | `bool` | Group the object's children by categories: attributes, classes, functions, methods, and modules. | `True` **`heading_level`** | `int` | The initial heading level to use. | `2` + **`members_order`** | `str` | The members ordering to use. Options: `alphabetical` - order by the members names, `source` - order members as they appear in the source file. | `alphabetical` """ # noqa: E501 def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignore missing docstring) @@ -77,6 +80,16 @@ def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignor # of the rendering recursion. Therefore, it's easier to use it as a plain value # than as an item in a dictionary. heading_level = final_config["heading_level"] + members_order = final_config["members_order"] + + if members_order == "alphabetical": + sort_function = _sort_key_alphabetical + elif members_order == "source": + sort_function = _sort_key_source + else: + raise PluginError(f"Unknown members_order '{members_order}', choose between 'alphabetical' and 'source'.") + + sort_object(data, sort_function=sort_function) return template.render( **{"config": final_config, data["category"]: data, "heading_level": heading_level, "root": True}, @@ -324,3 +337,36 @@ def rebuild_category_lists(obj: dict) -> None: obj["children"] = [child for _, child in obj["children"].items()] for child in obj["children"]: rebuild_category_lists(child) + + +def sort_object(obj: CollectorItem, sort_function: Callable[[CollectorItem], Any]) -> None: + """Sort the collected object's children. + + Sorts the object's children list, then each category separately, and then recurses into each. + + Arguments: + obj: The collected object, as a dict. Note that this argument is mutated. + sort_function: The sort key function used to determine the order of elements. + """ + obj["children"].sort(key=sort_function) + + for category in ("attributes", "classes", "functions", "methods", "modules"): + obj[category].sort(key=sort_function) + + for child in obj["children"]: + sort_object(child, sort_function=sort_function) + + +def _sort_key_alphabetical(item: CollectorItem) -> Any: + """Return a sort key for 'alphabetical' sorting of CollectorItems.""" + # chr(sys.maxunicode) is a string that contains the final unicode + # character, so if 'name' isn't found on the object, the item will go to + # the end of the list. + return item.get("name", chr(sys.maxunicode)) + + +def _sort_key_source(item: CollectorItem) -> Any: + """Return a sort key for 'source' sorting of CollectorItems.""" + # if 'line_start' isn't found on the object, the item will go to + # the start of the list. + return item.get("source", {}).get("line_start", -1) diff --git a/src/mkdocstrings/templates/python/material/children.html b/src/mkdocstrings/templates/python/material/children.html index 967ad493..7bc56c2d 100644 --- a/src/mkdocstrings/templates/python/material/children.html +++ b/src/mkdocstrings/templates/python/material/children.html @@ -17,7 +17,7 @@ {% filter heading(heading_level, id=html_id ~ "-attributes") %}Attributes{% endfilter %} {% endif %} {% with heading_level = heading_level + extra_level %} - {% for attribute in obj.attributes|sort(attribute="name") %} + {% for attribute in obj.attributes %} {% include "attribute.html" with context %} {% endfor %} {% endwith %} @@ -26,7 +26,7 @@ {% filter heading(heading_level, id=html_id ~ "-classes") %}Classes{% endfilter %} {% endif %} {% with heading_level = heading_level + extra_level %} - {% for class in obj.classes|sort(attribute="name") %} + {% for class in obj.classes %} {% include "class.html" with context %} {% endfor %} {% endwith %} @@ -35,7 +35,7 @@ {% filter heading(heading_level, id=html_id ~ "-functions") %}Functions{% endfilter %} {% endif %} {% with heading_level = heading_level + extra_level %} - {% for function in obj.functions|sort(attribute="name") %} + {% for function in obj.functions %} {% include "function.html" with context %} {% endfor %} {% endwith %} @@ -44,7 +44,7 @@ {% filter heading(heading_level, id=html_id ~ "-methods") %}Methods{% endfilter %} {% endif %} {% with heading_level = heading_level + extra_level %} - {% for method in obj.methods|sort(attribute="name") %} + {% for method in obj.methods %} {% include "method.html" with context %} {% endfor %} {% endwith %} @@ -53,7 +53,7 @@ {% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %} {% endif %} {% with heading_level = heading_level + extra_level %} - {% for module in obj.modules|sort(attribute="name") %} + {% for module in obj.modules %} {% include "module.html" with context %} {% endfor %} {% endwith %} @@ -62,7 +62,7 @@ {% else %} - {% for child in obj.children|sort(attribute="name") %} + {% for child in obj.children %} {% if child.category == "attribute" %} {% with attribute = child %} {% include "attribute.html" with context %} diff --git a/tests/test_python_handler.py b/tests/test_python_handler.py new file mode 100644 index 00000000..565e7527 --- /dev/null +++ b/tests/test_python_handler.py @@ -0,0 +1,81 @@ +"""Tests for the handlers.python module.""" + +from copy import deepcopy + +from mkdocstrings.handlers.python import _sort_key_alphabetical, _sort_key_source, rebuild_category_lists, sort_object + + +def test_members_order(): + """Assert that members sorting functions work correctly.""" + subcategories = {key: [] for key in ("attributes", "classes", "functions", "methods", "modules")} + categories = {"children": {}, **subcategories} + collected = { + "name": "root", + "children": { + "b": {"name": "b", "source": {"line_start": 0}, **categories}, + "a": {"name": "a", **categories}, + "z": {"name": "z", "source": {"line_start": 100}, **categories}, + "no_name": {"source": {"line_start": 10}, **categories}, + "c": { + "name": "c", + "source": {"line_start": 30}, + "children": { + "z": {"name": "z", "source": {"line_start": 200}, **categories}, + "a": {"name": "a", "source": {"line_start": 20}, **categories}, + }, + **subcategories, + }, + }, + "attributes": ["b", "c", "no_name", "z", "a"], + "classes": [], + "functions": [], + "methods": [], + "modules": [], + } + rebuild_category_lists(collected) + alphebetical = deepcopy(collected) + sort_object(alphebetical, _sort_key_alphabetical) + + rebuilt_categories = {"children": [], **subcategories} + assert ( + alphebetical["children"] + == alphebetical["attributes"] + == [ + {"name": "a", **rebuilt_categories}, + {"name": "b", "source": {"line_start": 0}, **rebuilt_categories}, + { + "name": "c", + "source": {"line_start": 30}, + "children": [ + {"name": "a", "source": {"line_start": 20}, **rebuilt_categories}, + {"name": "z", "source": {"line_start": 200}, **rebuilt_categories}, + ], + **subcategories, + }, + {"name": "z", "source": {"line_start": 100}, **rebuilt_categories}, + {"source": {"line_start": 10}, **rebuilt_categories}, + ] + ) + + source = deepcopy(collected) + sort_object(source, _sort_key_source) + + assert ( + source["children"] + == source["attributes"] + == [ + {"name": "a", **rebuilt_categories}, + {"name": "b", "source": {"line_start": 0}, **rebuilt_categories}, + {"source": {"line_start": 10}, **rebuilt_categories}, + { + "name": "c", + "source": {"line_start": 30}, + "children": [ + {"name": "a", "source": {"line_start": 20}, **rebuilt_categories}, + {"name": "z", "source": {"line_start": 200}, **rebuilt_categories}, + ], + **subcategories, + }, + {"name": "z", "source": {"line_start": 100}, **rebuilt_categories}, + ] + ) From d3c998eea70682f0efe23a51e6200482e1561a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Wed, 25 Aug 2021 18:37:02 +0200 Subject: [PATCH 32/50] docs: Make it clear Numpy docstrings need an extra --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c23dd84c..78d54fc9 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,8 @@ Automatic documentation from sources, for [MkDocs](https://mkdocs.org/). next to the object signature by *mkdocstrings*. - **Multiple docstring-styles support:** almost complete support for Google-style, Numpy-style, - and reStructuredText-style docstrings. *Note: only RST **style** is supported, not the whole markup.* + and reStructuredText-style docstrings. *Notes: only RST **style** is supported, not the whole markup. + Numpy-style requires an extra dependency from `pytkdocs`: `pytkdocs[numpy-style]`.* - **Admonition support in docstrings:** blocks like `Note:` or `Warning:` will be transformed to their [admonition](https://squidfunk.github.io/mkdocs-material/extensions/admonition/) equivalent. @@ -129,7 +130,7 @@ pip install mkdocs-material With `pip`: ```bash -python3.6 -m pip install mkdocstrings +pip install mkdocstrings ``` With `conda`: @@ -137,6 +138,12 @@ With `conda`: conda install -c conda-forge mkdocstrings ``` +Note for Python: you'll need an extra dependency to parse Numpy-style docstrings: + +``` +pip install pytkdocs[numpy-style] +``` + ## Quick usage ```yaml From 406334d451de08a809c04865aa993aa6fa2a197e Mon Sep 17 00:00:00 2001 From: Victor B <39555268+victorbnl@users.noreply.github.com> Date: Thu, 26 Aug 2021 23:01:23 +0200 Subject: [PATCH 33/50] docs: Add more space at the bottom PR #312: https://github.com/mkdocstrings/mkdocstrings/pull/312 --- docs/css/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/css/style.css b/docs/css/style.css index 27265bb0..b6bd0a29 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -15,3 +15,8 @@ a.external:hover::after, a.md-nav__link[href^="https:"]:hover::after { content: ' '; display: inline-block; } + +/* More space at the bottom of the page */ +.md-main__inner { + margin-bottom: 1.5rem; +} From 21069979a00ce48abc60a76aee0a0fec7a9205c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 22 Aug 2021 16:15:20 +0200 Subject: [PATCH 34/50] docs: Document loading and generating inventories --- docs/usage.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 2 ++ 2 files changed, 83 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index ce8c0bef..aba0ac7c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -112,6 +112,8 @@ The above is equivalent to: The path is relative to the docs directory. See [Theming](theming.md). - `handlers`: the handlers global configuration. +- `enable_inventory`: whether to enable inventory file generation. + See [Cross-references to other projects / inventories](#cross-references-to-other-projects-inventories) Example: @@ -250,6 +252,85 @@ The above tip about [Finding out the anchor](#finding-out-the-anchor) also appli You may also notice that such a heading does not get rendered as a `

` element directly, but rather the level gets shifted to fit the encompassing document structure. If you're curious about the implementation, check out [mkdocstrings.handlers.rendering.HeadingShiftingTreeprocessor][] and others. +### Cross-references to other projects / inventories + +!!! tip "New in version 0.16." + +Python developers coming from Sphinx might know about its `intersphinx` extension, +that allows to cross-reference items between several projects. +*mkdocstrings* has a similar feature. + +To reference an item from another project, you must first tell *mkdocstrings* +to load the inventory it provides. Each handler will be responsible of loading +inventories specific to its language. For example, the Python handler +can load Sphinx-generated inventories (`objects.inv`). + +In the following snippet, we load the inventory provided by `requests`: + +```yaml +plugins: +- mkdocstrings: + handlers: + python: + import: + - https://docs.python-requests.org/en/master/objects.inv +``` + +Now it is possible to cross-reference `requests`' items! For example: + +=== "Markdown" + ```md + See [requests.request][] to know what parameters you can pass. + ``` + +=== "Result (HTML)" + ```html +

See requests.request + to know what parameters you can pass.

+ ``` + +=== "Result (displayed)" + See [requests.request][] to know what parameters you can pass. + +You can of course select another version of the inventory, for example: + +```yaml +plugins: +- mkdocstrings: + handlers: + python: + import: + - https://docs.python-requests.org/en/v3.0.0/objects.inv +``` + +In case the inventory file is not served under the base documentation URL, +you can explicitly specify both URLs: + +```yaml +plugins: +- mkdocstrings: + handlers: + python: + import: + - url: https://cdn.example.com/version/objects.inv + base_url: https://docs.example.com/version +``` + +Absolute URLs to cross-referenced items will then be based +on `https://docs.example.com/version/` instead of `https://cdn.example.com/version/`. + +Reciprocally, *mkdocstrings* also allows to *generate* an inventory file in the Sphinx format. +It will be enabled by default if the Python handler is used, and generated as `objects.inv` in the final site directory. +Other projects will be able to cross-reference items from your project! + +To explicitely enable or disable the generation of the inventory file, use the global +`enable_inventory` option: + +```yaml +plugins: +- mkdocstrings: + enable_inventory: false +``` ## Watch directories diff --git a/mkdocs.yml b/mkdocs.yml index f3cbe277..b25d8041 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,5 +46,7 @@ plugins: - sys.path.append("docs") selection: new_path_syntax: yes + import: # demonstration purpose in the docs + - https://docs.python-requests.org/en/master/objects.inv watch: - src/mkdocstrings From 5c961e7c95650c2a6a95e060fc41a3bd346f1cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 22 Aug 2021 16:15:38 +0200 Subject: [PATCH 35/50] docs: Fix various small things --- docs/handlers/python.md | 2 +- docs/usage.md | 2 +- src/mkdocstrings/handlers/python.py | 2 +- src/mkdocstrings/plugin.py | 3 +++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/handlers/python.md b/docs/handlers/python.md index 183142ae..529f9e9c 100644 --- a/docs/handlers/python.md +++ b/docs/handlers/python.md @@ -249,7 +249,7 @@ Type annotations are read both in the code and in the docstrings. ::: snippets.function_annotations_rst:my_function selection: - docstring_style: "restructured-text" + docstring_style: "restructured-text" rendering: show_root_heading: no show_root_toc_entry: no diff --git a/docs/usage.md b/docs/usage.md index aba0ac7c..703dcab7 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -165,7 +165,7 @@ Any item that was inserted using the [autodoc syntax](#autodoc-syntax) cross-reference syntax (`[example][full.path.object1]`). But the cross-references are also applicable to the items' children that get pulled in. -#### Finding out the anchor +### Finding out the anchor If you're not sure which exact identifier a doc item uses, you can look at its "anchor", which your Web browser will show in the URL bar when clicking an item's entry in the table of contents. diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py index 73061b91..644a6cea 100644 --- a/src/mkdocstrings/handlers/python.py +++ b/src/mkdocstrings/handlers/python.py @@ -276,7 +276,7 @@ def load_inventory( ) -> Iterator[Tuple[str, str]]: """Yield items and their URLs from an inventory file streamed from `in_file`. - This implements mkdocstrings' `load_inventory` "protocal" (see plugin.py). + This implements mkdocstrings' `load_inventory` "protocol" (see plugin.py). Arguments: in_file: The binary file-like object to read the inventory from. diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index 0f192555..677fbb07 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -44,6 +44,7 @@ class MkdocstringsPlugin(BasePlugin): This plugin defines the following event hooks: - `on_config` + - `on_env` - `on_post_build` - `on_serve` @@ -213,6 +214,8 @@ def inventory_enabled(self) -> bool: def on_env(self, env, config: Config, **kwargs): """Extra actions that need to happen after all Markdown rendering and before HTML rendering. + Hook for the [`on_env` event](https://www.mkdocs.org/user-guide/plugins/#on_env). + - Write mkdocstrings' extra files into the site dir. - Gather results from background inventory download tasks. """ From 6348d3bdcdbc54812f08c90f82e50b508bf5cfc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 18:31:12 +0200 Subject: [PATCH 36/50] docs: Add another note about Numpy extra --- docs/handlers/python.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/handlers/python.md b/docs/handlers/python.md index 529f9e9c..967bbd58 100644 --- a/docs/handlers/python.md +++ b/docs/handlers/python.md @@ -212,6 +212,16 @@ Type annotations are read both in the code and in the docstrings. #### Numpy-style +!!! important "Extra dependency required" + You'll need an extra dependency to parse Numpy-style docstrings: + + ``` + pdm add -d --group docs 'pytkdocs[numpy-style]' + poetry add -D 'pytkdocs[numpy-style]' + pip install 'pytkdocs[numpy-style]' + # etc. + ``` + You can see examples of Numpy-style docstrings in [numpydoc's documentation](https://numpydoc.readthedocs.io/en/latest/format.html). From 1f9ca3be613bea60619bf0ee20eff68dbe968395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 18:31:27 +0200 Subject: [PATCH 37/50] docs: Set dark and light palettes --- mkdocs.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index b25d8041..c5d812fc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,9 +8,20 @@ repo_name: "mkdocstrings/mkdocstrings" theme: name: material palette: - scheme: preference + - media: "(prefers-color-scheme: light)" + scheme: default primary: teal accent: purple + toggle: + icon: material/weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: lime + toggle: + icon: material/weather-night + name: Switch to light mode extra_css: - css/style.css From 2251047e62a1c07d752e9ec1a58828de53ab5a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 19:21:48 +0200 Subject: [PATCH 38/50] docs: Fix link to docs section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78d54fc9..cb207ff0 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Automatic documentation from sources, for [MkDocs](https://mkdocs.org/). *any* Markdown heading into the global referencing scheme. **Note**: in versions prior to 0.15 *all* Markdown headers were included, but now you need to - [opt in](https://mkdocstrings.github.io/usage/#cross-references). + [opt in](https://mkdocstrings.github.io/usage/#cross-references-to-any-markdown-heading). - [**Inline injection in Markdown:**](https://mkdocstrings.github.io/usage/) instead of generating Markdown files, *mkdocstrings* allows you to inject From 96168d36a9f0e28533741c3d639c3d12132d4cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 19:21:59 +0200 Subject: [PATCH 39/50] chore: Update dependencies --- pyproject.toml | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f1f345bd..0cd4df13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"] packages = [ { include = "mkdocstrings", from = "src" } ] [tool.poetry.dependencies] -python = "^3.6" +python = "^3.6.2" Jinja2 = ">=2.11.1, <4.0" Markdown = "^3.3" MarkupSafe = ">=1.1, <3.0" @@ -27,42 +27,40 @@ pytkdocs = ">=0.2.0, <0.13.0" [tool.poetry.dev-dependencies] # formatting, quality, tests autoflake = "^1.4" -black = "^20.8b1" -isort = "^5.7.0" -mypy = "^0.812" -pytest = "^6.2.2" -pytest-cov = "^2.11.1" -# TODO: remove constraint when this issue is resolved in Poetry: -# https://github.com/pytest-dev/pytest-randomly/issues/335 -pytest-randomly = "<3.6.0" +black = "^21.9b0" +isort = "^5.9.3" +mypy = "^0.910" +pytest = "^6.2.5" +pytest-cov = "^2.12.1" +pytest-randomly = "^3.10.1" pytest-sugar = "^0.9.4" -pytest-xdist = "^2.2.0" +pytest-xdist = "^2.3.0" # tasks -duty = "^0.6.0" +duty = "^0.7.0" git-changelog = "^0.4.2" toml = "^0.10.2" # flake8 plugins -darglint = "^1.5.8" +darglint = "^1.8.0" flake8-bandit = "^2.1.2" -flake8-black = "^0.2.1" -flake8-bugbear = "^20.11.1" +flake8-black = "^0.2.3" +flake8-bugbear = "^21.9.1" flake8-builtins = "^1.5.3" -flake8-comprehensions = "^3.3.1" -flake8-docstrings = "^1.5.0" -flake8-pytest-style = "^1.3.0" +flake8-comprehensions = "^3.6.1" +flake8-docstrings = "^1.6.0" +flake8-pytest-style = "^1.5.0" flake8-string-format = "^0.3.0" -flake8-tidy-imports = "^4.2.1" +flake8-tidy-imports = "^4.4.1" flake8-variables-names = "^0.0.4" -pep8-naming = "^0.11.1" +pep8-naming = "^0.12.1" # docs -mkdocs-coverage = "^0.2.1" -mkdocs-material = "^6.2.7" -mkdocs-gen-files = {version = "^0.3.2", markers = "python_version>='3.7'"} -mkdocs-section-index = "^0.2.3" -mkdocs-literate-nav = "^0.3.1" +mkdocs-coverage = "^0.2.4" +mkdocs-material = "^7.2.8" +mkdocs-gen-files = {version = "^0.3.3", markers = "python_version>='3.7'"} +mkdocs-section-index = "^0.3.1" +mkdocs-literate-nav = "^0.4.0" [tool.poetry.plugins."mkdocs.plugins"] mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" From b8b09d8124116ba62726fe96edf69e54cfca6394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 19:23:36 +0200 Subject: [PATCH 40/50] chore: Prepare switch to PDM --- .copier-answers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 057f9978..d7c772db 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,6 +1,6 @@ # Changes here will be overwritten by Copier -_commit: 0.3.1 -_src_path: gh:pawamoy/copier-poetry +_commit: e9e472d +_src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: "Timoth\xE9e Mazzucotelli" author_username: pawamoy From 824b20e026fbecb26b4f9fadd1823f7453d510cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 20:31:11 +0200 Subject: [PATCH 41/50] chore: Switch to PDM template --- .copier-answers.yml | 2 +- .github/workflows/ci.yml | 78 ++++++------- .gitignore | 5 +- CONTRIBUTING.md | 12 +- Makefile | 2 +- config/coverage.ini | 25 ++-- config/flake8.ini | 58 +++++++++- config/mypy.ini | 3 + docs/css/material.css | 4 + docs/css/mkdocstrings.css | 6 + docs/css/style.css | 7 -- docs/gen_credits.py | 26 +++-- docs/{gen_doc_stubs.py => gen_ref_nav.py} | 10 +- duties.py | 129 ++++++++++----------- mkdocs.yml | 43 ++++++- pyproject.toml | 133 +++++++++++++--------- scripts/multirun.sh | 15 +-- scripts/setup.sh | 18 ++- src/mkdocstrings/py.typed | 0 19 files changed, 341 insertions(+), 235 deletions(-) create mode 100644 docs/css/material.css create mode 100644 docs/css/mkdocstrings.css rename docs/{gen_doc_stubs.py => gen_ref_nav.py} (72%) create mode 100644 src/mkdocstrings/py.typed diff --git a/.copier-answers.yml b/.copier-answers.yml index d7c772db..f844b711 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: e9e472d +_commit: 0.4.3 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: "Timoth\xE9e Mazzucotelli" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 385ddd4a..7b5ec9be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,14 +16,6 @@ env: PYTHONIOENCODING: UTF-8 PYTHONPATH: docs - # To fix an error when running Poetry on Windows - # (https://github.com/python-poetry/poetry/issues/2629), - # we set Poetry's cache directory to .poetry_cache in the current directory. - # It makes it easier to later remove the virtualenv when it's broken. - # Absolute path is necessary to avoid this issue: - # https://github.com/python-poetry/poetry/issues/3049 - POETRY_CACHE_DIR: ${{ github.workspace }}/.poetry_cache - jobs: quality: @@ -34,39 +26,44 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - name: Set up PDM + uses: pdm-project/setup-pdm@v2 with: python-version: 3.8 - - name: Set up Poetry - run: pip install poetry + - name: Set cache variables + id: set_variables + run: | + echo "::set-output name=PIP_CACHE::$(pip cache dir)" + echo "::set-output name=PDM_CACHE::$(pdm config cache_dir)" - - name: Set up the cache - uses: actions/cache@v1 + - name: Set up cache + uses: actions/cache@v2 with: - path: .poetry_cache - key: quality-poetry-cache + path: | + ${{ steps.set_variables.outputs.PIP_CACHE }} + ${{ steps.set_variables.outputs.PDM_CACHE }} + key: checks-cache - - name: Set up the project - run: poetry install -vv + - name: Resolving dependencies + run: pdm lock - - name: Check if the documentation builds correctly + - name: Install dependencies run: | - mkdir -p build/coverage - touch build/coverage/index.html - poetry run duty check-docs + pdm install -G duty -G docs -G quality -G typing + pip install safety + + - name: Check if the documentation builds correctly + run: pdm run duty check-docs - name: Check the code quality - run: poetry run duty check-code-quality + run: pdm run duty check-code-quality - name: Check if the code is correctly typed - run: poetry run duty check-types + run: pdm run duty check-types - name: Check for vulnerabilities in dependencies - run: | - pip install safety - poetry run duty check-dependencies + run: pdm run duty check-dependencies tests: @@ -81,22 +78,27 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - name: Set up PDM + uses: pdm-project/setup-pdm@v2 with: python-version: ${{ matrix.python-version }} - - name: Set up Poetry - run: pip install poetry + - name: Set cache variables + id: set_variables + run: | + echo "::set-output name=PIP_CACHE::$(pip cache dir)" + echo "::set-output name=PDM_CACHE::$(pdm config cache_dir)" - - name: Set up the cache - uses: actions/cache@v1 + - name: Set up cache + uses: actions/cache@v2 with: - path: .poetry_cache - key: tests-poetry-cache-${{ matrix.os }}-py${{ matrix.python-version }} + path: | + ${{ steps.set_variables.outputs.PIP_CACHE }} + ${{ steps.set_variables.outputs.PDM_CACHE }} + key: tests-cache-${{ runner.os }}-${{ matrix.python-version }} - - name: Set up the project - run: poetry install -vv || { rm -rf .poetry_cache/virtualenvs/*; poetry install -vv; } + - name: Install dependencies + run: pdm install -G duty -G tests - name: Run the test suite - run: poetry run duty test + run: pdm run duty test diff --git a/.gitignore b/.gitignore index 25973fb9..f6a13b06 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,14 @@ __pycache__/ dist/ *.egg-info/ build/ +htmlcov/ .coverage* pip-wheel-metadata/ .pytest_cache/ .python-version site/ -poetry.lock +pdm.lock +.pdm.toml +__pypackages__/ .mypy_cache/ .venv/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75609fd4..31a02435 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,18 +17,18 @@ make setup !!! note If it fails for some reason, you'll need to install - [Poetry](https://github.com/python-poetry/poetry) + [PDM](https://github.com/pdm-project/pdm) manually. You can install it with: ```bash python3 -m pip install --user pipx - pipx install poetry + pipx install pdm ``` Now you can try running `make setup` again, - or simply `poetry install`. + or simply `pdm install`. You now have the dependencies installed. @@ -43,11 +43,9 @@ on multiple Python versions, you can do one of the following: 1. `export PYTHON_VERSIONS= `: this will run the task with only the current Python version -2. run the task directly with `poetry run duty TASK`, - or `duty TASK` if the environment was already activated - through `poetry shell` +2. run the task directly with `pdm run duty TASK` -The Makefile detects if the Poetry environment is activated, +The Makefile detects if a virtual environment is activated, so `make` will work the same with the virtualenv activated or not. ## Development diff --git a/Makefile b/Makefile index 56c7b5f0..97aa6931 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .DEFAULT_GOAL := help SHELL := bash -DUTY = $(shell [ -n "${VIRTUAL_ENV}" ] || echo poetry run) duty +DUTY = $(shell [ -n "${VIRTUAL_ENV}" ] || echo pdm run) duty args = $(foreach a,$($(subst -,_,$1)_args),$(if $(value $a),$a="$($a)")) check_code_quality_args = files diff --git a/config/coverage.ini b/config/coverage.ini index 27b21edf..bb43c37b 100644 --- a/config/coverage.ini +++ b/config/coverage.ini @@ -1,23 +1,22 @@ -[coverage:paths] -source = - src/mkdocstrings - */site-packages/mkdocstrings - [coverage:run] branch = true -source = - src/mkdocstrings - tests parallel = true +source = + src/ + tests/ + +[coverage:paths] +equivalent = + src/ + __pypackages__/ [coverage:report] ignore_errors = True precision = 2 omit = - tests/* - -[coverage:html] -directory = build/coverage + src/*/__init__.py + src/*/__main__.py + tests/__init__.py [coverage:json] -output = build/coverage.json +output = htmlcov/coverage.json diff --git a/config/flake8.ini b/config/flake8.ini index 1db65393..6a019f12 100644 --- a/config/flake8.ini +++ b/config/flake8.ini @@ -1,5 +1,5 @@ [flake8] -exclude = fixtures,docs,site +exclude = fixtures,site max-line-length = 132 strictness = long docstring-convention = google @@ -23,7 +23,7 @@ ignore = W503 # two-lowercase-letters variable DO conform to snake_case naming style C0103 - # redunant with D102 (missing docstring) + # redundant with D102 (missing docstring) C0116 # line too long C0301 @@ -47,3 +47,57 @@ ignore = W1203 # short name VNE001 + # f-strings + WPS305 + # common variable names (too annoying) + WPS110 + # redundant with W0622 (builtin override), which is more precise about line number + WPS125 + # too many imports + WPS201 + # too many module members + WPS202 + # overused expression + WPS204 + # too many local variables + WPS210 + # too many arguments + WPS211 + # too many expressions + WPS213 + # too many methods + WPS214 + # too deep nesting + WPS220 + # high Jones complexity + WPS221 + # too many elif branches + WPS223 + # string over-use: can't disable it per file? + WPS226 + # too many public instance attributes + WPS230 + # too complex f-string + WPS237 + # too cumbersome, asks to write class A(object) + WPS306 + # multi-line parameters (incompatible with Black) + WPS317 + # multi-line strings (incompatible with attributes docstrings) + WPS322 + # implicit string concatenation + WPS326 + # explicit string concatenation + WPS336 + # noqa overuse + WPS402 + # __init__ modules with logic + WPS412 + # print statements + WPS421 + # statement with no effect (not compatible with attribute docstrings) + WPS428 + # redundant with C0415 (not top-level import) + WPS433 + # implicit dict.get usage (generally false-positive) + WPS529 diff --git a/config/mypy.ini b/config/mypy.ini index 98efbae5..aa988d34 100644 --- a/config/mypy.ini +++ b/config/mypy.ini @@ -1,3 +1,6 @@ [mypy] ignore_missing_imports = true exclude = tests/fixtures/ + +[mypy-toml.*] +ignore_missing_imports = true diff --git a/docs/css/material.css b/docs/css/material.css new file mode 100644 index 00000000..9e8c14a6 --- /dev/null +++ b/docs/css/material.css @@ -0,0 +1,4 @@ +/* More space at the bottom of the page. */ +.md-main__inner { + margin-bottom: 1.5rem; +} diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css new file mode 100644 index 00000000..42c77416 --- /dev/null +++ b/docs/css/mkdocstrings.css @@ -0,0 +1,6 @@ +/* Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: 4px solid rgba(230, 230, 230); + margin-bottom: 80px; +} diff --git a/docs/css/style.css b/docs/css/style.css index b6bd0a29..abd97598 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -1,10 +1,3 @@ -/* Indentation for mkdocstrings items. */ -div.doc-contents:not(.first) { - padding-left: 25px; - border-left: 4px solid rgba(230, 230, 230); - margin-bottom: 80px; -} - /* Mark external links as such (also in nav) */ a.external:hover::after, a.md-nav__link[href^="https:"]:hover::after { /* https://primer.style/octicons/link-external-16 */ diff --git a/docs/gen_credits.py b/docs/gen_credits.py index 3aa4f22a..370d2e7d 100644 --- a/docs/gen_credits.py +++ b/docs/gen_credits.py @@ -1,7 +1,10 @@ +"""Generate the credits page.""" + import functools +import re from itertools import chain from pathlib import Path -from urllib import request +from urllib.request import urlopen import mkdocs_gen_files import toml @@ -16,13 +19,18 @@ def get_credits_data() -> dict: Data required to render the credits template. """ project_dir = Path(__file__).parent.parent - metadata = toml.load(project_dir / "pyproject.toml")["tool"]["poetry"] - lock_data = toml.load(project_dir / "poetry.lock") + metadata = toml.load(project_dir / "pyproject.toml")["project"] + metadata_pdm = toml.load(project_dir / "pyproject.toml")["tool"]["pdm"] + lock_data = toml.load(project_dir / "pdm.lock") project_name = metadata["name"] - poetry_dependencies = chain(metadata["dependencies"].keys(), metadata["dev-dependencies"].keys()) - direct_dependencies = {dep.lower() for dep in poetry_dependencies} - direct_dependencies.remove("python") + all_dependencies = chain( + metadata.get("dependencies", []), + chain(*metadata.get("optional-dependencies", {}).values()), + chain(*metadata_pdm.get("dev-dependencies", {}).values()), + ) + direct_dependencies = {re.sub(r"[^\w-].*$", "", dep) for dep in all_dependencies} + direct_dependencies = {dep.lower() for dep in direct_dependencies} indirect_dependencies = {pkg["name"].lower() for pkg in lock_data["package"]} indirect_dependencies -= direct_dependencies @@ -45,10 +53,10 @@ def get_credits(): commit = "c78c29caa345b6ace19494a98b1544253cbaf8c1" template_url = f"https://raw.githubusercontent.com/pawamoy/jinja-templates/{commit}/credits.md" template_data = get_credits_data() - template_text = request.urlopen(template_url).read().decode("utf8") # noqa: S310 + template_text = urlopen(template_url).read().decode("utf8") # noqa: S310 return jinja_env.from_string(template_text).render(**template_data) -with mkdocs_gen_files.open("credits.md", "w") as f: - f.write(get_credits()) +with mkdocs_gen_files.open("credits.md", "w") as fd: + fd.write(get_credits()) mkdocs_gen_files.set_edit_path("credits.md", "gen_credits.py") diff --git a/docs/gen_doc_stubs.py b/docs/gen_ref_nav.py similarity index 72% rename from docs/gen_doc_stubs.py rename to docs/gen_ref_nav.py index 359878d2..8c7b4da2 100644 --- a/docs/gen_doc_stubs.py +++ b/docs/gen_ref_nav.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +"""Generate the code reference pages and navigation.""" from pathlib import Path @@ -11,11 +11,13 @@ doc_path = path.relative_to("src", "mkdocstrings").with_suffix(".md") full_doc_path = Path("reference", doc_path) - nav[module_path.parts] = doc_path + parts = list(module_path.parts) + parts[-1] = f"{parts[-1]}.py" + nav[parts] = doc_path - with mkdocs_gen_files.open(full_doc_path, "w") as f: + with mkdocs_gen_files.open(full_doc_path, "w") as fd: ident = ".".join(module_path.parts) - print("::: " + ident, file=f) + print("::: " + ident, file=fd) mkdocs_gen_files.set_edit_path(full_doc_path, path) diff --git a/duties.py b/duties.py index 1e42fc65..5e0236d8 100644 --- a/duties.py +++ b/duties.py @@ -3,16 +3,16 @@ import os import re import sys +from functools import wraps from pathlib import Path from shutil import which from typing import List, Optional, Pattern -from urllib import request +from urllib.request import urlopen from duty import duty -from git_changelog.build import Changelog, Version -from jinja2.sandbox import SandboxedEnvironment -PY_SRC_LIST = ("src/mkdocstrings", "tests", "duties.py", "docs") +PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "docs")) +PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS) PY_SRC = " ".join(PY_SRC_LIST) TESTING = os.environ.get("TESTING", "0") in {"1", "true"} CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""} @@ -20,16 +20,7 @@ PTY = not WINDOWS and not CI -def latest(lines: List[str], regex: Pattern) -> Optional[str]: - """Return the last released version. - - Arguments: - lines: Lines of the changelog file. - regex: A compiled regex to find version numbers. - - Returns: - The last version. - """ +def _latest(lines: List[str], regex: Pattern) -> Optional[str]: for line in lines: match = regex.search(line) if match: @@ -37,46 +28,13 @@ def latest(lines: List[str], regex: Pattern) -> Optional[str]: return None -def unreleased(versions: List[Version], last_release: str) -> List[Version]: - """Return the most recent versions down to latest release. - - Arguments: - versions: All the versions (released and unreleased). - last_release: The latest release. - - Returns: - A list of versions. - """ +def _unreleased(versions, last_release): for index, version in enumerate(versions): if version.tag == last_release: return versions[:index] return versions -def read_changelog(filepath: str) -> List[str]: - """Read the changelog file. - - Arguments: - filepath: The path to the changelog file. - - Returns: - The changelog lines. - """ - with open(filepath, "r") as changelog_file: - return changelog_file.read().splitlines() - - -def write_changelog(filepath: str, lines: List[str]) -> None: - """Write the changelog file. - - Arguments: - filepath: The path to the changelog file. - lines: The lines to write to the file. - """ - with open(filepath, "w") as changelog_file: - changelog_file.write("\n".join(lines).rstrip("\n") + "\n") - - def update_changelog( inplace_file: str, marker: str, @@ -93,8 +51,11 @@ def update_changelog( template_url: The URL to the Jinja template used to render contents. commit_style: The style of commit messages to parse. """ + from git_changelog.build import Changelog + from jinja2.sandbox import SandboxedEnvironment + env = SandboxedEnvironment(autoescape=False) - template_text = request.urlopen(template_url).read().decode("utf8") # noqa: S310 + template_text = urlopen(template_url).read().decode("utf8") # noqa: S310 template = env.from_string(template_text) changelog = Changelog(".", style=commit_style) @@ -106,13 +67,17 @@ def update_changelog( last_version.url += planned_tag last_version.compare_url = last_version.compare_url.replace("HEAD", planned_tag) - lines = read_changelog(inplace_file) - last_released = latest(lines, re.compile(version_regex)) + with open(inplace_file, "r") as changelog_file: + lines = changelog_file.read().splitlines() + + last_released = _latest(lines, re.compile(version_regex)) if last_released: - changelog.versions_list = unreleased(changelog.versions_list, last_released) + changelog.versions_list = _unreleased(changelog.versions_list, last_released) rendered = template.render(changelog=changelog, inplace=True) lines[lines.index(marker)] = rendered - write_changelog(inplace_file, lines) + + with open(inplace_file, "w") as changelog_file: # noqa: WPS440 + changelog_file.write("\n".join(lines).rstrip("\n") + "\n") @duty @@ -122,13 +87,15 @@ def changelog(ctx): Arguments: ctx: The context instance (passed automatically). """ + commit = "166758a98d5e544aaa94fda698128e00733497f4" + template_url = f"https://raw.githubusercontent.com/pawamoy/jinja-templates/{commit}/keepachangelog.md" ctx.run( update_changelog, kwargs={ "inplace_file": "CHANGELOG.md", "marker": "", "version_regex": r"^## \[v?(?P[^\]]+)", - "template_url": "https://raw.githubusercontent.com/pawamoy/jinja-templates/master/keepachangelog.md", + "template_url": template_url, "commit_style": "angular", }, title="Updating changelog", @@ -172,32 +139,49 @@ def check_dependencies(ctx): else: safety = "safety" nofail = True - - # Ignore tornado/39462 as there is currently no fix - # See https://github.com/tornadoweb/tornado/issues/2981 - ignored_cves = "39462" - ctx.run( - "poetry export -f requirements.txt --without-hashes | " - f"{safety} check --stdin --full-report -i {ignored_cves}", + f"pdm export -f requirements --without-hashes | {safety} check --stdin --full-report", title="Checking dependencies", pty=PTY, nofail=nofail, ) +def no_docs_py36(nofail=True): + """ + Decorate a duty that builds docs to warn that it's not possible on Python 3.6. + + Arguments: + nofail: Whether to fail or not. + + Returns: + The decorated function. + """ + + def decorator(func): + @wraps(func) + def wrapper(ctx): + if sys.version_info <= (3, 7, 0): + ctx.run(["false"], title="Docs can't be built on Python 3.6", nofail=nofail, quiet=True) + else: + func(ctx) + + return wrapper + + return decorator + + @duty +@no_docs_py36() def check_docs(ctx): """Check if the documentation builds correctly. Arguments: ctx: The context instance (passed automatically). """ - # mkdocs-gen-files works on 3.7+ only - nofail = sys.version_info < (3, 7) - Path("build/coverage").mkdir(parents=True, exist_ok=True) - Path("build/coverage/index.html").touch(exist_ok=True) - ctx.run("mkdocs build -s", title="Building documentation", pty=PTY, nofail=nofail, quiet=nofail) + Path("htmlcov").mkdir(parents=True, exist_ok=True) + Path("htmlcov/index.html").touch(exist_ok=True) + ctx.run("mkdocs build -s", title="Building documentation", pty=PTY) @duty @@ -223,6 +207,7 @@ def clean(ctx): 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") @@ -230,6 +215,7 @@ def clean(ctx): @duty +@no_docs_py36(nofail=False) def docs(ctx): """Build the documentation locally. @@ -240,6 +226,7 @@ def docs(ctx): @duty +@no_docs_py36(nofail=False) def docs_serve(ctx, host="127.0.0.1", port=8000): """Serve the documentation (localhost:8000). @@ -252,6 +239,7 @@ def docs_serve(ctx, host="127.0.0.1", port=8000): @duty +@no_docs_py36(nofail=False) def docs_deploy(ctx): """Deploy the documentation on GitHub pages. @@ -286,15 +274,14 @@ def release(ctx, version): ctx: The context instance (passed automatically). version: The new version number to use. """ - ctx.run(f"poetry version {version}", title=f"Bumping version in pyproject.toml to {version}", pty=PTY) 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) if not TESTING: ctx.run("git push", title="Pushing commits", pty=False) ctx.run("git push --tags", title="Pushing tags", pty=False) - ctx.run("poetry build", title="Building dist/wheel", pty=PTY) - ctx.run("poetry publish", title="Publishing version", pty=PTY) + ctx.run("pdm build", title="Building dist/wheel", pty=PTY) + ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY) docs_deploy.run() # type: ignore @@ -305,7 +292,7 @@ def coverage(ctx): Arguments: ctx: The context instance (passed automatically). """ - ctx.run("coverage combine .coverage-*", nofail=True) + ctx.run("coverage combine", nofail=True) ctx.run("coverage report --rcfile=config/coverage.ini", capture=False) ctx.run("coverage html --rcfile=config/coverage.ini") @@ -325,7 +312,7 @@ def test(ctx, match: str = ""): ctx.run("pip install sphinx docutils --no-deps", title="Installing additional test dependencies") py_version = f"{sys.version_info.major}{sys.version_info.minor}" - os.environ["COVERAGE_FILE"] = f".coverage-{py_version}" + os.environ["COVERAGE_FILE"] = f".coverage.{py_version}" ctx.run( ["pytest", "-c", "config/pytest.ini", "-n", "auto", "-k", match, "tests"], title="Running tests", diff --git a/mkdocs.yml b/mkdocs.yml index c5d812fc..a7448472 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,9 +4,36 @@ site_url: "https://mkdocstrings.github.io/" repo_url: "https://github.com/mkdocstrings/mkdocstrings" edit_uri: "blob/master/docs/" repo_name: "mkdocstrings/mkdocstrings" +site_dir: "site" + +nav: +- Home: + - Overview: index.md + - Changelog: changelog.md + - Credits: credits.md + - License: license.md +- Usage: + - Theming: theming.md + - Handlers: + - handlers/overview.md + - Python: handlers/python.md + - Crystal: https://mkdocstrings.github.io/crystal/ + - Troubleshooting: troubleshooting.md +# defer to gen-files + literate-nav +- Code Reference: reference/ +- Development: + - Contributing: contributing.md + - Code of Conduct: code_of_conduct.md + - Coverage report: coverage.md +- Author's website: https://pawamoy.github.io/ theme: name: material + icon: + logo: material/currency-sign + features: + - navigation.tabs + - navigation.top palette: - media: "(prefers-color-scheme: light)" scheme: default @@ -24,10 +51,12 @@ theme: name: Switch to light mode extra_css: -- css/style.css +- css/material.css +- css/mkdocstrings.css markdown_extensions: - admonition +- pymdownx.details - pymdownx.emoji - pymdownx.magiclink - pymdownx.snippets: @@ -43,12 +72,11 @@ plugins: - gen-files: scripts: - docs/gen_credits.py - - docs/gen_doc_stubs.py + - docs/gen_ref_nav.py - literate-nav: nav_file: SUMMARY.md - section-index -- coverage: - html_report_dir: build/coverage +- coverage - mkdocstrings: handlers: python: @@ -61,3 +89,10 @@ plugins: - https://docs.python-requests.org/en/master/objects.inv watch: - src/mkdocstrings + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/pawamoy + - icon: fontawesome/brands/twitter + link: https://twitter.com/pawamoy diff --git a/pyproject.toml b/pyproject.toml index 0cd4df13..56a97e86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,69 +1,90 @@ [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["pdm-pep517"] +build-backend = "pdm.pep517.api" -[tool.poetry] +[project] name = "mkdocstrings" -version = "0.15.2" +version = {use_scm = true} description = "Automatic documentation from sources, for MkDocs." -authors = ["Timothée Mazzucotelli "] -license = "ISC License" +authors = [{name = "Timothée Mazzucotelli", email = "pawamoy@pm.me"}] +license = {file = "LICENSE"} readme = "README.md" -repository = "https://github.com/mkdocstrings/mkdocstrings" -homepage = "https://github.com/mkdocstrings/mkdocstrings" +requires-python = ">=3.6" keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"] -packages = [ { include = "mkdocstrings", from = "src" } ] +dynamic = ["version", "classifiers"] +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: ISC License (ISCL)", + "Typing :: Typed", +] +dependencies = [ + "Jinja2>=2.11.1,<4.0", + "Markdown~=3.3", + "MarkupSafe>=1.1,<3.0", + "mkdocs~=1.2", + "mkdocs-autorefs>=0.1,<0.4", + "pymdown-extensions>=6.3,<9.0", + "pytkdocs>=0.2.0,<0.13.0", +] -[tool.poetry.dependencies] -python = "^3.6.2" -Jinja2 = ">=2.11.1, <4.0" -Markdown = "^3.3" -MarkupSafe = ">=1.1, <3.0" -mkdocs = "^1.1.1" -mkdocs-autorefs = ">=0.1, <0.4" -pymdown-extensions = ">=6.3, <9.0" -pytkdocs = ">=0.2.0, <0.13.0" +[project.urls] +Homepage = "https://mkdocstrings.github.io" +Documentation = "https://mkdocstrings.github.io" +Changelog = "https://mkdocstrings.github.io/changelog" +Repository = "https://github.com/mkdocstrings/mkdocstrings" +Issues = "https://github.com/mkdocstrings/mkdocstrings/issues" +Discussions = "https://github.com/mkdocstrings/mkdocstrings/discussions" +Gitter = "https://gitter.im/mkdocstrings/community" +Funding = "https://github.com/sponsors/mkdocstrings" -[tool.poetry.dev-dependencies] -# formatting, quality, tests -autoflake = "^1.4" -black = "^21.9b0" -isort = "^5.9.3" -mypy = "^0.910" -pytest = "^6.2.5" -pytest-cov = "^2.12.1" -pytest-randomly = "^3.10.1" -pytest-sugar = "^0.9.4" -pytest-xdist = "^2.3.0" - -# tasks -duty = "^0.7.0" -git-changelog = "^0.4.2" -toml = "^0.10.2" - -# flake8 plugins -darglint = "^1.8.0" -flake8-bandit = "^2.1.2" -flake8-black = "^0.2.3" -flake8-bugbear = "^21.9.1" -flake8-builtins = "^1.5.3" -flake8-comprehensions = "^3.6.1" -flake8-docstrings = "^1.6.0" -flake8-pytest-style = "^1.5.0" -flake8-string-format = "^0.3.0" -flake8-tidy-imports = "^4.4.1" -flake8-variables-names = "^0.0.4" -pep8-naming = "^0.12.1" +[project.entry-points."mkdocs.plugins"] +mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" -# docs -mkdocs-coverage = "^0.2.4" -mkdocs-material = "^7.2.8" -mkdocs-gen-files = {version = "^0.3.3", markers = "python_version>='3.7'"} -mkdocs-section-index = "^0.3.1" -mkdocs-literate-nav = "^0.4.0" +[tool.pdm] +package-dir = "src" -[tool.poetry.plugins."mkdocs.plugins"] -mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" +[tool.pdm.dev-dependencies] +duty = ["duty~=0.6"] +docs = [ + "mkdocs-coverage~=0.2; python_version >= '3.7'", + "mkdocs-gen-files~=0.3; python_version >= '3.7'", + "mkdocs-literate-nav~=0.4; python_version >= '3.7'", + "mkdocs-material~=7.1; python_version >= '3.7'", + "mkdocs-section-index~=0.3; python_version >= '3.7'", + "toml~=0.10; python_version >= '3.7'", +] +format = [ + "autoflake~=1.4", + "black~=20.8b1", + "isort~=5.8", +] +maintain = [ + # TODO: remove this section when git-changelog is more powerful + "git-changelog~=0.4", +] +quality = [ + "darglint~=1.7", + "flake8-bandit~=2.1", + "flake8-black~=0.2", + "flake8-bugbear~=21.3", + "flake8-builtins~=1.5", + "flake8-comprehensions~=3.4", + "flake8-docstrings~=1.6", + "flake8-pytest-style~=1.4", + "flake8-string-format~=0.3", + "flake8-tidy-imports~=4.2", + "flake8-variables-names~=0.0", + "pep8-naming~=0.11", + "wps-light~=0.15", +] +tests = [ + "pytest~=6.2", + "pytest-cov~=2.11", + "pytest-randomly~=3.6", + "pytest-sugar~=0.9", + "pytest-xdist~=2.2", +] +typing = ["mypy~=0.812"] [tool.black] line-length = 120 diff --git a/scripts/multirun.sh b/scripts/multirun.sh index 3cacf958..4ca6e2ce 100755 --- a/scripts/multirun.sh +++ b/scripts/multirun.sh @@ -5,18 +5,13 @@ PYTHON_VERSIONS="${PYTHON_VERSIONS-3.6 3.7 3.8 3.9}" if [ -n "${PYTHON_VERSIONS}" ]; then for python_version in ${PYTHON_VERSIONS}; do - if output=$(poetry env use "${python_version}" 2>&1); then - if echo "${output}" | grep -q ^Creating; then - echo "> Environment for Python ${python_version} not created, skipping" >&2 - poetry env remove "${python_version}" &>/dev/null || true - else - echo "> poetry run $@ (Python ${python_version})" - poetry run "$@" - fi + if pdm use -f "${python_version}" &>/dev/null; then + echo "> pdm run $@ (Python ${python_version})" + pdm run "$@" else - echo "> poetry env use ${python_version}: Python version not available?" >&2 + echo "> pdm use -f ${python_version}: Python version not available?" >&2 fi done else - poetry run "$@" + pdm run "$@" fi diff --git a/scripts/setup.sh b/scripts/setup.sh index d7b15786..cfddbac7 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,21 +12,17 @@ install_with_pipx() { fi } -install_with_pipx poetry +install_with_pipx pdm if [ -n "${PYTHON_VERSIONS}" ]; then for python_version in ${PYTHON_VERSIONS}; do - if output=$(poetry env use "${python_version}" 2>&1); then - if echo "${output}" | grep -q ^Creating; then - echo "> Created environment for Python ${python_version}" - else - echo "> Using Python ${python_version} environment" - fi - poetry install + if pdm use -f "${python_version}" &>/dev/null; then + echo "> Using Python ${python_version} environment" + pdm install else - echo "> poetry env use ${python_version}: Python version not available?" >&2 + echo "> pdm use -f ${python_version}: Python version not available?" >&2 fi done else - poetry install -fi \ No newline at end of file + pdm install +fi diff --git a/src/mkdocstrings/py.typed b/src/mkdocstrings/py.typed new file mode 100644 index 00000000..e69de29b From 62c8c8a6a45a49bd60198423e06cf79338c6197b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 22:50:28 +0200 Subject: [PATCH 42/50] ci: Quality --- config/flake8.ini | 28 ++++++++++++++++++++++- config/mypy.ini | 9 ++++++++ duties.py | 11 +++++---- src/mkdocstrings/extension.py | 4 ++-- src/mkdocstrings/handlers/base.py | 7 +++--- src/mkdocstrings/handlers/python.py | 2 +- src/mkdocstrings/handlers/rendering.py | 4 ++-- src/mkdocstrings/inventory.py | 6 ++--- src/mkdocstrings/plugin.py | 31 ++++++++++++++------------ tests/test_extension.py | 2 +- tests/test_python_handler.py | 7 +++++- 11 files changed, 78 insertions(+), 33 deletions(-) diff --git a/config/flake8.ini b/config/flake8.ini index 6a019f12..2b50d854 100644 --- a/config/flake8.ini +++ b/config/flake8.ini @@ -1,5 +1,5 @@ [flake8] -exclude = fixtures,site +exclude = fixtures,site,snippets max-line-length = 132 strictness = long docstring-convention = google @@ -13,6 +13,8 @@ ignore = E203 # redundant with E0602 (undefined variable) F821 + # error suffix foe exception + N818 # black already deals with quoting Q000 # use of assert @@ -41,6 +43,8 @@ ignore = R0914 # too many statements R0915 + # protected attribute + W0212 # redundant with F401 (unused import) W0611 # lazy formatting for logging calls @@ -77,6 +81,10 @@ ignore = WPS226 # too many public instance attributes WPS230 + # too complex function + WPS231 + # too many variables unpacked + WPS236 # too complex f-string WPS237 # too cumbersome, asks to write class A(object) @@ -89,15 +97,33 @@ ignore = WPS326 # explicit string concatenation WPS336 + # line starts with dot (incompatible with Black) + WPS348 + # blank line before bracket (incompatible with Black) + WPS355 + # raw string + WPS360 # noqa overuse WPS402 # __init__ modules with logic WPS412 + # del/pass + WPS420 # print statements WPS421 # statement with no effect (not compatible with attribute docstrings) WPS428 + # magic numbers + WPS432 # redundant with C0415 (not top-level import) WPS433 + # multiline usage (variable docstring) + WPS462 + # try finally without except + WPS501 # implicit dict.get usage (generally false-positive) WPS529 + # subclassing builtin + WPS600 + # getter/stter (false positives) + WPS615 diff --git a/config/mypy.ini b/config/mypy.ini index aa988d34..e88e9042 100644 --- a/config/mypy.ini +++ b/config/mypy.ini @@ -2,5 +2,14 @@ ignore_missing_imports = true exclude = tests/fixtures/ +[mypy-docutils.*] +ignore_missing_imports = true + +[mypy-markdown.*] +ignore_missing_imports = true + [mypy-toml.*] ignore_missing_imports = true + +[mypy-yaml.*] +ignore_missing_imports = true diff --git a/duties.py b/duties.py index 5e0236d8..284c97ce 100644 --- a/duties.py +++ b/duties.py @@ -148,8 +148,7 @@ def check_dependencies(ctx): def no_docs_py36(nofail=True): - """ - Decorate a duty that builds docs to warn that it's not possible on Python 3.6. + """Decorate a duty that builds docs to warn that it's not possible on Python 3.6. Arguments: nofail: Whether to fail or not. @@ -305,11 +304,15 @@ def test(ctx, match: str = ""): ctx: The context instance (passed automatically). match: A pytest expression to filter selected tests. """ - try: + try: # noqa: WPS229 import sphinx # isort:skip # noqa: F401 import docutils # isort:skip # noqa: F401 except ImportError: - ctx.run("pip install sphinx docutils --no-deps", title="Installing additional test dependencies") + py = f"{sys.version_info.major}.{sys.version_info.minor}" + ctx.run( + f"pip install sphinx docutils --no-deps -t __pypackages__/{py}/lib", + title="Installing additional test dependencies", + ) py_version = f"{sys.version_info.major}{sys.version_info.minor}" os.environ["COVERAGE_FILE"] = f".coverage.{py_version}" diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py index 0e6c36ff..afe59708 100644 --- a/src/mkdocstrings/extension.py +++ b/src/mkdocstrings/extension.py @@ -41,7 +41,7 @@ try: from mkdocs.exceptions import PluginError # New in MkDocs 1.2 except ImportError: - PluginError = SystemExit + PluginError = SystemExit # noqa: WPS440 log = get_logger(__name__) @@ -183,7 +183,7 @@ def _process_block(self, identifier: str, yaml_block: str, heading_level: int = if not self._updated_env: log.debug("Updating renderer's env") - handler.renderer._update_env(self.md, self._config) # noqa: W0212 (protected member OK) + handler.renderer._update_env(self.md, self._config) # noqa: WPS437 (protected member OK) self._updated_env = True log.debug("Rendering templates") diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py index a21c18c2..f496e7ed 100644 --- a/src/mkdocstrings/handlers/base.py +++ b/src/mkdocstrings/handlers/base.py @@ -96,7 +96,7 @@ def __init__(self, directory: str, theme: str, custom_templates: Optional[str] = for path in paths: css_path = path / "style.css" if css_path.is_file(): - self.extra_css += "\n" + css_path.read_text(encoding="utf-8") + self.extra_css += "\n" + css_path.read_text(encoding="utf-8") # noqa: WPS601 break if custom_templates is not None: @@ -343,9 +343,8 @@ def get_anchor(self, identifier: str) -> Optional[str]: anchor = handler.renderer.get_anchor(handler.collector.collect(identifier, {})) except CollectionError: continue - else: - if anchor is not None: - return anchor + if anchor is not None: + return anchor return None def get_handler_name(self, config: dict) -> str: diff --git a/src/mkdocstrings/handlers/python.py b/src/mkdocstrings/handlers/python.py index 644a6cea..0aab09ac 100644 --- a/src/mkdocstrings/handlers/python.py +++ b/src/mkdocstrings/handlers/python.py @@ -290,7 +290,7 @@ def load_inventory( if base_url is None: base_url = posixpath.dirname(url) - for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values(): + for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values(): # noqa: WPS526 yield item.name, posixpath.join(base_url, item.uri) diff --git a/src/mkdocstrings/handlers/rendering.py b/src/mkdocstrings/handlers/rendering.py index 77c15a13..e8383cf2 100644 --- a/src/mkdocstrings/handlers/rendering.py +++ b/src/mkdocstrings/handlers/rendering.py @@ -52,7 +52,7 @@ def __init__(self, md: Markdown): config = ext.getConfigs() config["language_prefix"] = config["lang_prefix"] self._css_class = config.pop("css_class", "highlight") - super().__init__(**{k: v for k, v in config.items() if k in self._highlight_config_keys}) + super().__init__(**{name: opt for name, opt in config.items() if name in self._highlight_config_keys}) def highlight( # noqa: W0221 (intentionally different params, we're extending the functionality) self, @@ -185,7 +185,7 @@ def run(self, root: Element): el = copy.copy(el) # 'toc' extension's first pass (which we require to build heading stubs/ids) also edits the HTML. # Undo the permalink edit so we can pass this heading to the outer pass of the 'toc' extension. - if len(el) > 0 and el[-1].get("class") == self.md.treeprocessors["toc"].permalink_class: + if len(el) > 0 and el[-1].get("class") == self.md.treeprocessors["toc"].permalink_class: # noqa: WPS507 del el[-1] self.headings.append(el) diff --git a/src/mkdocstrings/inventory.py b/src/mkdocstrings/inventory.py index cfcb5676..9a59d2b4 100644 --- a/src/mkdocstrings/inventory.py +++ b/src/mkdocstrings/inventory.py @@ -51,10 +51,10 @@ def format_sphinx(self) -> str: @classmethod def parse_sphinx(cls, line: str) -> "InventoryItem": """Parse a line from a Sphinx v2 inventory file and return an `InventoryItem` from it.""" - m = cls.sphinx_item_regex.search(line) - if not m: + match = cls.sphinx_item_regex.search(line) + if not match: raise ValueError(line) - name, domain, role, priority, uri, dispname = m.groups() + name, domain, role, priority, uri, dispname = match.groups() if uri.endswith("$"): uri = uri[:-1] + name if dispname == "-": diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py index 677fbb07..4e693473 100644 --- a/src/mkdocstrings/plugin.py +++ b/src/mkdocstrings/plugin.py @@ -13,12 +13,12 @@ """ import collections -import concurrent.futures import functools import gzip import os -import urllib.request +from concurrent import futures from typing import Any, BinaryIO, Callable, Iterable, List, Mapping, Optional, Tuple +from urllib import request from mkdocs.config import Config from mkdocs.config.config_options import Type as MkType @@ -37,6 +37,9 @@ RENDERING_OPTS_KEY: str = "rendering" """The name of the rendering parameter in YAML configuration blocks.""" +InventoryImportType = List[Tuple[str, Mapping[str, Any]]] +InventoryLoaderType = Callable[..., Iterable[Tuple[str, str]]] + class MkdocstringsPlugin(BasePlugin): """An `mkdocs` plugin. @@ -153,7 +156,7 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused else: theme_name = config["theme"].name - to_import: List[Tuple[str, Mapping[str, Any]]] = [] + to_import: InventoryImportType = [] for handler_name, conf in self.config["handlers"].items(): for import_item in conf.pop("import", ()): if isinstance(import_item, str): @@ -169,7 +172,7 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused } self._handlers = Handlers(extension_config) - try: + try: # noqa: WPS229 # If autorefs plugin is explicitly enabled, just use it. autorefs = config["plugins"]["autorefs"] log.debug(f"Picked up existing autorefs instance {autorefs!r}") @@ -189,8 +192,8 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: W0613 (unused self._inv_futures = [] if to_import: - inv_loader = concurrent.futures.ThreadPoolExecutor(4) - for handler_name, import_item in to_import: + inv_loader = futures.ThreadPoolExecutor(4) + for handler_name, import_item in to_import: # noqa: WPS440 future = inv_loader.submit( self._load_inventory, self.get_handler(handler_name).load_inventory, **import_item ) @@ -230,9 +233,9 @@ def on_env(self, env, config: Config, **kwargs): if self._inv_futures: log.debug(f"Waiting for {len(self._inv_futures)} inventory download(s)") - concurrent.futures.wait(self._inv_futures, timeout=30) - for k, v in collections.ChainMap(*(f.result() for f in self._inv_futures)).items(): - config["plugins"]["autorefs"].register_url(k, v) + futures.wait(self._inv_futures, timeout=30) + for page, identifier in collections.ChainMap(*(fut.result() for fut in self._inv_futures)).items(): + config["plugins"]["autorefs"].register_url(page, identifier) self._inv_futures = [] def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 (unused arguments, cannot be static) @@ -251,8 +254,8 @@ def on_post_build(self, config: Config, **kwargs) -> None: # noqa: W0613,R0201 config: The MkDocs config object. kwargs: Additional arguments passed by MkDocs. """ - for f in self._inv_futures: - f.cancel() + for future in self._inv_futures: + future.cancel() if self._handlers: log.debug("Tearing handlers down") @@ -271,7 +274,7 @@ def get_handler(self, handler_name: str) -> BaseHandler: @classmethod @functools.lru_cache(maxsize=None) - def _load_inventory(cls, loader: Callable[..., Iterable[Tuple[str, str]]], url: str, **kwargs) -> Mapping[str, str]: + def _load_inventory(cls, loader: InventoryLoaderType, url: str, **kwargs) -> Mapping[str, str]: """Download and process inventory files using a handler. Arguments: @@ -283,8 +286,8 @@ def _load_inventory(cls, loader: Callable[..., Iterable[Tuple[str, str]]], url: A mapping from identifier to absolute URL. """ log.debug(f"Downloading inventory from {url!r}") - req = urllib.request.Request(url, headers={"Accept-Encoding": "gzip", "User-Agent": "mkdocstrings/0.15.0"}) - with urllib.request.urlopen(req) as resp: # noqa: S310 (URL audit OK: comes from a checked-in config) + req = request.Request(url, headers={"Accept-Encoding": "gzip", "User-Agent": "mkdocstrings/0.15.0"}) + with request.urlopen(req) as resp: # noqa: S310 (URL audit OK: comes from a checked-in config) content: BinaryIO = resp if "gzip" in resp.headers.get("content-encoding", ""): content = gzip.GzipFile(fileobj=resp) # type: ignore[assignment] diff --git a/tests/test_extension.py b/tests/test_extension.py index 82d18001..3d4a5294 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -12,7 +12,7 @@ from mkdocs.config.defaults import get_schema except ImportError: - def get_schema(): + def get_schema(): # noqa: WPS440 """Fallback for old versions of MkDocs.""" return config.DEFAULT_SCHEMA diff --git a/tests/test_python_handler.py b/tests/test_python_handler.py index 565e7527..315b5760 100644 --- a/tests/test_python_handler.py +++ b/tests/test_python_handler.py @@ -2,7 +2,12 @@ from copy import deepcopy -from mkdocstrings.handlers.python import _sort_key_alphabetical, _sort_key_source, rebuild_category_lists, sort_object +from mkdocstrings.handlers.python import ( # noqa: WPS450 + _sort_key_alphabetical, + _sort_key_source, + rebuild_category_lists, + sort_object, +) def test_members_order(): From 1ce58582da9d09e6816a7e414beb52b4abbb94ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 22:50:47 +0200 Subject: [PATCH 43/50] docs: Clean up, fix word break --- docs/SUMMARY.md | 14 -------------- docs/css/mkdocstrings.css | 5 +++++ 2 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 docs/SUMMARY.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md deleted file mode 100644 index ae424f83..00000000 --- a/docs/SUMMARY.md +++ /dev/null @@ -1,14 +0,0 @@ -- [Overview](index.md) -- [Usage](usage.md) - - [Theming](theming.md) - - [Handlers](handlers/overview.md) - - [Python](handlers/python.md) - - [Crystal](https://mkdocstrings.github.io/crystal/) - - [Troubleshooting](troubleshooting.md) -- [Code Reference](reference/) -- [Contributing](contributing.md) - - [Code of Conduct](code_of_conduct.md) - - [Coverage report](coverage.md) -- [Changelog](changelog.md) -- [Credits](credits.md) -- [License](license.md) diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css index 42c77416..a83172e5 100644 --- a/docs/css/mkdocstrings.css +++ b/docs/css/mkdocstrings.css @@ -4,3 +4,8 @@ div.doc-contents:not(.first) { border-left: 4px solid rgba(230, 230, 230); margin-bottom: 80px; } + +/* Avoid breaking parameters name, etc. in table cells. */ +td code { + word-break: normal !important; +} From 297b1453726348e6e5ba0fc97059daec54d2bcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 22:54:56 +0200 Subject: [PATCH 44/50] docs: Add style back --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index a7448472..8eb7231d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,6 +51,7 @@ theme: name: Switch to light mode extra_css: +- css/style.css - css/material.css - css/mkdocstrings.css From 81509035a25ebe32e998f4a8a1f595c8d972e199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 23:03:14 +0200 Subject: [PATCH 45/50] ci: Also install docs dependencies to run tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b5ec9be..b5750cd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: key: tests-cache-${{ runner.os }}-${{ matrix.python-version }} - name: Install dependencies - run: pdm install -G duty -G tests + run: pdm install -G duty -G tests -G docs - name: Run the test suite run: pdm run duty test From 0b4b2f5259f9607ecb82f11ae2372ddeb156bca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 23:30:33 +0200 Subject: [PATCH 46/50] tests: Add pygments to tests dependencies --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 56a97e86..13b5d494 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,8 @@ Funding = "https://github.com/sponsors/mkdocstrings" [project.entry-points."mkdocs.plugins"] mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" + +[project.optional-dependencies] [tool.pdm] package-dir = "src" @@ -78,6 +80,7 @@ quality = [ "wps-light~=0.15", ] tests = [ + "pygments~=2.10", # python 3.6 "pytest~=6.2", "pytest-cov~=2.11", "pytest-randomly~=3.6", From e404b28e01aea7355f54e03ced0125ceec49c98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 20 Sep 2021 23:54:35 +0200 Subject: [PATCH 47/50] build: Fix building sdist/wheels by including namespace package --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 13b5d494..c517aa90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" [project.optional-dependencies] [tool.pdm] package-dir = "src" +includes = ["src/mkdocstrings"] [tool.pdm.dev-dependencies] duty = ["duty~=0.6"] From b8d3fa986f2ebab88322cbef0424270199701326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 21 Sep 2021 13:12:40 +0200 Subject: [PATCH 48/50] Merge pull request #321 from mkdocstrings/hide-parens-no-bases refactor: Don't add parentheses when class has no bases --- src/mkdocstrings/templates/python/material/class.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mkdocstrings/templates/python/material/class.html b/src/mkdocstrings/templates/python/material/class.html index 414b6eb7..62a70f57 100644 --- a/src/mkdocstrings/templates/python/material/class.html +++ b/src/mkdocstrings/templates/python/material/class.html @@ -24,7 +24,7 @@ {% if show_full_path %}{{ class.path }}{% else %}{{ class.name }}{% endif %} - {% if config.show_bases and class.bases != ['object'] %} + {% if config.show_bases and class.bases and class.bases != ['object'] %} ({% for base in class.bases -%} {{ base|brief_xref() }}{% if not loop.last %}, {% endif %} {% endfor %}) From 6a3030e410712f6e26249131a17aa38b04b12bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 21 Sep 2021 13:38:44 +0200 Subject: [PATCH 49/50] docs: Fix missing usage page and autorefs modules names --- docs/gen_ref_nav.py | 4 ++-- mkdocs.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/gen_ref_nav.py b/docs/gen_ref_nav.py index 8c7b4da2..1411abdb 100644 --- a/docs/gen_ref_nav.py +++ b/docs/gen_ref_nav.py @@ -21,8 +21,8 @@ mkdocs_gen_files.set_edit_path(full_doc_path, path) -nav["mkdocs_autorefs", "references"] = "autorefs/references.md" -nav["mkdocs_autorefs", "plugin"] = "autorefs/plugin.md" +nav["mkdocs_autorefs", "references.py"] = "autorefs/references.md" +nav["mkdocs_autorefs", "plugin.py"] = "autorefs/plugin.md" with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) diff --git a/mkdocs.yml b/mkdocs.yml index 8eb7231d..96c2e575 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ nav: - Credits: credits.md - License: license.md - Usage: + - usage.md - Theming: theming.md - Handlers: - handlers/overview.md From b05569c292b08df4b6f363966f80dec17ada8aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 21 Sep 2021 13:44:48 +0200 Subject: [PATCH 50/50] chore: Prepare release 0.16.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5ad7112..ef6d4eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.16.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.16.0) - 2021-09-20 + +[Compare with 0.15.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.0...0.16.0) + +### Features +- Add a rendering option to change the sorting of members ([b1fff8b](https://github.com/mkdocstrings/mkdocstrings/commit/b1fff8b8ef4d6d77417fc43ed8be4b578d6437e4) by Joe Rickerby). [Issue #114](https://github.com/mkdocstrings/mkdocstrings/issues/114), [PR #274](https://github.com/mkdocstrings/mkdocstrings/pull/274) +- Add option to show Python base classes ([436f550](https://github.com/mkdocstrings/mkdocstrings/commit/436f5504ad72ab6d1f5b4303e6b68bc84562c32b) by Brian Koropoff). [Issue #269](https://github.com/mkdocstrings/mkdocstrings/issues/269), [PR #297](https://github.com/mkdocstrings/mkdocstrings/pull/297) +- Support loading external Python inventories in Sphinx format ([a8418cb](https://github.com/mkdocstrings/mkdocstrings/commit/a8418cb4c6193d35cdc72508b118a712cf0334e1) by Oleh Prypin). [PR #287](https://github.com/mkdocstrings/mkdocstrings/pull/287) +- Support loading external inventories and linking to them ([8b675f4](https://github.com/mkdocstrings/mkdocstrings/commit/8b675f4671f8bbfd2f337ed043e3682b0a0ad0f6) by Oleh Prypin). [PR #277](https://github.com/mkdocstrings/mkdocstrings/pull/277) +- Very basic support for MkDocs theme ([974ca90](https://github.com/mkdocstrings/mkdocstrings/commit/974ca9010efca1b8279767faf8efcd2470a8371d) by Oleh Prypin). [PR #272](https://github.com/mkdocstrings/mkdocstrings/pull/272) +- Generate objects inventory ([14ed959](https://github.com/mkdocstrings/mkdocstrings/commit/14ed959860a784a835cd71f911081f2026d66c81) and [bbd85a9](https://github.com/mkdocstrings/mkdocstrings/commit/bbd85a92fa70bddfe10a907a4d63b8daf0810cb2) by Timothée Mazzucotelli). [Issue #251](https://github.com/mkdocstrings/mkdocstrings/issues/251), [PR #253](https://github.com/mkdocstrings/mkdocstrings/pull/253) + +### Bug Fixes +- Don't render empty code blocks for missing type annotations ([d2e9e1e](https://github.com/mkdocstrings/mkdocstrings/commit/d2e9e1ef3cf304081b07f763843a9722bf9b117e) by Oleh Prypin). +- Fix custom handler not being used ([6dcf342](https://github.com/mkdocstrings/mkdocstrings/commit/6dcf342fb83b19e385d56d37235f2b23e8c8c767) by Timothée Mazzucotelli). [Issue #259](https://github.com/mkdocstrings/mkdocstrings/issues/259), [PR #263](https://github.com/mkdocstrings/mkdocstrings/pull/263) +- Don't hide `setup_commands` errors ([92418c4](https://github.com/mkdocstrings/mkdocstrings/commit/92418c4b3e80b67d5116efa73931fc113daa60e9) by Gabriel Vîjială). [PR #258](https://github.com/mkdocstrings/mkdocstrings/pull/258) + +### Code Refactoring +- Move writing extra files to an earlier stage in the build ([3890ab5](https://github.com/mkdocstrings/mkdocstrings/commit/3890ab597692e56d7ece576c166373b66ff4e615) by Oleh Prypin). [PR #275](https://github.com/mkdocstrings/mkdocstrings/pull/275) + + ## [0.15.2](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.15.2) - 2021-06-09 [Compare with 0.15.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.15.1...0.15.2)