From 3cbe472e98be1174966bdbda34d6a6fb2336f2cf Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Mon, 23 Jan 2023 22:14:34 +0000 Subject: [PATCH 01/14] ci: Fix typing Fixes problems reported by mypy. Related typeshed issue: https://github.com/python/typeshed/issues/8430 PR #53: https://github.com/mkdocstrings/python/pull/53 --- pyproject.toml | 2 +- src/mkdocstrings_handlers/python/handler.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 914bd58b..e32d921b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ tests = [ "pytest-xdist>=2.4", ] typing = [ - "mypy>=0.910", + "mypy>=0.911", "types-markdown>=3.3", "types-toml>=0.10", ] diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 940f2731..f2732ee8 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -2,6 +2,7 @@ from __future__ import annotations +import copy import glob import os import posixpath @@ -9,7 +10,7 @@ import sys from collections import ChainMap from contextlib import suppress -from typing import Any, BinaryIO, Iterator, Optional, Tuple +from typing import Any, BinaryIO, Iterator, Mapping, Optional, Tuple from griffe.agents.extensions import load_extensions from griffe.collections import LinesCollection, ModulesCollection @@ -189,13 +190,15 @@ def load_inventory( for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values(): # noqa: WPS526 yield item.name, posixpath.join(base_url, item.uri) - def collect(self, identifier: str, config: dict) -> CollectorItem: # noqa: D102,WPS231 + def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: # noqa: D102,WPS231 module_name = identifier.split(".", 1)[0] unknown_module = module_name not in self._modules_collection if config.get("fallback", False) and unknown_module: raise CollectionError("Not loading additional modules during fallback") - final_config = ChainMap(config, self.default_config) + # See: https://github.com/python/typeshed/issues/8430 + mutable_config = dict(copy.deepcopy(config)) + final_config = ChainMap(mutable_config, self.default_config) parser_name = final_config["docstring_style"] parser_options = final_config["docstring_options"] parser = parser_name and Parser(parser_name) @@ -232,8 +235,10 @@ def collect(self, identifier: str, config: dict) -> CollectorItem: # noqa: D102 return doc_object - def render(self, data: CollectorItem, config: dict) -> str: # noqa: D102 (ignore missing docstring) - final_config = ChainMap(config, self.default_config) + def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa: D102 (ignore missing docstring) + # See https://github.com/python/typeshed/issues/8430 + mutabled_config = dict(copy.deepcopy(config)) + final_config = ChainMap(mutabled_config, self.default_config) template = self.env.get_template(f"{data.kind.value}.html") From 32be783c3fed56a2ff576d5053079a89f3bc7dcd Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Mon, 23 Jan 2023 22:15:46 +0000 Subject: [PATCH 02/14] docs: Minor grammar improvements --- CODE_OF_CONDUCT.md | 6 +++--- CONTRIBUTING.md | 4 ++-- README.md | 10 +++++----- docs/customization.md | 4 ++-- docs/usage.md | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 35f1f538..3994752a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -3,7 +3,7 @@ ## Our Pledge In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and +contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and @@ -39,7 +39,7 @@ response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or +that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. @@ -58,7 +58,7 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pawamoy@pm.me. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. +obligated to maintain confidentiality concerning the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba0c5d2b..9b793ad3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,7 +93,7 @@ Scope and body are optional. Type can be: - `feat`: New feature. - `fix`: Bug fix. - `perf`: About performance. -- `refactor`: Changes which are not features nor bug fixes. +- `refactor`: Changes that are not features or bug fixes. - `style`: A change in code style/format. - `tests`: About tests. @@ -111,7 +111,7 @@ Fixes #15. Link to any related issue in the Pull Request message. -During review, we recommend using fixups: +During the review, we recommend using fixups: ```bash # SHA is the SHA of the commit you want to fix diff --git a/README.md b/README.md index f446ee8a..b59516ef 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ dependencies = [ ] ``` -You can also explicitely depend on the handler: +You can also explicitly depend on the handler: ```toml title="pyproject.toml" # PEP 621 dependencies declaration @@ -59,11 +59,11 @@ dependencies = [ [Griffe](https://github.com/mkdocstrings/griffe). - **Support for type annotations:** Griffe collects your type annotations and *mkdocstrings* uses them - to display parameters types or return types. It is even able to automatically add cross-references - to other objects from your API, from the standard library or from third-party libraries! + to display parameter types or return types. It is even able to automatically add cross-references + to other objects from your API, from the standard library or third-party libraries! See [how to load inventories](https://mkdocstrings.github.io/usage/#cross-references-to-other-projects-inventories) to enable it. -- **Recursive documentation of Python objects:** just use the module dotted-path as identifier, and you get the full +- **Recursive documentation of Python objects:** just use the module dotted-path as an 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 @@ -77,7 +77,7 @@ dependencies = [ *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, + of Contents, which is nicely displayed by the Material theme. Thanks to *mkdocstrings* cross-reference ability, you can reference other objects within your docstrings, with the classic Markdown syntax: `[this object][package.module.object]` or directly with `[package.module.object][]` diff --git a/docs/customization.md b/docs/customization.md index d1d02cca..5e729675 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -124,7 +124,7 @@ See them [in the repository](https://github.com/mkdocstrings/python/tree/master/ See the general *mkdocstrings* documentation to learn how to override them: https://mkdocstrings.github.io/theming/#templates. In preparation for Jinja2 blocks, which will improve customization, -each one of these templates extends in fact a base version in `theme/_base`. Example: +each one of these templates extends a base version in `theme/_base`. Example: ```html+jinja title="theme/docstring/admonition.html" {% extends "_base/docstring/admonition.html" %} @@ -139,7 +139,7 @@ each one of these templates extends in fact a base version in `theme/_base`. Exa ``` It means you will be able to customize only *parts* of a template -without having to fully copy-paste it in your project: +without having to fully copy-paste it into your project: ```jinja title="templates/theme/docstring.html" {% extends "_base/docstring.html" %} diff --git a/docs/usage.md b/docs/usage.md index de28ca16..9fce14ad 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -74,7 +74,7 @@ plugins: do_something: false ``` -These options affect how the documentation is collected from sources and renderered: +These options affect how the documentation is collected from sources and rendered: headings, members, docstrings, etc. ::: mkdocstrings_handlers.python.handler.PythonHandler.default_config @@ -202,7 +202,7 @@ TIP: **This is the recommended method.** ``` Except for case 1, which is supported by default, **we strongly recommend -to set the path to your packages using this option, even if it works without it** +setting the path to your packages using this option, even if it works without it** (for example because your project manager automatically adds `src` to PYTHONPATH), to make sure anyone can build your docs from any location on their filesystem. @@ -211,7 +211,7 @@ to make sure anyone can build your docs from any location on their filesystem. WARNING: **This method has limitations.** This method might work for you, with your current setup, but not for others trying your build your docs with their own setup/environment. -We recommend to use the [`paths` method](#using-the-paths-option) instead. +We recommend using the [`paths` method](#using-the-paths-option) instead. You can take advantage of the usual Python loading mechanisms. In Bash and other shells, you can run your command like this @@ -270,7 +270,7 @@ In Bash and other shells, you can run your command like this WARNING: **This method has limitations.** This method might work for you, with your current setup, but not for others trying your build your docs with their own setup/environment. -We recommend to use the [`paths` method](#using-the-paths-option) instead. +We recommend using the [`paths` method](#using-the-paths-option) instead. Install your package in the current environment, and run MkDocs: From f5ea6fd81f7a531e8a97bb0e48267188d72936c1 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Mon, 23 Jan 2023 22:30:10 +0000 Subject: [PATCH 03/14] feat: Allow custom list of domains for inventories Issue mkdocstrings/mkdocstrings#510: https://github.com/mkdocstrings/mkdocstrings/issues/510 PR #49: https://github.com/mkdocstrings/python/pull/49 --- docs/schema.json | 10 +++++++++- pyproject.toml | 2 +- src/mkdocstrings_handlers/python/handler.py | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/schema.json b/docs/schema.json index a68b9041..0a341cb4 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -26,6 +26,14 @@ "base_url": { "title": "Base URL used to build references URLs.", "type": "string" + }, + "domains": { + "title": "Domains to import from the inventory.", + "description": "If not defined it will only import 'py' domain.", + "type": "array", + "items": { + "type": "string" + } } } } @@ -203,4 +211,4 @@ } }, "additionalProperties": false -} \ No newline at end of file +} diff --git a/pyproject.toml b/pyproject.toml index e32d921b..3e45c972 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "mkdocstrings>=0.19", + "mkdocstrings>=0.20", "griffe>=0.24", ] diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index f2732ee8..6107ed4c 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -169,6 +169,7 @@ def load_inventory( in_file: BinaryIO, url: str, base_url: Optional[str] = None, + domains: list[str] | None = None, **kwargs: Any, ) -> Iterator[Tuple[str, str]]: """Yield items and their URLs from an inventory file streamed from `in_file`. @@ -179,15 +180,17 @@ def load_inventory( 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. + domains: A list of domain strings to filter the inventory by, when not passed, "py" will be used. **kwargs: Ignore additional arguments passed from the config. Yields: Tuples of (item identifier, item URL). """ + domains = domains or ["py"] if base_url is None: base_url = posixpath.dirname(url) - for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values(): # noqa: WPS526 + for item in Inventory.parse_sphinx(in_file, domain_filter=domains).values(): # noqa: WPS526 yield item.name, posixpath.join(base_url, item.uri) def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: # noqa: D102,WPS231 From a6c55fb52f362dd49b1a7e334a631f6ea3b1b963 Mon Sep 17 00:00:00 2001 From: Jeremy Goh <30731072+thatlittleboy@users.noreply.github.com> Date: Sat, 4 Feb 2023 03:01:59 +0800 Subject: [PATCH 04/14] feat: Add show options for docstrings Issue mkdocstrings/mkdocstrings#466: https://github.com/mkdocstrings/mkdocstrings/issues/466 PR #56: https://github.com/mkdocstrings/python/pull/56 --- docs/schema.json | 62 ++++++++++++++++++- src/mkdocstrings_handlers/python/handler.py | 20 ++++++ .../templates/material/_base/docstring.html | 26 ++++---- 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/docs/schema.json b/docs/schema.json index 0a341cb4..9c5ef69d 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -140,8 +140,68 @@ "type": "boolean", "default": false }, + "show_docstring_attributes": { + "title": "Whether to display the \"Attributes\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_description": { + "title": "Whether to display the textual block (including admonitions) in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_examples": { + "title": "Whether to display the \"Examples\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_other_parameters": { + "title": "Whether to display the \"Other Parameters\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_parameters": { + "title": "Whether to display the \"Parameters\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_raises": { + "title": "Whether to display the \"Raises\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_receives": { + "title": "Whether to display the \"Receives\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_returns": { + "title": "Whether to display the \"Returns\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_warns": { + "title": "Whether to display the \"Warns\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, + "show_docstring_yields": { + "title": "Whether to display the \"Yields\" section in the object's docstring.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "boolean", + "default": true + }, "show_source": { - "title": "Show the source code of this object..", + "title": "Show the source code of this object.", "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", "type": "boolean", "default": true diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 6107ed4c..f32eafac 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -79,6 +79,16 @@ class PythonHandler(BaseHandler): "separate_signature": False, "line_length": 60, "merge_init_into_class": False, + "show_docstring_attributes": True, + "show_docstring_description": True, + "show_docstring_examples": True, + "show_docstring_other_parameters": True, + "show_docstring_parameters": True, + "show_docstring_raises": True, + "show_docstring_receives": True, + "show_docstring_returns": True, + "show_docstring_warns": True, + "show_docstring_yields": True, "show_source": True, "show_bases": True, "show_submodules": False, @@ -119,6 +129,16 @@ class PythonHandler(BaseHandler): line_length (int): Maximum line length when formatting code/signatures. Default: `60`. merge_init_into_class (bool): Whether to merge the `__init__` method into the class' signature and docstring. Default: `False`. show_if_no_docstring (bool): Show the object heading even if it has no docstring or children with docstrings. Default: `False`. + show_docstring_attributes (bool): Whether to display the "Attributes" section in the object's docstring. Default: `True`. + show_docstring_description (bool): Whether to display the textual block (including admonitions) in the object's docstring. Default: `True`. + show_docstring_examples (bool): Whether to display the "Examples" section in the object's docstring. Default: `True`. + show_docstring_other_parameters (bool): Whether to display the "Other Parameters" section in the object's docstring. Default: `True`. + show_docstring_parameters (bool): Whether to display the "Parameters" section in the object's docstring. Default: `True`. + show_docstring_raises (bool): Whether to display the "Raises" section in the object's docstring. Default: `True`. + show_docstring_receives (bool): Whether to display the "Receives" section in the object's docstring. Default: `True`. + show_docstring_returns (bool): Whether to display the "Returns" section in the object's docstring. Default: `True`. + show_docstring_warns (bool): Whether to display the "Warns" section in the object's docstring. Default: `True`. + show_docstring_yields (bool): Whether to display the "Yields" section in the object's docstring. Default: `True`. Attributes: Signatures/annotations options: annotations_path (str): The verbosity for annotations path: `brief` (recommended), or `source` (as written in the source). Default: `"brief"`. diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html b/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html index bd1b6963..1f840771 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html +++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring.html @@ -1,27 +1,27 @@ {% if docstring_sections %} {{ log.debug("Rendering docstring") }} {% for section in docstring_sections %} - {% if section.kind.value == "text" %} + {% if config.show_docstring_description and section.kind.value == "text" %} {{ section.value|convert_markdown(heading_level, html_id) }} - {% elif section.kind.value == "attributes" %} + {% elif config.show_docstring_attributes and section.kind.value == "attributes" %} {% include "docstring/attributes.html" with context %} - {% elif section.kind.value == "parameters" %} + {% elif config.show_docstring_parameters and section.kind.value == "parameters" %} {% include "docstring/parameters.html" with context %} - {% elif section.kind.value == "other parameters" %} + {% elif config.show_docstring_other_parameters and section.kind.value == "other parameters" %} {% include "docstring/other_parameters.html" with context %} - {% elif section.kind.value == "raises" %} + {% elif config.show_docstring_raises and section.kind.value == "raises" %} {% include "docstring/raises.html" with context %} - {% elif section.kind.value == "warns" %} - {% include "docstring/warns.html" with context %} - {% elif section.kind.value == "yields" %} + {% elif config.show_docstring_warns and section.kind.value == "warns" %} + {% include "docstring/warns.html" with context %} + {% elif config.show_docstring_yields and section.kind.value == "yields" %} {% include "docstring/yields.html" with context %} - {% elif section.kind.value == "receives" %} - {% include "docstring/receives.html" with context %} - {% elif section.kind.value == "returns" %} + {% elif config.show_docstring_receives and section.kind.value == "receives" %} + {% include "docstring/receives.html" with context %} + {% elif config.show_docstring_returns and section.kind.value == "returns" %} {% include "docstring/returns.html" with context %} - {% elif section.kind.value == "examples" %} + {% elif config.show_docstring_examples and section.kind.value == "examples" %} {% include "docstring/examples.html" with context %} - {% elif section.kind.value == "admonition" %} + {% elif config.show_docstring_description and section.kind.value == "admonition" %} {% include "docstring/admonition.html" with context %} {% endif %} {% endfor %} From 50d541729223c154482958b8c18b3a06ad6032a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 3 Mar 2023 14:14:33 +0100 Subject: [PATCH 05/14] chore: Include namespace package in coverage --- config/coverage.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/config/coverage.ini b/config/coverage.ini index 418d1bb9..dbd47711 100644 --- a/config/coverage.ini +++ b/config/coverage.ini @@ -11,6 +11,7 @@ equivalent = __pypackages__/ [coverage:report] +include_namespace_packages = true precision = 2 omit = src/*/__init__.py From 9164742f87362e8241dea11bec0fd96f6b9d9dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 3 Mar 2023 14:15:02 +0100 Subject: [PATCH 06/14] refactor: Log (debug) unresolved aliases --- src/mkdocstrings_handlers/python/handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index afb8ed98..718e9d62 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -217,6 +217,7 @@ def collect(self, identifier: str, config: dict) -> CollectorItem: # noqa: D102 unresolved, iterations = loader.resolve_aliases(implicit=False, external=False) if unresolved: logger.warning(f"{len(unresolved)} aliases were still unresolved after {iterations} iterations") + logger.debug(f"Unresolved aliases: {', '.join(sorted(unresolved))}") try: doc_object = self._modules_collection[identifier] From 36002cb9c89fba35d23afb07a866dd8c6877f742 Mon Sep 17 00:00:00 2001 From: Gilad <88031955+gilfree@users.noreply.github.com> Date: Sun, 5 Mar 2023 17:32:44 +0200 Subject: [PATCH 07/14] feat: Allow pre-loading modules Add option to preload modules. Preloading modules allows to render members of objects that originate from other packages than their parent. Direct members of modules must be listed in `__all__`. This option typically allows to render aliases, by collecting their parent module (package) which allows to resolve these aliases. Issue mkdocstrings/mkdocstrings#503: https://github.com/mkdocstrings/mkdocstrings/issues/503 PR #60: https://github.com/mkdocstrings/python/pull/60 --- docs/schema.json | 8 ++++++++ src/mkdocstrings_handlers/python/handler.py | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/schema.json b/docs/schema.json index 9c5ef69d..e14048d2 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -262,6 +262,14 @@ "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", "enum": ["brief", "source"], "default": "brief" + }, + "preload_modules": { + "title": "Pre-load modules. It permits to resolve aliases pointing to these modules (packages), and therefore render members of an object that are external to the given object (originating from another package).", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", + "type": "array", + "items": { + "type":"string" + } } }, "additionalProperties": false diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index f32eafac..b69ba08f 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -99,6 +99,7 @@ class PythonHandler(BaseHandler): "members": None, "filters": ["!^_[^_]"], "annotations_path": "brief", + "preload_modules": None, } """ Attributes: Headings options: @@ -150,6 +151,16 @@ class PythonHandler(BaseHandler): Attributes: Additional options: show_bases (bool): Show the base classes of a class. Default: `True`. show_source (bool): Show the source code of this object. Default: `True`. + preload_modules (list[str] | None): Pre-load modules that are + not specified directly in autodoc instructions (`::: identifier`). + It is useful when you want to render documentation for a particular member of an object, + and this member is imported from another package than its parent. + + For an imported member to be rendered, you need to add it to the `__all__` attribute + of the importing module. + + The modules must be listed as an array of strings. Default: `None`. + """ # noqa: E501 def __init__( @@ -235,7 +246,10 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: modules_collection=self._modules_collection, lines_collection=self._lines_collection, ) - try: + try: # noqa: WPS229 we expect one type of exception, and want to fail on the first one + for pre_loaded_module in final_config.get("preload_modules") or []: + if pre_loaded_module not in self._modules_collection: + loader.load_module(pre_loaded_module) loader.load_module(module_name) except ImportError as error: raise CollectionError(str(error)) from error From 02052e248b125a113ab788faa9a075adbdc92ca6 Mon Sep 17 00:00:00 2001 From: Gilad <88031955+gilfree@users.noreply.github.com> Date: Tue, 7 Mar 2023 21:19:37 +0200 Subject: [PATCH 08/14] feat: Allow resolving alias to external modules PR #61: https://github.com/mkdocstrings/python/pull/61 Follow-up of PR #60: https://github.com/mkdocstrings/python/pull/60 Co-authored-by: gilfree --- docs/schema.json | 6 ++++++ docs/usage.md | 9 +++++++++ src/mkdocstrings_handlers/python/handler.py | 6 ++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/schema.json b/docs/schema.json index e14048d2..2d2a29f7 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -49,6 +49,12 @@ "format": "path" } }, + "load_external_modules": { + "title": "Load external modules to resolve aliases.", + "markdownDescription": "https://mkdocstrings.github.io/python/usage/#global-only-options", + "type": "boolean", + "default": false + }, "options": { "title": "Options for collecting and rendering objects.", "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", diff --git a/docs/usage.md b/docs/usage.md index 9fce14ad..332a72ad 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -52,6 +52,15 @@ Some options are **global only**, and go directly under the handler's name. More details at [Finding modules](#finding-modules). +- `load_external_modules`: + this option allows resolving aliases to any external module. + Enabling this option will tell handler that when it encounters an import that is made public + through the `__all__` variable, it will resolve it recursively to *any* module. + **Use with caution:** this can load a *lot* of modules, slowing down your build + or triggering errors that we do not yet handle. + **We recommend using the `preload_modules` option instead**, + which acts as an include-list rather than as include-all. + ## Global/local options The other options can be used both globally *and* locally, under the `options` key. diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index b69ba08f..eb3c3147 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -100,6 +100,7 @@ class PythonHandler(BaseHandler): "filters": ["!^_[^_]"], "annotations_path": "brief", "preload_modules": None, + "load_external_modules": False, } """ Attributes: Headings options: @@ -253,8 +254,9 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: loader.load_module(module_name) except ImportError as error: raise CollectionError(str(error)) from error - - unresolved, iterations = loader.resolve_aliases(implicit=False, external=False) + unresolved, iterations = loader.resolve_aliases( + implicit=False, external=final_config["load_external_modules"] + ) if unresolved: logger.debug(f"{len(unresolved)} aliases were still unresolved after {iterations} iterations") logger.debug(f"Unresolved aliases: {', '.join(sorted(unresolved))}") From 80480b081a46aada65158a7474bf1f1972acc30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 1 Apr 2023 16:35:22 +0200 Subject: [PATCH 09/14] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/workflows/ci.yml | 5 +- CODE_OF_CONDUCT.md | 151 +++++++++++----- CONTRIBUTING.md | 37 +++- Makefile | 4 +- config/black.toml | 3 + config/coverage.ini | 3 + config/flake8.ini | 108 ------------ config/pytest.ini | 6 + config/ruff.toml | 100 +++++++++++ docs/credits.md | 5 + duties.py | 286 +++++++++++-------------------- mkdocs.yml | 8 +- pyproject.toml | 43 +---- scripts/gen_credits.py | 143 +++++++++------- {docs => scripts}/gen_ref_nav.py | 4 +- scripts/multirun.sh | 26 --- scripts/setup.sh | 36 +--- 18 files changed, 459 insertions(+), 511 deletions(-) create mode 100644 config/black.toml delete mode 100644 config/flake8.ini create mode 100644 config/ruff.toml rename {docs => scripts}/gen_ref_nav.py (88%) mode change 100755 => 100644 delete mode 100755 scripts/multirun.sh diff --git a/.copier-answers.yml b/.copier-answers.yml index 8cacdedb..f7a640c0 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.10.6 +_commit: 0.11.2 _src_path: gh:pawamoy/copier-pdm author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9abeb46..f7fd9bb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: python-version: "3.8" - name: Resolving dependencies - run: pdm lock + run: pdm lock -v - name: Install dependencies run: pdm install -G duty -G docs -G quality -G typing -G security @@ -74,6 +74,9 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Resolving dependencies + run: pdm lock -v + - name: Install dependencies run: pdm install --no-editable -G duty -G tests -G docs diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3994752a..fe3eefbf 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,73 +2,132 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or email address, + without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned with this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at pawamoy@pm.me. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality concerning the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community leaders responsible for enforcement at +pawamoy@pm.me. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b793ad3..488292a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,14 +19,14 @@ make setup > you'll need to install > [PDM](https://github.com/pdm-project/pdm) > manually. -> +> > You can install it with: -> +> > ```bash > python3 -m pip install --user pipx > pipx install pdm > ``` -> +> > Now you can try running `make setup` again, > or simply `pdm install`. @@ -75,8 +75,9 @@ Don't bother updating the changelog, we will take care of this. ## Commit message convention -Commits messages must follow the -[Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message): +Commit messages must follow our convention based on the +[Angular style](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message) +or the [Karma convention](https://karma-runner.github.io/4.0/dev/git-commit-msg.html): ``` [(scope)]: Subject @@ -84,11 +85,17 @@ Commits messages must follow the [Body] ``` +**Subject and body must be valid Markdown.** +Subject must have proper casing (uppercase for first letter +if it makes sense), but no dot at the end, and no punctuation +in general. + Scope and body are optional. Type can be: - `build`: About packaging, building wheels, etc. - `chore`: About packaging or repo/files management. - `ci`: About Continuous Integration. +- `deps`: Dependencies update. - `docs`: About documentation. - `feat`: New feature. - `fix`: Bug fix. @@ -97,16 +104,28 @@ Scope and body are optional. Type can be: - `style`: A change in code style/format. - `tests`: About tests. -**Subject (and body) must be valid Markdown.** -If you write a body, please add issues references at the end: +If you write a body, please add trailers at the end +(for example issues and PR references, or co-authors), +without relying on GitHub's flavored Markdown: ``` Body. -References: #10, #11. -Fixes #15. +Issue #10: https://github.com/namespace/project/issues/10 +Related to PR namespace/other-project#15: https://github.com/namespace/other-project/pull/15 ``` +These "trailers" must appear at the end of the body, +without any blank lines between them. The trailer title +can contain any character except colons `:`. +We expect a full URI for each trailer, not just GitHub autolinks +(for example, full GitHub URLs for commits and issues, +not the hash or the #issue-number). + +We do not enforce a line length on commit messages summary and body, +but please avoid very long summaries, and very long lines in the body, +unless they are part of code blocks that must not be wrapped. + ## Pull requests guidelines Link to any related issue in the Pull Request message. diff --git a/Makefile b/Makefile index 58291575..b034ffff 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ setup: .PHONY: check check: - @bash scripts/multirun.sh duty check-quality check-types check-docs + @pdm multirun duty check-quality check-types check-docs @$(DUTY) check-dependencies .PHONY: $(BASIC_DUTIES) @@ -50,4 +50,4 @@ $(BASIC_DUTIES): .PHONY: $(QUALITY_DUTIES) $(QUALITY_DUTIES): - @bash scripts/multirun.sh duty $@ $(call args,$@) + @pdm multirun duty $@ $(call args,$@) diff --git a/config/black.toml b/config/black.toml new file mode 100644 index 00000000..d24affe5 --- /dev/null +++ b/config/black.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 120 +exclude = "tests/fixtures" diff --git a/config/coverage.ini b/config/coverage.ini index dbd47711..19b34d9b 100644 --- a/config/coverage.ini +++ b/config/coverage.ini @@ -17,6 +17,9 @@ omit = src/*/__init__.py src/*/__main__.py tests/__init__.py +exclude_lines = + pragma: no cover + if TYPE_CHECKING [coverage:json] output = htmlcov/coverage.json diff --git a/config/flake8.ini b/config/flake8.ini deleted file mode 100644 index 4126bd51..00000000 --- a/config/flake8.ini +++ /dev/null @@ -1,108 +0,0 @@ -[flake8] -exclude = fixtures,site -max-line-length = 132 -docstring-convention = google -ban-relative-imports = true -ignore = - # redundant with W0622 (builtin override), which is more precise about line number - A001 - # missing docstring in magic method - D105 - # multi-line docstring summary should start at the first line - D212 - # does not support Parameters sections - D417 - # whitespace before ':' (incompatible with Black) - E203 - # redundant with E0602 (undefined variable) - F821 - # black already deals with quoting - Q000 - # use of assert - S101 - # we are not parsing XML - S405 - # line break before binary operator (incompatible with Black) - W503 - # two-lowercase-letters variable DO conform to snake_case naming style - C0103 - # redundant with D102 (missing docstring) - C0116 - # line too long - C0301 - # too many instance attributes - R0902 - # too few public methods - R0903 - # too many public methods - R0904 - # too many branches - R0912 - # too many methods - R0913 - # too many local variables - R0914 - # too many statements - R0915 - # redundant with F401 (unused import) - W0611 - # lazy formatting for logging calls - 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 - # multiline attribute docstring - WPS462 - # implicit dict.get usage (generally false-positive) - WPS529 diff --git a/config/pytest.ini b/config/pytest.ini index ad72bbe6..5a493959 100644 --- a/config/pytest.ini +++ b/config/pytest.ini @@ -14,3 +14,9 @@ addopts = --cov-config config/coverage.ini testpaths = tests + +# action:message_regex:warning_class:module_regex:line +filterwarnings = + error + # TODO: remove once pytest-xdist 4 is released + ignore:.*rsyncdir:DeprecationWarning:xdist diff --git a/config/ruff.toml b/config/ruff.toml new file mode 100644 index 00000000..55bab1a8 --- /dev/null +++ b/config/ruff.toml @@ -0,0 +1,100 @@ +target-version = "py37" +line-length = 132 +exclude = [ + "fixtures", + "site", +] +select = [ + "A", + "ANN", + "ARG", + "B", + "BLE", + "C", + "C4", + "COM", + "D", + "DTZ", + "E", + "ERA", + "EXE", + "F", + "FBT", + "G", + "I", + "ICN", + "INP", + "ISC", + "N", + "PGH", + "PIE", + "PL", + "PLC", + "PLE", + "PLR", + "PLW", + "PT", + "PYI", + "Q", + "RUF", + "RSE", + "RET", + "S", + "SIM", + "SLF", + "T", + "T10", + "T20", + "TCH", + "TID", + "TRY", + "UP", + "W", + "YTT", +] +ignore = [ + "A001", # Variable is shadowing a Python builtin + "ANN101", # Missing type annotation for self + "ANN102", # Missing type annotation for cls + "ANN204", # Missing return type annotation for special method __str__ + "ANN401", # Dynamically typed expressions (typing.Any) are disallowed + "ARG005", # Unused lambda argument + "C901", # Too complex + "D105", # Missing docstring in magic method + "D417", # Missing argument description in the docstring + "E501", # Line too long + "G004", # Logging statement uses f-string + "PLR0911", # Too many return statements + "PLR0912", # Too many branches + "PLR0913", # Too many arguments to function call + "PLR0915", # Too many statements + "SLF001", # Private member accessed + "TRY003", # Avoid specifying long messages outside the exception class +] + +[per-file-ignores] +"src/*/cli.py" = [ + "T201", # Print statement +] +"scripts/*.py" = [ + "INP001", # File is part of an implicit namespace package + "T201", # Print statement +] +"tests/*.py" = [ + "ARG005", # Unused lambda argument + "FBT001", # Boolean positional arg in function definition + "PLR2004", # Magic value used in comparison + "S101", # Use of assert detected +] + +[flake8-quotes] +docstring-quotes = "double" + +[flake8-tidy-imports] +ban-relative-imports = "all" + +[isort] +known-first-party = ["mkdocstrings_handlers"] + +[pydocstyle] +convention = "google" diff --git a/docs/credits.md b/docs/credits.md index 02e1dd81..9db45873 100644 --- a/docs/credits.md +++ b/docs/credits.md @@ -1,3 +1,8 @@ +--- +hide: +- toc +--- + ```python exec="yes" --8<-- "scripts/gen_credits.py" ``` diff --git a/duties.py b/duties.py index 1b64bcda..b4ef4474 100644 --- a/duties.py +++ b/duties.py @@ -1,153 +1,90 @@ """Development tasks.""" -import importlib +from __future__ import annotations + import os -import re import sys -from io import StringIO from pathlib import Path -from typing import List, Optional, Pattern -from urllib.request import urlopen +from typing import TYPE_CHECKING from duty import duty +from duty.callables import black, blacken_docs, coverage, lazy, mkdocs, mypy, pytest, ruff, safety + +if TYPE_CHECKING: + from duty.context import Context -PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "docs")) +PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "scripts")) 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", ""} WINDOWS = os.name == "nt" PTY = not WINDOWS and not CI +MULTIRUN = os.environ.get("PDM_MULTIRUN", "0") == "1" -def _latest(lines: List[str], regex: Pattern) -> Optional[str]: - for line in lines: - match = regex.search(line) - if match: - return match.groupdict()["version"] - return None - - -def _unreleased(versions, last_release): - for index, version in enumerate(versions): - if version.tag == last_release: - return versions[:index] - return versions - - -def update_changelog( - inplace_file: str, - marker: str, - version_regex: str, - template_url: str, -) -> None: - """ - Update the given changelog file in place. - - Arguments: - inplace_file: The file to update in-place. - marker: The line after which to insert new contents. - version_regex: A regular expression to find currently documented versions in the file. - template_url: The URL to the Jinja template used to render contents. - """ - from git_changelog.build import Changelog - from git_changelog.commit import AngularStyle - from jinja2.sandbox import SandboxedEnvironment - - AngularStyle.DEFAULT_RENDER.insert(0, AngularStyle.TYPES["build"]) - env = SandboxedEnvironment(autoescape=False) - template_text = urlopen(template_url).read().decode("utf8") # noqa: S310 - template = env.from_string(template_text) - changelog = Changelog(".", style="angular") - - if len(changelog.versions_list) == 1: - last_version = changelog.versions_list[0] - if last_version.planned_tag is None: - planned_tag = "0.1.0" - last_version.tag = planned_tag - last_version.url += planned_tag - last_version.compare_url = last_version.compare_url.replace("HEAD", planned_tag) - - 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) - rendered = template.render(changelog=changelog, inplace=True) - lines[lines.index(marker)] = rendered - - with open(inplace_file, "w") as changelog_file: # noqa: WPS440 - changelog_file.write("\n".join(lines).rstrip("\n") + "\n") +def pyprefix(title: str) -> str: # noqa: D103 + if MULTIRUN: + prefix = f"(python{sys.version_info.major}.{sys.version_info.minor})" + return f"{prefix:14}{title}" + return title @duty -def changelog(ctx): - """ - Update the changelog in-place with latest commits. +def changelog(ctx: Context) -> None: + """Update the changelog in-place with latest commits. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ - commit = "166758a98d5e544aaa94fda698128e00733497f4" - template_url = f"https://raw.githubusercontent.com/pawamoy/jinja-templates/{commit}/keepachangelog.md" + from git_changelog.cli import build_and_render + + git_changelog = lazy("git_changelog")(build_and_render) ctx.run( - update_changelog, - kwargs={ - "inplace_file": "CHANGELOG.md", - "marker": "", - "version_regex": r"^## \[v?(?P[^\]]+)", - "template_url": template_url, - }, + git_changelog( + repository=".", + output="CHANGELOG.md", + convention="angular", + template="keepachangelog", + parse_trailers=True, + parse_refs=False, + sections=("build", "deps", "feat", "fix", "refactor"), + bump_latest=True, + in_place=True, + ), title="Updating changelog", - pty=PTY, ) @duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies"]) -def check(ctx): - """ - Check it all! +def check(ctx: Context) -> None: # noqa: ARG001 + """Check it all! - Arguments: + Parameters: ctx: The context instance (passed automatically). """ @duty -def check_quality(ctx, files=PY_SRC): - """ - Check the code quality. +def check_quality(ctx: Context) -> None: + """Check the code quality. - Arguments: + Parameters: ctx: The context instance (passed automatically). - files: The files to check. """ - ctx.run(f"flake8 --config=config/flake8.ini {files}", title="Checking code quality", pty=PTY) + ctx.run( + ruff.check(*PY_SRC_LIST, config="config/ruff.toml"), + title=pyprefix("Checking code quality"), + ) @duty -def check_dependencies(ctx): - """ - Check for vulnerabilities in dependencies. +def check_dependencies(ctx: Context) -> None: + """Check for vulnerabilities in dependencies. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ - # undo possible patching - # see https://github.com/pyupio/safety/issues/348 - for module in sys.modules: # noqa: WPS528 - if module.startswith("safety.") or module == "safety": - del sys.modules[module] # noqa: WPS420 - - importlib.invalidate_caches() - - # reload original, unpatched safety - from safety.formatter import SafetyFormatter - from safety.safety import calculate_remediations - from safety.safety import check as safety_check - from safety.util import read_requirements - # retrieve the list of dependencies requirements = ctx.run( ["pdm", "export", "-f", "requirements", "--without-hashes"], @@ -155,57 +92,39 @@ def check_dependencies(ctx): allow_overrides=False, ) - # check using safety as a library - def safety(): # noqa: WPS430 - packages = list(read_requirements(StringIO(requirements))) - vulns, db_full = safety_check(packages=packages, ignore_vulns="") - remediations = calculate_remediations(vulns, db_full) - output_report = SafetyFormatter("text").render_vulnerabilities( - announcements=[], - vulnerabilities=vulns, - remediations=remediations, - full=True, - packages=packages, - ) - if vulns: - print(output_report) - return False - return True - - ctx.run(safety, title="Checking dependencies") + ctx.run(safety.check(requirements), title="Checking dependencies") @duty -def check_docs(ctx): - """ - Check if the documentation builds correctly. +def check_docs(ctx: Context) -> None: + """Check if the documentation builds correctly. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ 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) + ctx.run(mkdocs.build(strict=True), title=pyprefix("Building documentation")) -@duty # noqa: WPS231 -def check_types(ctx): # noqa: WPS231 - """ - Check that the code is correctly typed. +@duty +def check_types(ctx: Context) -> None: + """Check that the code is correctly typed. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ - os.environ["MYPYPATH"] = "src" - ctx.run(f"mypy --config-file config/mypy.ini {PY_SRC}", title="Type-checking", pty=PTY) + ctx.run( + mypy.run(*PY_SRC_LIST, config_file="config/mypy.ini"), + title=pyprefix("Type-checking"), + ) @duty(silent=True) -def clean(ctx): - """ - Delete temporary files. +def clean(ctx: Context) -> None: + """Delete temporary files. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ ctx.run("rm -rf .coverage*") @@ -222,63 +141,65 @@ def clean(ctx): @duty -def docs(ctx): - """ - Build the documentation locally. +def docs(ctx: Context) -> None: + """Build the documentation locally. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ - ctx.run("mkdocs build", title="Building documentation") + ctx.run(mkdocs.build, title="Building documentation") @duty -def docs_serve(ctx, host="127.0.0.1", port=8000): - """ - Serve the documentation (localhost:8000). +def docs_serve(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: + """Serve the documentation (localhost:8000). - Arguments: + Parameters: ctx: The context instance (passed automatically). host: The host to serve the docs from. port: The port to serve the docs on. """ - ctx.run(f"mkdocs serve -a {host}:{port}", title="Serving documentation", capture=False) + ctx.run( + mkdocs.serve(dev_addr=f"{host}:{port}"), + title="Serving documentation", + capture=False, + ) @duty -def docs_deploy(ctx): - """ - Deploy the documentation on GitHub pages. +def docs_deploy(ctx: Context) -> None: + """Deploy the documentation on GitHub pages. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ - ctx.run("mkdocs gh-deploy", title="Deploying documentation") + ctx.run(mkdocs.gh_deploy, title="Deploying documentation") @duty -def format(ctx): - """ - Run formatting tools on the code. +def format(ctx: Context) -> None: + """Run formatting tools on the code. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ ctx.run( - f"autoflake -ir --exclude tests/fixtures --remove-all-unused-imports {PY_SRC}", - title="Removing unused imports", - pty=PTY, + ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True), + title="Auto-fixing code", + ) + ctx.run(black.run(*PY_SRC_LIST, config="config/black.toml"), title="Formatting code") + ctx.run( + blacken_docs.run(*PY_SRC_LIST, "docs", exts=["py", "md"], line_length=120), + title="Formatting docs", + nofail=True, ) - ctx.run(f"isort {PY_SRC}", title="Ordering imports", pty=PTY) - ctx.run(f"black {PY_SRC}", title="Formatting code", pty=PTY) @duty -def release(ctx, version): - """ - Release a new Python package. +def release(ctx: Context, version: str) -> None: + """Release a new Python package. - Arguments: + Parameters: ctx: The context instance (passed automatically). version: The new version number to use. """ @@ -293,32 +214,29 @@ def release(ctx, version): docs_deploy.run() -@duty(silent=True) -def coverage(ctx): - """ - Report coverage as text and HTML. +@duty(silent=True, aliases=["coverage"]) +def cov(ctx: Context) -> None: + """Report coverage as text and HTML. - Arguments: + Parameters: ctx: The context instance (passed automatically). """ - ctx.run("coverage combine", nofail=True) - ctx.run("coverage report --rcfile=config/coverage.ini", capture=False) - ctx.run("coverage html --rcfile=config/coverage.ini") + ctx.run(coverage.combine, nofail=True) + ctx.run(coverage.report(rcfile="config/coverage.ini"), capture=False) + ctx.run(coverage.html(rcfile="config/coverage.ini")) @duty -def test(ctx, match: str = ""): - """ - Run the test suite. +def test(ctx: Context, match: str = "") -> None: + """Run the test suite. - Arguments: + Parameters: ctx: The context instance (passed automatically). match: A pytest expression to filter selected tests. """ py_version = f"{sys.version_info.major}{sys.version_info.minor}" os.environ["COVERAGE_FILE"] = f".coverage.{py_version}" ctx.run( - ["pytest", "-c", "config/pytest.ini", "-n", "auto", "-k", match, "tests"], - title="Running tests", - pty=PTY, + pytest.run("-n", "auto", "tests", config_file="config/pytest.ini", select=match), + title=pyprefix("Running tests"), ) diff --git a/mkdocs.yml b/mkdocs.yml index 6b851eed..f07a73d2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,8 @@ theme: - navigation.tabs - navigation.tabs.sticky - navigation.top + - search.highlight + - search.suggest palette: - media: "(prefers-color-scheme: light)" scheme: default @@ -69,9 +71,9 @@ plugins: - markdown-exec - gen-files: scripts: - - docs/gen_ref_nav.py + - scripts/gen_ref_nav.py - literate-nav: - nav_file: SUMMARY.md + nav_file: SUMMARY.txt - coverage - section-index - mkdocstrings: @@ -95,5 +97,7 @@ extra: social: - icon: fontawesome/brands/github link: https://github.com/pawamoy + - icon: fontawesome/brands/mastodon + link: https://fosstodon.org/@pawamoy - icon: fontawesome/brands/twitter link: https://twitter.com/pawamoy diff --git a/pyproject.toml b/pyproject.toml index 3e45c972..673d3c8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ includes = ["src/mkdocstrings_handlers"] editable-backend = "editables" [tool.pdm.dev-dependencies] -duty = ["duty>=0.7"] +duty = ["duty>=0.8"] docs = [ "mkdocs>=1.3", "mkdocs-coverage>=0.2", @@ -64,32 +64,13 @@ docs = [ "markdown-exec>=0.5", "toml>=0.10", ] -format = [ - "autoflake>=1.4", - "black>=21.10b0", - "isort>=5.10", -] maintain = [ - "git-changelog>=0.4", + "black>=23.1", + "blacken-docs>=1.13", + "git-changelog>=1.0", ] quality = [ - # TODO: remove once importlib-metadata version conflict is resolved - "importlib-metadata<5; python_version < '3.8'", - "flake8>=4; python_version >= '3.8'", - - "darglint>=1.8", - "flake8-bandit>=2.1", - "flake8-black>=0.2", - "flake8-bugbear>=21.9", - "flake8-builtins>=1.5", - "flake8-comprehensions>=3.7", - "flake8-docstrings>=1.6", - "flake8-pytest-style>=1.5", - "flake8-string-format>=0.3", - "flake8-tidy-imports>=4.5", - "flake8-variables-names>=0.0", - "pep8-naming>=0.12", - "wps-light>=0.15", + "ruff>=0.0.246", ] tests = [ "pytest>=6.2", @@ -103,17 +84,3 @@ typing = [ "types-toml>=0.10", ] security = ["safety>=2"] - -[tool.black] -line-length = 120 -exclude = "tests/fixtures" - -[tool.isort] -line_length = 120 -not_skip = "__init__.py" -multi_line_output = 3 -force_single_line = false -balanced_wrapping = true -default_section = "THIRDPARTY" -known_first_party = "mkdocstrings_handlers" -include_trailing_comma = true diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index 10f5647d..7f59f8f9 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -1,16 +1,22 @@ +"""Script to generate the project's credits.""" + +from __future__ import annotations + import re +import sys from itertools import chain from pathlib import Path from textwrap import dedent +from typing import Mapping, cast import toml from jinja2 import StrictUndefined from jinja2.sandbox import SandboxedEnvironment -try: - from importlib.metadata import metadata, PackageNotFoundError -except ImportError: - from importlib_metadata import metadata, PackageNotFoundError +if sys.version_info < (3, 8): + from importlib_metadata import PackageNotFoundError, metadata +else: + from importlib.metadata import PackageNotFoundError, metadata project_dir = Path(".") pyproject = toml.load(project_dir / "pyproject.toml") @@ -21,31 +27,33 @@ project_name = project["name"] regex = re.compile(r"(?P[\w.-]+)(?P.*)$") -def get_license(pkg_name): + +def _get_license(pkg_name: str) -> str: try: data = metadata(pkg_name) except PackageNotFoundError: return "?" - license = data.get("License", "").strip() - multiple_lines = bool(license.count("\n")) + license_name = cast(dict, data).get("License", "").strip() + multiple_lines = bool(license_name.count("\n")) # TODO: remove author logic once all my packages licenses are fixed author = "" - if multiple_lines or not license or license == "UNKNOWN": - for header, value in data.items(): + if multiple_lines or not license_name or license_name == "UNKNOWN": + for header, value in cast(dict, data).items(): if header == "Classifier" and value.startswith("License ::"): - license = value.rsplit("::", 1)[1].strip() + license_name = value.rsplit("::", 1)[1].strip() elif header == "Author-email": author = value - if license == "Other/Proprietary License" and "pawamoy" in author: - license = "ISC" - return license or "?" + if license_name == "Other/Proprietary License" and "pawamoy" in author: + license_name = "ISC" + return license_name or "?" -def get_deps(base_deps): + +def _get_deps(base_deps: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, str]]: deps = {} for dep in base_deps: - parsed = regex.match(dep).groupdict() + parsed = regex.match(dep).groupdict() # type: ignore[union-attr] dep_name = parsed["dist"].lower() - deps[dep_name] = {"license": get_license(dep_name), **parsed, **lock_pkgs[dep_name]} + deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]} again = True while again: @@ -53,58 +61,63 @@ def get_deps(base_deps): for pkg_name in lock_pkgs: if pkg_name in deps: for pkg_dependency in lock_pkgs[pkg_name].get("dependencies", []): - parsed = regex.match(pkg_dependency).groupdict() + parsed = regex.match(pkg_dependency).groupdict() # type: ignore[union-attr] dep_name = parsed["dist"].lower() - if dep_name not in deps: - deps[dep_name] = {"license": get_license(dep_name), **parsed, **lock_pkgs[dep_name]} + if dep_name not in deps and dep_name != project["name"]: + deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]} again = True return deps -dev_dependencies = get_deps(chain(*pdm.get("dev-dependencies", {}).values())) -prod_dependencies = get_deps( - chain( - project.get("dependencies", []), - chain(*project.get("optional-dependencies", {}).values()), + +def _render_credits() -> str: + dev_dependencies = _get_deps(chain(*pdm.get("dev-dependencies", {}).values())) # type: ignore[arg-type] + prod_dependencies = _get_deps( + chain( # type: ignore[arg-type] + project.get("dependencies", []), + chain(*project.get("optional-dependencies", {}).values()), + ), ) -) - -template_data = { - "project_name": project_name, - "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: dep["name"]), - "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: dep["name"]), - "more_credits": "http://pawamoy.github.io/credits/", -} -template_text = dedent( - """ - These projects were used to build `{{ project_name }}`. **Thank you!** - - [`python`](https://www.python.org/) | - [`pdm`](https://pdm.fming.dev/) | - [`copier-pdm`](https://github.com/pawamoy/copier-pdm) - - {% macro dep_line(dep) -%} - [`{{ dep.name }}`](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }} - {%- endmacro %} - - ### Runtime dependencies - - Project | Summary | Version (accepted) | Version (last resolved) | License - ------- | ------- | ------------------ | ----------------------- | ------- - {% for dep in prod_dependencies -%} - {{ dep_line(dep) }} - {% endfor %} - - ### Development dependencies - - Project | Summary | Version (accepted) | Version (last resolved) | License - ------- | ------- | ------------------ | ----------------------- | ------- - {% for dep in dev_dependencies -%} - {{ dep_line(dep) }} - {% endfor %} - - {% if more_credits %}**[More credits from the author]({{ more_credits }})**{% endif %} - """ -) -jinja_env = SandboxedEnvironment(undefined=StrictUndefined) -print(jinja_env.from_string(template_text).render(**template_data)) + + template_data = { + "project_name": project_name, + "prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: dep["name"]), + "dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: dep["name"]), + "more_credits": "http://pawamoy.github.io/credits/", + } + template_text = dedent( + """ + These projects were used to build `{{ project_name }}`. **Thank you!** + + [`python`](https://www.python.org/) | + [`pdm`](https://pdm.fming.dev/) | + [`copier-pdm`](https://github.com/pawamoy/copier-pdm) + + {% macro dep_line(dep) -%} + [`{{ dep.name }}`](https://pypi.org/project/{{ dep.name }}/) | {{ dep.summary }} | {{ ("`" ~ dep.spec ~ "`") if dep.spec else "" }} | `{{ dep.version }}` | {{ dep.license }} + {%- endmacro %} + + ### Runtime dependencies + + Project | Summary | Version (accepted) | Version (last resolved) | License + ------- | ------- | ------------------ | ----------------------- | ------- + {% for dep in prod_dependencies -%} + {{ dep_line(dep) }} + {% endfor %} + + ### Development dependencies + + Project | Summary | Version (accepted) | Version (last resolved) | License + ------- | ------- | ------------------ | ----------------------- | ------- + {% for dep in dev_dependencies -%} + {{ dep_line(dep) }} + {% endfor %} + + {% if more_credits %}**[More credits from the author]({{ more_credits }})**{% endif %} + """, + ) + jinja_env = SandboxedEnvironment(undefined=StrictUndefined) + return jinja_env.from_string(template_text).render(**template_data) + + +print(_render_credits()) diff --git a/docs/gen_ref_nav.py b/scripts/gen_ref_nav.py old mode 100755 new mode 100644 similarity index 88% rename from docs/gen_ref_nav.py rename to scripts/gen_ref_nav.py index 14f0f4ad..97d8b5a7 --- a/docs/gen_ref_nav.py +++ b/scripts/gen_ref_nav.py @@ -17,7 +17,7 @@ parts = parts[:-1] doc_path = doc_path.with_name("index.md") full_doc_path = full_doc_path.with_name("index.md") - elif parts[-1] == "__main__": + elif parts[-1].startswith("_"): continue nav[parts] = doc_path.as_posix() @@ -28,5 +28,5 @@ mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path) -with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: +with mkdocs_gen_files.open("reference/SUMMARY.txt", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) diff --git a/scripts/multirun.sh b/scripts/multirun.sh deleted file mode 100755 index a55d1746..00000000 --- a/scripts/multirun.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -e - -PYTHON_VERSIONS="${PYTHON_VERSIONS-3.7 3.8 3.9 3.10 3.11}" - -restore_previous_python_version() { - if pdm use -f "$1" &>/dev/null; then - echo "> Restored previous Python version: ${1##*/}" - fi -} - -if [ -n "${PYTHON_VERSIONS}" ]; then - old_python_version="$(pdm config python.path)" - echo "> Currently selected Python version: ${old_python_version##*/}" - trap "restore_previous_python_version ${old_python_version}" EXIT - for python_version in ${PYTHON_VERSIONS}; do - if pdm use -f "python${python_version}" &>/dev/null; then - echo "> pdm run $@ (python${python_version})" - pdm run "$@" - else - echo "> pdm use -f python${python_version}: Python interpreter not available?" >&2 - fi - done -else - pdm run "$@" -fi diff --git a/scripts/setup.sh b/scripts/setup.sh index 188eaebc..9e7ab1ff 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -3,36 +3,18 @@ set -e PYTHON_VERSIONS="${PYTHON_VERSIONS-3.7 3.8 3.9 3.10 3.11}" -install_with_pipx() { - if ! command -v "$1" &>/dev/null; then - if ! command -v pipx &>/dev/null; then - python3 -m pip install --user pipx - fi - pipx install "$1" +if ! command -v pdm &>/dev/null; then + if ! command -v pipx &>/dev/null; then + python3 -m pip install --user pipx fi -} - -install_with_pipx pdm - -restore_previous_python_version() { - if pdm use -f "$1" &>/dev/null; then - echo "> Restored previous Python version: ${1##*/}" - fi -} + pipx install pdm +fi +if ! pdm self list 2>/dev/null | grep -q pdm-multirun; then + pipx inject pdm pdm-multirun +fi if [ -n "${PYTHON_VERSIONS}" ]; then - if old_python_version="$(pdm config python.path 2>/dev/null)"; then - echo "> Currently selected Python version: ${old_python_version##*/}" - trap "restore_previous_python_version ${old_python_version}" EXIT - fi - for python_version in ${PYTHON_VERSIONS}; do - if pdm use -f "python${python_version}" &>/dev/null; then - echo "> Using Python ${python_version} interpreter" - pdm install - else - echo "> pdm use -f python${python_version}: Python interpreter not available?" >&2 - fi - done + pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install else pdm install fi From 2860bb60eebd4a8682eb687fb891d6f0a18f952a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 2 Apr 2023 13:56:34 +0200 Subject: [PATCH 10/14] chore: Add back MYPYPATH env var for type checking action --- duties.py | 1 + 1 file changed, 1 insertion(+) diff --git a/duties.py b/duties.py index b4ef4474..51cef860 100644 --- a/duties.py +++ b/duties.py @@ -114,6 +114,7 @@ def check_types(ctx: Context) -> None: Parameters: ctx: The context instance (passed automatically). """ + os.environ["MYPYPATH"] = "src" ctx.run( mypy.run(*PY_SRC_LIST, config_file="config/mypy.ini"), title=pyprefix("Type-checking"), From 1a131d73a5e3060755560d14431706164a426b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 2 Apr 2023 14:00:51 +0200 Subject: [PATCH 11/14] ci: Quality --- src/mkdocstrings_handlers/python/__init__.py | 2 +- src/mkdocstrings_handlers/python/handler.py | 53 +++++++++++-------- src/mkdocstrings_handlers/python/rendering.py | 27 +++++----- .../templates/material/_base/children.html | 10 ++-- tests/conftest.py | 36 +++++++------ tests/test_handler.py | 28 ++++++---- tests/test_rendering.py | 9 ++-- tests/test_themes.py | 11 +++- 8 files changed, 105 insertions(+), 71 deletions(-) diff --git a/src/mkdocstrings_handlers/python/__init__.py b/src/mkdocstrings_handlers/python/__init__.py index 706d85ee..f93ab20e 100644 --- a/src/mkdocstrings_handlers/python/__init__.py +++ b/src/mkdocstrings_handlers/python/__init__.py @@ -2,7 +2,7 @@ from mkdocstrings_handlers.python.handler import get_handler -__all__ = ["get_handler"] # noqa: WPS410 +__all__ = ["get_handler"] # TODO: CSS classes everywhere in templates # TODO: name normalization (filenames, Jinja2 variables, HTML tags, CSS classes) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index eb3c3147..23b4a76d 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -10,7 +10,7 @@ import sys from collections import ChainMap from contextlib import suppress -from typing import Any, BinaryIO, Iterator, Mapping, Optional, Tuple +from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Mapping from griffe.agents.extensions import load_extensions from griffe.collections import LinesCollection, ModulesCollection @@ -18,7 +18,6 @@ from griffe.exceptions import AliasResolutionError from griffe.loader import GriffeLoader from griffe.logger import patch_loggers -from markdown import Markdown from mkdocstrings.extension import PluginError from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem from mkdocstrings.inventory import Inventory @@ -26,14 +25,18 @@ from mkdocstrings_handlers.python import rendering +if TYPE_CHECKING: + from markdown import Markdown + + if sys.version_info >= (3, 11): from contextlib import chdir else: # TODO: remove once support for Python 3.10 is dropped from contextlib import contextmanager - @contextmanager # noqa: WPS440 - def chdir(path: str): # noqa: D103,WPS440 + @contextmanager + def chdir(path: str) -> Iterator[None]: # noqa: D103 old_wd = os.getcwd() os.chdir(path) try: @@ -162,10 +165,14 @@ class PythonHandler(BaseHandler): The modules must be listed as an array of strings. Default: `None`. - """ # noqa: E501 + """ def __init__( - self, *args: Any, config_file_path: str | None = None, paths: list[str] | None = None, **kwargs: Any + self, + *args: Any, + config_file_path: str | None = None, + paths: list[str] | None = None, + **kwargs: Any, ) -> None: """Initialize the handler. @@ -186,9 +193,8 @@ def __init__( paths.append(os.path.dirname(config_file_path)) search_paths = [path for path in sys.path if path] # eliminate empty path for path in reversed(paths): - if not os.path.isabs(path): - if config_file_path: - path = os.path.abspath(os.path.join(os.path.dirname(config_file_path), path)) + if not os.path.isabs(path) and config_file_path: + path = os.path.abspath(os.path.join(os.path.dirname(config_file_path), path)) # noqa: PLW2901 if path not in search_paths: search_paths.insert(0, path) self._paths = search_paths @@ -200,10 +206,10 @@ def load_inventory( cls, in_file: BinaryIO, url: str, - base_url: Optional[str] = None, + base_url: str | None = None, domains: list[str] | None = None, - **kwargs: Any, - ) -> Iterator[Tuple[str, str]]: + **kwargs: Any, # noqa: ARG003 + ) -> Iterator[tuple[str, str]]: """Yield items and their URLs from an inventory file streamed from `in_file`. This implements mkdocstrings' `load_inventory` "protocol" (see [`mkdocstrings.plugin`][mkdocstrings.plugin]). @@ -222,10 +228,10 @@ def load_inventory( if base_url is None: base_url = posixpath.dirname(url) - for item in Inventory.parse_sphinx(in_file, domain_filter=domains).values(): # noqa: WPS526 + for item in Inventory.parse_sphinx(in_file, domain_filter=domains).values(): yield item.name, posixpath.join(base_url, item.uri) - def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: # noqa: D102,WPS231 + def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: # noqa: D102 module_name = identifier.split(".", 1)[0] unknown_module = module_name not in self._modules_collection if config.get("fallback", False) and unknown_module: @@ -247,7 +253,7 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: modules_collection=self._modules_collection, lines_collection=self._lines_collection, ) - try: # noqa: WPS229 we expect one type of exception, and want to fail on the first one + try: for pre_loaded_module in final_config.get("preload_modules") or []: if pre_loaded_module not in self._modules_collection: loader.load_module(pre_loaded_module) @@ -255,7 +261,8 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: except ImportError as error: raise CollectionError(str(error)) from error unresolved, iterations = loader.resolve_aliases( - implicit=False, external=final_config["load_external_modules"] + implicit=False, + external=final_config["load_external_modules"], ) if unresolved: logger.debug(f"{len(unresolved)} aliases were still unresolved after {iterations} iterations") @@ -263,7 +270,7 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: try: doc_object = self._modules_collection[identifier] - except KeyError as error: # noqa: WPS440 + except KeyError as error: raise CollectionError(f"{identifier} could not be found") from error if not unknown_module: @@ -287,9 +294,11 @@ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa heading_level = final_config["heading_level"] try: final_config["members_order"] = rendering.Order(final_config["members_order"]) - except ValueError: + except ValueError as error: choices = "', '".join(item.value for item in rendering.Order) - raise PluginError(f"Unknown members_order '{final_config['members_order']}', choose between '{choices}'.") + raise PluginError( + f"Unknown members_order '{final_config['members_order']}', choose between '{choices}'.", + ) from error if final_config["filters"]: final_config["filters"] = [ @@ -320,11 +329,11 @@ def get_anchors(self, data: CollectorItem) -> list[str]: # noqa: D102 (ignore m def get_handler( - theme: str, # noqa: W0613 (unused argument config) - custom_templates: Optional[str] = None, + theme: str, + custom_templates: str | None = None, config_file_path: str | None = None, paths: list[str] | None = None, - **config: Any, + **config: Any, # noqa: ARG001 ) -> PythonHandler: """Simply return an instance of `PythonHandler`. diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index 8e5f7d85..9ee91769 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -6,13 +6,15 @@ import re import sys from functools import lru_cache -from typing import Any, Pattern, Sequence +from typing import TYPE_CHECKING, Any, Callable, Match, Pattern, Sequence -from griffe.dataclasses import Alias, Object from markupsafe import Markup -from mkdocstrings.handlers.base import CollectorItem from mkdocstrings.loggers import get_logger +if TYPE_CHECKING: + from griffe.dataclasses import Alias, Object + from mkdocstrings.handlers.base import CollectorItem + logger = get_logger(__name__) @@ -102,7 +104,7 @@ def do_order_members( return sorted(members, key=order_map[order]) -def do_crossref(path: str, brief: bool = True) -> Markup: +def do_crossref(path: str, *, brief: bool = True) -> Markup: """Filter to create cross-references. Parameters: @@ -118,7 +120,7 @@ def do_crossref(path: str, brief: bool = True) -> Markup: return Markup("{path}").format(full_path=full_path, path=path) -def do_multi_crossref(text: str, code: bool = True) -> Markup: +def do_multi_crossref(text: str, *, code: bool = True) -> Markup: """Filter to create cross-references. Parameters: @@ -131,8 +133,8 @@ def do_multi_crossref(text: str, code: bool = True) -> Markup: group_number = 0 variables = {} - def repl(match): # noqa: WPS430 - nonlocal group_number # noqa: WPS420 + def repl(match: Match) -> str: + nonlocal group_number group_number += 1 path = match.group() path_var = f"path{group_number}" @@ -145,7 +147,7 @@ def repl(match): # noqa: WPS430 return Markup(text).format(**variables) -def _keep_object(name, filters): +def _keep_object(name: str, filters: Sequence[tuple[Pattern, bool]]) -> bool: keep = None rules = set() for regex, exclude in filters: @@ -153,7 +155,7 @@ def _keep_object(name, filters): if regex.search(name): keep = not exclude if keep is None: - if rules == {False}: # noqa: WPS531 + if rules == {False}: # only included stuff, no match = reject return False # only excluded stuff, or included and excluded stuff, no match = keep @@ -163,7 +165,8 @@ def _keep_object(name, filters): def do_filter_objects( objects_dictionary: dict[str, Object | Alias], - filters: list[tuple[bool, Pattern]] | None = None, + *, + filters: Sequence[tuple[Pattern, bool]] | None = None, members_list: list[str] | None = None, keep_no_docstrings: bool = True, ) -> list[Object | Alias]: @@ -195,14 +198,14 @@ def do_filter_objects( @lru_cache(maxsize=1) -def _get_black_formatter(): +def _get_black_formatter() -> Callable[[str, int], str]: try: from black import Mode, format_str except ModuleNotFoundError: logger.warning("Formatting signatures requires Black to be installed.") return lambda text, _: text - def formatter(code, line_length): # noqa: WPS430 + def formatter(code: str, line_length: int) -> str: mode = Mode(line_length=line_length) return format_str(code, mode=mode) diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/children.html b/src/mkdocstrings_handlers/python/templates/material/_base/children.html index 71755ea7..9e27ed0f 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/children.html +++ b/src/mkdocstrings_handlers/python/templates/material/_base/children.html @@ -19,7 +19,7 @@ {% set extra_level = 0 %} {% endif %} - {% with attributes = obj.attributes|filter_objects(config.filters, members_list, config.show_if_no_docstring) %} + {% with attributes = obj.attributes|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %} {% if attributes %} {% if config.show_category_heading %} {% filter heading(heading_level, id=html_id ~ "-attributes") %}Attributes{% endfilter %} @@ -34,7 +34,7 @@ {% endif %} {% endwith %} - {% with classes = obj.classes|filter_objects(config.filters, members_list, config.show_if_no_docstring) %} + {% with classes = obj.classes|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %} {% if classes %} {% if config.show_category_heading %} {% filter heading(heading_level, id=html_id ~ "-classes") %}Classes{% endfilter %} @@ -49,7 +49,7 @@ {% endif %} {% endwith %} - {% with functions = obj.functions|filter_objects(config.filters, members_list, config.show_if_no_docstring) %} + {% with functions = obj.functions|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %} {% if functions %} {% if config.show_category_heading %} {% filter heading(heading_level, id=html_id ~ "-functions") %}Functions{% endfilter %} @@ -67,7 +67,7 @@ {% endwith %} {% if config.show_submodules %} - {% with modules = obj.modules|filter_objects(config.filters, members_list, config.show_if_no_docstring) %} + {% with modules = obj.modules|filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring) %} {% if modules %} {% if config.show_category_heading %} {% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %} @@ -88,7 +88,7 @@ {% else %} {% for child in obj.members| - filter_objects(config.filters, members_list, config.show_if_no_docstring)| + filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring)| order_members(config.members_order, members_list) %} {% if not (obj.kind.value == "class" and child.name == "__init__" and config.merge_init_into_class) %} diff --git a/tests/conftest.py b/tests/conftest.py index ce71a665..5a34bd77 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,15 +3,23 @@ from __future__ import annotations from collections import ChainMap +from typing import TYPE_CHECKING, Any, Iterator import pytest from markdown.core import Markdown from mkdocs import config from mkdocs.config.defaults import get_schema +if TYPE_CHECKING: + from pathlib import Path + + from mkdocstrings.plugin import MkdocstringsPlugin + + from mkdocstrings_handlers.python.handler import PythonHandler + @pytest.fixture(name="mkdocs_conf") -def fixture_mkdocs_conf(request, tmp_path): +def fixture_mkdocs_conf(request: pytest.FixtureRequest, tmp_path: Path) -> Iterator[config.Config]: """Yield a MkDocs configuration object. Parameters: @@ -21,9 +29,9 @@ def fixture_mkdocs_conf(request, tmp_path): Yields: MkDocs config. """ - conf = config.Config(schema=get_schema()) - while hasattr(request, "_parent_request") and hasattr(request._parent_request, "_parent_request"): # noqa: WPS437 - request = request._parent_request # noqa: WPS437 + conf = config.Config(schema=get_schema()) # type: ignore[call-arg] + while hasattr(request, "_parent_request") and hasattr(request._parent_request, "_parent_request"): + request = request._parent_request conf_dict = { "site_name": "foo", @@ -33,7 +41,7 @@ def fixture_mkdocs_conf(request, tmp_path): **getattr(request, "param", {}), } # Re-create it manually as a workaround for https://github.com/mkdocs/mkdocs/issues/2289 - mdx_configs = dict(ChainMap(*conf_dict.get("markdown_extensions", []))) + mdx_configs: dict[str, Any] = dict(ChainMap(*conf_dict.get("markdown_extensions", []))) # type: ignore[arg-type] conf.load_dict(conf_dict) assert conf.validate() == ([], []) @@ -48,7 +56,7 @@ def fixture_mkdocs_conf(request, tmp_path): @pytest.fixture(name="plugin") -def fixture_plugin(mkdocs_conf): +def fixture_plugin(mkdocs_conf: config.Config) -> MkdocstringsPlugin: """Return a plugin instance. Parameters: @@ -57,26 +65,24 @@ def fixture_plugin(mkdocs_conf): Returns: mkdocstrings plugin instance. """ - plugin = mkdocs_conf["plugins"]["mkdocstrings"] - plugin.md = Markdown(extensions=mkdocs_conf["markdown_extensions"], extension_configs=mkdocs_conf["mdx_configs"]) - return plugin + return mkdocs_conf["plugins"]["mkdocstrings"] @pytest.fixture(name="ext_markdown") -def fixture_ext_markdown(plugin): +def fixture_ext_markdown(mkdocs_conf: config.Config) -> Markdown: """Return a Markdown instance with MkdocstringsExtension. Parameters: - plugin: Pytest fixture: [tests.conftest.fixture_plugin][]. + mkdocs_conf: Pytest fixture: [tests.conftest.fixture_mkdocs_conf][]. Returns: A Markdown instance. """ - return plugin.md + return Markdown(extensions=mkdocs_conf["markdown_extensions"], extension_configs=mkdocs_conf["mdx_configs"]) @pytest.fixture(name="handler") -def fixture_handler(plugin): +def fixture_handler(plugin: MkdocstringsPlugin, ext_markdown: Markdown) -> PythonHandler: """Return a handler instance. Parameters: @@ -86,5 +92,5 @@ def fixture_handler(plugin): A handler instance. """ handler = plugin.handlers.get_handler("python") - handler._update_env(plugin.md, plugin.handlers._config) # noqa: WPS437 - return handler + handler._update_env(ext_markdown, plugin.handlers._config) + return handler # type: ignore[return-value] diff --git a/tests/test_handler.py b/tests/test_handler.py index 93148d5f..fc31942c 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -1,35 +1,41 @@ """Tests for the `handler` module.""" +from __future__ import annotations + import os from glob import glob +from typing import TYPE_CHECKING import pytest from griffe.docstrings.dataclasses import DocstringSectionExamples, DocstringSectionKind from mkdocstrings_handlers.python.handler import CollectionError, PythonHandler, get_handler +if TYPE_CHECKING: + from pathlib import Path + -def test_collect_missing_module(): +def test_collect_missing_module() -> None: """Assert error is raised for missing modules.""" handler = get_handler(theme="material") with pytest.raises(CollectionError): handler.collect("aaaaaaaa", {}) -def test_collect_missing_module_item(): +def test_collect_missing_module_item() -> None: """Assert error is raised for missing items within existing modules.""" handler = get_handler(theme="material") with pytest.raises(CollectionError): handler.collect("mkdocstrings.aaaaaaaa", {}) -def test_collect_module(): +def test_collect_module() -> None: """Assert existing module can be collected.""" handler = get_handler(theme="material") assert handler.collect("mkdocstrings", {}) -def test_collect_with_null_parser(): +def test_collect_with_null_parser() -> None: """Assert we can pass `None` as parser when collecting.""" handler = get_handler(theme="material") assert handler.collect("mkdocstrings", {"docstring_style": None}) @@ -44,7 +50,7 @@ def test_collect_with_null_parser(): ], indirect=["handler"], ) -def test_render_docstring_examples_section(handler): +def test_render_docstring_examples_section(handler: PythonHandler) -> None: """Assert docstrings' examples section can be rendered. Parameters: @@ -63,7 +69,7 @@ def test_render_docstring_examples_section(handler): assert "Hello" in rendered -def test_expand_globs(tmp_path): +def test_expand_globs(tmp_path: Path) -> None: """Assert globs are correctly expanded. Parameters: @@ -81,14 +87,14 @@ def test_expand_globs(tmp_path): handler = PythonHandler( handler="python", theme="material", - config_file_path=tmp_path / "mkdocs.yml", + config_file_path=str(tmp_path.joinpath("mkdocs.yml")), paths=["*exp*"], ) - for path in globbed_paths: # noqa: WPS440 - assert str(path) in handler._paths # noqa: WPS437 + for path in globbed_paths: + assert str(path) in handler._paths -def test_expand_globs_without_changing_directory(): +def test_expand_globs_without_changing_directory() -> None: """Assert globs are correctly expanded when we are already in the right directory.""" handler = PythonHandler( handler="python", @@ -97,4 +103,4 @@ def test_expand_globs_without_changing_directory(): paths=["*.md"], ) for path in list(glob(os.path.abspath(".") + "/*.md")): - assert path in handler._paths # noqa: WPS437 + assert path in handler._paths diff --git a/tests/test_rendering.py b/tests/test_rendering.py index 533eaf34..45b6048b 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -1,14 +1,17 @@ """Tests for the `rendering` module.""" +from __future__ import annotations + import re from dataclasses import dataclass +from typing import Any import pytest from mkdocstrings_handlers.python import rendering -def test_format_code_and_signature(): +def test_format_code_and_signature() -> None: """Assert code and signatures can be Black-formatted.""" assert rendering.do_format_code("print('Hello')", 100) assert rendering.do_format_code('print("Hello")', 100) @@ -28,7 +31,7 @@ class _FakeObject: (["aa", "ab", "ac", "da"], {"members_list": ["aa", "ab"]}, {"aa", "ab"}), ], ) -def test_filter_objects(names, filter_params, expected_names): +def test_filter_objects(names: list[str], filter_params: dict[str, Any], expected_names: set[str]) -> None: """Assert the objects filter works correctly. Parameters: @@ -37,6 +40,6 @@ def test_filter_objects(names, filter_params, expected_names): expected_names: Names expected to be kept. """ objects = {name: _FakeObject(name) for name in names} - filtered = rendering.do_filter_objects(objects, **filter_params) + filtered = rendering.do_filter_objects(objects, **filter_params) # type: ignore[arg-type] filtered_names = {obj.name for obj in filtered} assert set(filtered_names) == set(expected_names) diff --git a/tests/test_themes.py b/tests/test_themes.py index b1e7d5d5..bedcc806 100644 --- a/tests/test_themes.py +++ b/tests/test_themes.py @@ -1,9 +1,16 @@ """Tests for the different themes we claim to support.""" +from __future__ import annotations + import sys +from typing import TYPE_CHECKING import pytest +if TYPE_CHECKING: + from markdown import Markdown + from mkdocstrings.plugin import MkdocstringsPlugin + @pytest.mark.parametrize( "plugin", @@ -27,7 +34,7 @@ ], ) @pytest.mark.skipif(sys.version_info < (3, 7), reason="material is not installed on Python 3.6") -def test_render_themes_templates_python(module, plugin): +def test_render_themes_templates_python(module: str, plugin: MkdocstringsPlugin, ext_markdown: Markdown) -> None: """Test rendering of a given theme's templates. Parameters: @@ -35,6 +42,6 @@ def test_render_themes_templates_python(module, plugin): plugin: Pytest fixture: [tests.conftest.fixture_plugin][]. """ handler = plugin.handlers.get_handler("python") - handler._update_env(plugin.md, plugin.handlers._config) # noqa: WPS437 + handler._update_env(ext_markdown, plugin.handlers._config) data = handler.collect(module, {}) handler.render(data, {}) From a190e2c4a752e74a05ad03702837a0914c198742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 2 Apr 2023 14:01:55 +0200 Subject: [PATCH 12/14] fix: Prevent alias resolution error when searching for anchors Issue #64: https://github.com/mkdocstrings/python/issues/64 --- src/mkdocstrings_handlers/python/handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 23b4a76d..18b35218 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -272,6 +272,8 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem: doc_object = self._modules_collection[identifier] except KeyError as error: raise CollectionError(f"{identifier} could not be found") from error + except AliasResolutionError as error: + raise CollectionError(str(error)) from error if not unknown_module: with suppress(AliasResolutionError): From 075735ce8d86921fbf092d7ad1d009bbb3a2e0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 3 Apr 2023 16:18:05 +0200 Subject: [PATCH 13/14] refactor: Support Griffe 0.26 --- src/mkdocstrings_handlers/python/handler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 18b35218..ffb645ea 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -12,7 +12,6 @@ from contextlib import suppress from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Mapping -from griffe.agents.extensions import load_extensions from griffe.collections import LinesCollection, ModulesCollection from griffe.docstrings.parsers import Parser from griffe.exceptions import AliasResolutionError @@ -25,6 +24,12 @@ from mkdocstrings_handlers.python import rendering +try: + from griffe.extensions import load_extensions +except ImportError: + # TODO: remove once support for Griffe 0.25 is dropped + from griffe.agents.extensions import load_extensions # type: ignore[no-redef] + if TYPE_CHECKING: from markdown import Markdown From c40d166097fcc0c2bdf76918d816e7b5e63d0e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 3 Apr 2023 16:20:19 +0200 Subject: [PATCH 14/14] chore: Prepare release 0.9.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28ea412e..3f6dd2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ 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.9.0](https://github.com/mkdocstrings/python/releases/tag/0.9.0) - 2023-04-03 + +[Compare with 0.8.3](https://github.com/mkdocstrings/python/compare/0.8.3...0.9.0) + +### Features + +- Allow resolving alias to external modules ([02052e2](https://github.com/mkdocstrings/python/commit/02052e248b125a113ab788faa9a075adbdc92ca6) by Gilad). [PR #61](https://github.com/mkdocstrings/python/pull/61), [Follow-up of PR #60](https://github.com/mkdocstrings/python/pull/60) +- Allow pre-loading modules ([36002cb](https://github.com/mkdocstrings/python/commit/36002cb9c89fba35d23afb07a866dd8c6877f742) by Gilad). [Issue mkdocstrings/mkdocstrings#503](https://github.com/mkdocstrings/mkdocstrings/issues/503), [PR #60](https://github.com/mkdocstrings/python/pull/60) +- Add show options for docstrings ([a6c55fb](https://github.com/mkdocstrings/python/commit/a6c55fb52f362dd49b1a7e334a631f6ea3b1b963) by Jeremy Goh). [Issue mkdocstrings/mkdocstrings#466](https://github.com/mkdocstrings/mkdocstrings/issues/466), [PR #56](https://github.com/mkdocstrings/python/pull/56) +- Allow custom list of domains for inventories ([f5ea6fd](https://github.com/mkdocstrings/python/commit/f5ea6fd81f7a531e8a97bb0e48267188d72936c1) by Sorin Sbarnea). [Issue mkdocstrings/mkdocstrings#510](https://github.com/mkdocstrings/mkdocstrings/issues/510), [PR #49](https://github.com/mkdocstrings/python/pull/49) + +### Bug Fixes + +- Prevent alias resolution error when searching for anchors ([a190e2c](https://github.com/mkdocstrings/python/commit/a190e2c4a752e74a05ad03702837a0914c198742) by Timothée Mazzucotelli). [Issue #64](https://github.com/mkdocstrings/python/issues/64) + +### Code Refactoring + +- Support Griffe 0.26 ([075735c](https://github.com/mkdocstrings/python/commit/075735ce8d86921fbf092d7ad1d009bbb3a2e0bb) by Timothée Mazzucotelli). +- Log (debug) unresolved aliases ([9164742](https://github.com/mkdocstrings/python/commit/9164742f87362e8241dea11bec0fd96f6b9d9dda) by Timothée Mazzucotelli). + ## [0.8.3](https://github.com/mkdocstrings/python/releases/tag/0.8.3) - 2023-01-04 [Compare with 0.8.2](https://github.com/mkdocstrings/python/compare/0.8.2...0.8.3)