From 970f962de59117be3bd11043da895482a0751c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 13 Jan 2025 18:24:30 +0100 Subject: [PATCH 1/3] refactor: Use dataclasses for configuration/options and automate schema generation --- config/pytest.ini | 3 +- docs/schema.json | 316 ------ docs/usage/index.md | 7 - duties.py | 2 + mkdocs.yml | 5 + pyproject.toml | 5 +- scripts/griffe_extensions.py | 46 + scripts/mkdocs_hooks.py | 32 + src/mkdocstrings_handlers/python/config.py | 994 ++++++++++++++++++ src/mkdocstrings_handlers/python/handler.py | 409 +++---- src/mkdocstrings_handlers/python/rendering.py | 25 +- .../material/_base/children.html.jinja | 2 +- .../material/_base/summary/modules.html.jinja | 2 +- tests/helpers.py | 9 +- tests/test_end_to_end.py | 6 +- tests/test_handler.py | 60 +- tests/test_rendering.py | 16 +- tests/test_themes.py | 5 +- 18 files changed, 1265 insertions(+), 679 deletions(-) delete mode 100644 docs/schema.json create mode 100644 scripts/griffe_extensions.py create mode 100644 scripts/mkdocs_hooks.py create mode 100644 src/mkdocstrings_handlers/python/config.py diff --git a/config/pytest.ini b/config/pytest.ini index 25f9c92e..4f43c18e 100644 --- a/config/pytest.ini +++ b/config/pytest.ini @@ -10,6 +10,7 @@ testpaths = # action:message_regex:warning_class:module_regex:line filterwarnings = error - # TODO: remove once pytest-xdist 4 is released + # TODO: Remove once pytest-xdist 4 is released. ignore:.*rsyncdir:DeprecationWarning:xdist + # TODO: Remove once mkdocstrings stops setting fallback function. ignore:.*fallback anchor function:DeprecationWarning:mkdocstrings diff --git a/docs/schema.json b/docs/schema.json deleted file mode 100644 index e1863d26..00000000 --- a/docs/schema.json +++ /dev/null @@ -1,316 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema", - "title": "Python handler for mkdocstrings.", - "type": "object", - "properties": { - "python": { - "markdownDescription": "https://mkdocstrings.github.io/python/", - "type": "object", - "properties": { - "import": { - "title": "Inventories to import.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/#global-only-options", - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "url": { - "title": "URL of the inventory file.", - "type": "string" - }, - "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" - } - } - } - } - ] - } - }, - "paths": { - "title": "Local absolute/relative paths (relative to mkdocs.yml) to search packages into.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/#paths", - "type": "array", - "items": { - "type": "string", - "format": "path" - } - }, - "load_external_modules": { - "title": "Load external modules to resolve aliases.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/#load_external_modules", - "type": "boolean", - "default": false - }, - "options": { - "title": "Options for collecting and rendering objects.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/#globallocal-options", - "type": "object", - "properties": { - "docstring_style": { - "title": "The docstring style to use when parsing docstrings.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/docstrings/#docstring_style", - "enum": [ - "google", - "numpy", - "sphinx" - ], - "default": "google" - }, - "docstring_options": { - "title": "The options for the docstring parser.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/docstrings/#docstring_options", - "default": null, - "items": { - "$ref": "https://raw.githubusercontent.com/mkdocstrings/griffe/master/docs/schema-docstrings-options.json" - } - }, - "show_root_heading": { - "title": "Show the heading of the object at the root of the documentation tree.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_root_heading", - "type": "boolean", - "default": false - }, - "show_root_toc_entry": { - "title": "If the root heading is not shown, at least add a ToC entry for it.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_root_toc_entry", - "type": "boolean", - "default": true - }, - "show_root_full_path": { - "title": "Show the full Python path for the root object heading.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_root_full_path", - "type": "boolean", - "default": true - }, - "show_root_members_full_path": { - "title": "Show the full Python path of the root members.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_root_members_full_path", - "type": "boolean", - "default": false - }, - "show_object_full_path": { - "title": "Show the full Python path of every object.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_object_full_path", - "type": "boolean", - "default": false - }, - "show_symbol_type_heading": { - "title": "Show the symbol type in headings (e.g. mod, class, func and attr).", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_symbol_type_heading", - "type": "boolean", - "default": false - }, - "show_symbol_type_toc": { - "title": "Show the symbol type in the Table of Contents (e.g. mod, class, func and attr).", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_symbol_type_toc", - "type": "boolean", - "default": false - }, - "show_category_heading": { - "title": "When grouped by categories, show a heading for each category.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#show_category_heading", - "type": "boolean", - "default": false - }, - "show_if_no_docstring": { - "title": "Show the object heading even if it has no docstring or children with docstrings.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/docstrings/#show_if_no_docstring", - "type": "boolean", - "default": false - }, - "show_signature": { - "title": "Show methods and functions signatures.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/signatures/#show_signature", - "type": "boolean", - "default": true - }, - "show_signature_annotations": { - "title": "Show the type annotations in methods and functions signatures.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/signatures/#show_signature_annotations", - "type": "boolean", - "default": false - }, - "separate_signature": { - "title": "Whether to put the whole signature in a code block below the heading. If a formatter (Black or Ruff) is installed, the signature is also formatted using it.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/signatures/#separate_signature", - "type": "boolean", - "default": false - }, - "line_length": { - "title": "Maximum line length when formatting code/signatures.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/signatures/#line_length", - "type": "integer", - "default": 60 - }, - "merge_init_into_class": { - "title": "Whether to merge the `__init__` method into the class' signature and docstring.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/docstrings/#merge_init_into_class", - "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/configuration/docstrings/#show_docstring_attributes", - "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/configuration/docstrings/#show_docstring_description", - "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/configuration/docstrings/#show_docstring_examples", - "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/configuration/docstrings/#show_docstring_other_parameters", - "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/configuration/docstrings/#show_docstring_parameters", - "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/configuration/docstrings/#show_docstring_raises", - "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/configuration/docstrings/#show_docstring_receives", - "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/configuration/docstrings/#show_docstring_returns", - "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/configuration/docstrings/#show_docstring_warns", - "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/configuration/docstrings/#show_docstring_yields", - "type": "boolean", - "default": true - }, - "show_source": { - "title": "Show the source code of this object.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/general/#show_source", - "type": "boolean", - "default": true - }, - "show_bases": { - "title": "Show the base classes of a class.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/general/#show_bases", - "type": "boolean", - "default": true - }, - "show_submodules": { - "title": "When rendering a module, show its submodules recursively.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/members/#show_submodules", - "type": "boolean", - "default": false - }, - "group_by_category": { - "title": "Group the object's children by categories: attributes, classes, functions, and modules.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/members/#group_by_category", - "type": "boolean", - "default": true - }, - "heading_level": { - "title": "The initial heading level to use.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/headings/#heading_level", - "type": "integer", - "default": 2 - }, - "members_order": { - "title": "The members ordering to use.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/members/#members_order", - "enum": [ - "alphabetical", - "source" - ], - "default": "alphabetical" - }, - "docstring_section_style": { - "title": "The style used to render docstring sections.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/docstrings/#docstring_section_style", - "enum": [ - "list", - "spacy", - "table" - ], - "default": "table" - }, - "members": { - "title": "An explicit list of members to render.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/members/#members", - "type": [ - "boolean", - "array" - ], - "default": null - }, - "filters": { - "title": "A list of filters applied to filter objects based on their name. A filter starting with `!` will exclude matching objects instead of including them. The `members` option takes precedence over `filters` (filters will still be applied recursively to lower members in the hierarchy).", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/members/#filters", - "type": "array", - "default": [ - "!^_[^_]" - ] - }, - "annotations_path": { - "title": "The verbosity for annotations path.", - "markdownDescription": "https://mkdocstrings.github.io/python/usage/configuration/signatures/#annotations_path", - "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/configuration/general/#preload_modules", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false -} \ No newline at end of file diff --git a/docs/usage/index.md b/docs/usage/index.md index 87f0a13f..8caafb48 100644 --- a/docs/usage/index.md +++ b/docs/usage/index.md @@ -199,13 +199,6 @@ in the following pages: - [Docstrings options](configuration/docstrings.md): options related to docstrings (parsing and rendering) - [Signature options](configuration/signatures.md): options related to signatures and type annotations -#### Options summary - -::: mkdocstrings_handlers.python.handler.PythonHandler.default_config - options: - show_root_heading: false - show_root_toc_entry: false - ## Finding modules There are multiple ways to tell the handler where to find your packages/modules. diff --git a/duties.py b/duties.py index bd051334..9e516ce5 100644 --- a/duties.py +++ b/duties.py @@ -88,6 +88,8 @@ def check_types(ctx: Context) -> None: ctx.run( tools.mypy(*PY_SRC_LIST, config_file="config/mypy.ini"), title=pyprefix("Type-checking"), + # TODO: Update when Pydantic supports 3.14. + nofail=sys.version_info >= (3, 14), ) diff --git a/mkdocs.yml b/mkdocs.yml index 2d546126..396f738f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,9 @@ validation: absolute_links: warn unrecognized_links: warn +hooks: +- scripts/mkdocs_hooks.py + nav: - Home: - Overview: index.md @@ -160,6 +163,8 @@ plugins: docstring_options: ignore_init_summary: true docstring_section_style: list + extensions: + - scripts/griffe_extensions.py filters: ["!^_"] heading_level: 1 inherited_members: true diff --git a/pyproject.toml b/pyproject.toml index c6f3cc50..b4a453e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,10 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "mkdocstrings>=0.26", + "mkdocstrings>=0.28", "mkdocs-autorefs>=1.2", "griffe>=0.49", + "typing-extensions>=4.0; python_version < '3.11'", ] [project.urls] @@ -106,6 +107,7 @@ dev = [ "mkdocs-git-revision-date-localized-plugin>=1.2", "mkdocs-literate-nav>=0.6", "mkdocs-material>=9.5", + "pydantic>=2.10", "mkdocs-minify-plugin>=0.8", # YORE: EOL 3.10: Remove line. "tomli>=2.0; python_version < '3.11'", @@ -113,3 +115,4 @@ dev = [ [tool.inline-snapshot] storage-dir = "tests/snapshots" +format-command = "ruff format --config config/ruff.toml --stdin-filename {filename}" diff --git a/scripts/griffe_extensions.py b/scripts/griffe_extensions.py new file mode 100644 index 00000000..4ff0c8cc --- /dev/null +++ b/scripts/griffe_extensions.py @@ -0,0 +1,46 @@ +"""Custom extensions for Griffe.""" + +from __future__ import annotations + +import ast +from typing import Any + +import griffe + +logger = griffe.get_logger("griffe_extensions") + + +class CustomFields(griffe.Extension): + """Support our custom dataclass fields.""" + + def on_attribute_instance( + self, + *, + attr: griffe.Attribute, + agent: griffe.Visitor | griffe.Inspector, + **kwargs: Any, # noqa: ARG002 + ) -> None: + """Fetch descriptions from `Field` annotations.""" + if attr.docstring: + return + try: + field: griffe.ExprCall = attr.annotation.slice.elements[1] # type: ignore[union-attr] + except AttributeError: + return + + if field.canonical_path == "mkdocstrings_handler.python.config.Field": + description = next( + attr.value + for attr in field.arguments + if isinstance(attr, griffe.ExprKeyword) and attr.name == "description" + ) + if not isinstance(description, str): + logger.warning(f"Field description of {attr.path} is not a static string") + description = str(description) + + attr.docstring = griffe.Docstring( + ast.literal_eval(description), + parent=attr, + parser=agent.docstring_parser, + parser_options=agent.docstring_options, + ) diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py new file mode 100644 index 00000000..63e7578e --- /dev/null +++ b/scripts/mkdocs_hooks.py @@ -0,0 +1,32 @@ +"""Generate a JSON schema of the Python handler configuration.""" + +import json +from os.path import join +from typing import Any + +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.plugins import get_plugin_logger + +from mkdocstrings_handlers.python.config import PythonInputConfig + +# TODO: Update when Pydantic supports Python 3.14 (sources and duties as well). +try: + from pydantic import TypeAdapter +except ImportError: + TypeAdapter = None # type: ignore[assignment,misc] + + +logger = get_plugin_logger(__name__) + + +def on_post_build(config: MkDocsConfig, **kwargs: Any) -> None: # noqa: ARG001 + """Write `schema.json` to the site directory.""" + if TypeAdapter is None: + logger.info("Pydantic is not installed, skipping JSON schema generation") + return + adapter = TypeAdapter(PythonInputConfig) + schema = adapter.json_schema() + schema["$schema"] = "https://json-schema.org/draft-07/schema" + with open(join(config.site_dir, "schema.json"), "w") as file: + json.dump(schema, file, indent=2) + logger.debug("Generated JSON schema") diff --git a/src/mkdocstrings_handlers/python/config.py b/src/mkdocstrings_handlers/python/config.py new file mode 100644 index 00000000..07f34397 --- /dev/null +++ b/src/mkdocstrings_handlers/python/config.py @@ -0,0 +1,994 @@ +"""Configuration and options dataclasses.""" + +from __future__ import annotations + +import re +import sys +from dataclasses import field, fields +from typing import TYPE_CHECKING, Annotated, Any, Literal + +# YORE: EOL 3.10: Replace block with line 2. +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +try: + # When Pydantic is available, use it to validate options (done automatically). + # Users can therefore opt into validation by installing Pydantic in development/CI. + # When building the docs to deploy them, Pydantic is not required anymore. + + # When building our own docs, Pydantic is always installed (see `docs` group in `pyproject.toml`) + # to allow automatic generation of a JSON Schema. The JSON Schema is then referenced by mkdocstrings, + # which is itself referenced by mkdocs-material's schema system. For example in VSCode: + # + # "yaml.schemas": { + # "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" + # } + from inspect import cleandoc + + from pydantic import Field as BaseField + from pydantic.dataclasses import dataclass + + _base_url = "https://mkdocstrings.github.io/python/usage" + + def Field( # noqa: N802, D103 + *args: Any, + description: str, + group: Literal["general", "headings", "members", "docstrings", "signatures"] | None = None, + parent: str | None = None, + **kwargs: Any, + ) -> None: + def _add_markdown_description(schema: dict[str, Any]) -> None: + url = f"{_base_url}/{f'configuration/{group}/' if group else ''}#{parent or schema['title']}" + schema["markdownDescription"] = f"[DOCUMENTATION]({url})\n\n{schema['description']}" + + return BaseField( + *args, + description=cleandoc(description), + field_title_generator=lambda name, _: name, + json_schema_extra=_add_markdown_description, + **kwargs, + ) +except ImportError: + from dataclasses import dataclass # type: ignore[no-redef] + + def Field(*args: Any, **kwargs: Any) -> None: # type: ignore[misc] # noqa: D103, N802 + pass + + +if TYPE_CHECKING: + from collections.abc import MutableMapping + + +# YORE: EOL 3.9: Remove block. +_dataclass_options = {"frozen": True} +if sys.version_info >= (3, 10): + _dataclass_options["kw_only"] = True + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class GoogleStyleOptions: + """Google style docstring options.""" + + ignore_init_summary: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="Whether to ignore the summary in `__init__` methods' docstrings.", + ), + ] = False + + returns_multiple_items: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="""Whether to parse multiple items in `Yields` and `Returns` sections. + + When true, each item's continuation lines must be indented. + When false (single item), no further indentation is required. + """, + ), + ] = True + + returns_named_value: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="""Whether to parse `Yields` and `Returns` section items as name and description, rather than type and description. + + When true, type must be wrapped in parentheses: `(int): Description.`. Names are optional: `name (int): Description.`. + When false, parentheses are optional but the items cannot be named: `int: Description`. + """, + ), + ] = True + + returns_type_in_property_summary: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="Whether to parse the return type of properties at the beginning of their summary: `str: Summary of the property`.", + ), + ] = False + + receives_multiple_items: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="""Whether to parse multiple items in `Receives` sections. + + When true, each item's continuation lines must be indented. + When false (single item), no further indentation is required. + """, + ), + ] = True + + receives_named_value: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="""Whether to parse `Receives` section items as name and description, rather than type and description. + + When true, type must be wrapped in parentheses: `(int): Description.`. Names are optional: `name (int): Description.`. + When false, parentheses are optional but the items cannot be named: `int: Description`. + """, + ), + ] = True + + trim_doctest_flags: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="Whether to remove doctest flags from Python example blocks.", + ), + ] = True + + warn_unknown_params: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="Warn about documented parameters not appearing in the signature.", + ), + ] = True + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class NumpyStyleOptions: + """Numpy style docstring options.""" + + ignore_init_summary: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="Whether to ignore the summary in `__init__` methods' docstrings.", + ), + ] = False + + trim_doctest_flags: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="Whether to remove doctest flags from Python example blocks.", + ), + ] = True + + warn_unknown_params: Annotated[ + bool, + Field( + group="docstrings", + parent="docstring_options", + description="Warn about documented parameters not appearing in the signature.", + ), + ] = True + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class SphinxStyleOptions: + """Sphinx style docstring options.""" + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class PerStyleOptions: + """Per style options.""" + + google: Annotated[ + GoogleStyleOptions, + Field( + group="docstrings", + parent="docstring_options", + description="Google-style options.", + ), + ] = field(default_factory=GoogleStyleOptions) + + numpy: Annotated[ + NumpyStyleOptions, + Field( + group="docstrings", + parent="docstring_options", + description="Numpydoc-style options.", + ), + ] = field(default_factory=NumpyStyleOptions) + + sphinx: Annotated[ + SphinxStyleOptions, + Field( + group="docstrings", + parent="docstring_options", + description="Sphinx-style options.", + ), + ] = field(default_factory=SphinxStyleOptions) + + @classmethod + def from_data(cls, **data: Any) -> Self: + """Create an instance from a dictionary.""" + if "google" in data: + data["google"] = GoogleStyleOptions(**data["google"]) + if "numpy" in data: + data["numpy"] = NumpyStyleOptions(**data["numpy"]) + if "sphinx" in data: + data["sphinx"] = SphinxStyleOptions(**data["sphinx"]) + return cls(**data) + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class AutoStyleOptions: + """Auto style docstring options.""" + + method: Annotated[ + Literal["heuristics", "max_sections"], + Field( + group="docstrings", + parent="docstring_options", + description="The method to use to determine the docstring style.", + ), + ] = "heuristics" + + style_order: Annotated[ + list[str], + Field( + group="docstrings", + parent="docstring_options", + description="The order of the docstring styles to try.", + ), + ] = field(default_factory=lambda: ["sphinx", "google", "numpy"]) + + default: Annotated[ + str | None, + Field( + group="docstrings", + parent="docstring_options", + description="The default docstring style to use if no other style is detected.", + ), + ] = None + + per_style_options: Annotated[ + PerStyleOptions, + Field( + group="docstrings", + parent="docstring_options", + description="Per-style options.", + ), + ] = field(default_factory=PerStyleOptions) + + @classmethod + def from_data(cls, **data: Any) -> Self: + """Create an instance from a dictionary.""" + if "per_style_options" in data: + data["per_style_options"] = PerStyleOptions.from_data(**data["per_style_options"]) + return cls(**data) + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class SummaryOption: + """Summary option.""" + + attributes: Annotated[ + bool, + Field( + group="members", + parent="summary", + description="Whether to render summaries of attributes.", + ), + ] = False + + functions: Annotated[ + bool, + Field( + group="members", + parent="summary", + description="Whether to render summaries of functions (methods).", + ), + ] = False + + classes: Annotated[ + bool, + Field( + group="members", + parent="summary", + description="Whether to render summaries of classes.", + ), + ] = False + + modules: Annotated[ + bool, + Field( + group="members", + parent="summary", + description="Whether to render summaries of modules.", + ), + ] = False + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class PythonInputOptions: + """Accepted input options.""" + + allow_inspection: Annotated[ + bool, + Field( + group="general", + description="Whether to allow inspecting modules when visiting them is not possible.", + ), + ] = True + + force_inspection: Annotated[ + bool, + Field( + group="general", + description="Whether to force using dynamic analysis when loading data.", + ), + ] = False + + annotations_path: Annotated[ + Literal["brief", "source", "full"], + Field( + group="signatures", + description="The verbosity for annotations path: `brief` (recommended), `source` (as written in the source), or `full`.", + ), + ] = "brief" + + docstring_options: Annotated[ + GoogleStyleOptions | NumpyStyleOptions | SphinxStyleOptions | AutoStyleOptions | None, + Field( + group="docstrings", + description="""The options for the docstring parser. + + See [docstring parsers](https://mkdocstrings.github.io/griffe/reference/docstrings/) and their options in Griffe docs. + """, + ), + ] = None + + docstring_section_style: Annotated[ + Literal["table", "list", "spacy"], + Field( + group="docstrings", + description="The style used to render docstring sections.", + ), + ] = "table" + + docstring_style: Annotated[ + Literal["auto", "google", "numpy", "sphinx"] | None, + Field( + group="docstrings", + description="The docstring style to use: `auto`, `google`, `numpy`, `sphinx`, or `None`.", + ), + ] = "google" + + extensions: Annotated[ + list[str | dict[str, Any]], + Field( + group="general", + description="A list of Griffe extensions to load.", + ), + ] = field(default_factory=list) + + filters: Annotated[ + list[str], + Field( + group="members", + description="""A list of filters applied to filter objects based on their name. + + A filter starting with `!` will exclude matching objects instead of including them. + The `members` option takes precedence over `filters` (filters will still be applied recursively + to lower members in the hierarchy). + """, + ), + ] = field(default_factory=lambda: ["!^_[^_]"]) + + find_stubs_package: Annotated[ + bool, + Field( + group="general", + description="Whether to load stubs package (package-stubs) when extracting docstrings.", + ), + ] = False + + group_by_category: Annotated[ + bool, + Field( + group="members", + description="Group the object's children by categories: attributes, classes, functions, and modules.", + ), + ] = True + + heading: Annotated[ + str, + Field( + group="headings", + description="A custom string to override the autogenerated heading of the root object.", + ), + ] = "" + + heading_level: Annotated[ + int, + Field( + group="headings", + description="The initial heading level to use.", + ), + ] = 2 + + inherited_members: Annotated[ + bool | list[str], + Field( + group="members", + description="""A boolean, or an explicit list of inherited members to render. + + If true, select all inherited members, which can then be filtered with `members`. + If false or empty list, do not select any inherited member. + """, + ), + ] = False + + line_length: Annotated[ + int, + Field( + group="signatures", + description="Maximum line length when formatting code/signatures.", + ), + ] = 60 + + members: Annotated[ + list[str] | bool | None, + Field( + group="members", + description="""A boolean, or an explicit list of members to render. + + If true, select all members without further filtering. + If false or empty list, do not render members. + If none, select all members and apply further filtering with filters and docstrings. + """, + ), + ] = None + + members_order: Annotated[ + Literal["alphabetical", "source"], + Field( + group="members", + description="""The members ordering to use. + + - `alphabetical`: order by the members names, + - `source`: order members as they appear in the source file. + """, + ), + ] = "alphabetical" + + merge_init_into_class: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to merge the `__init__` method into the class' signature and docstring.", + ), + ] = False + + modernize_annotations: Annotated[ + bool, + Field( + group="signatures", + description="Whether to modernize annotations, for example `Optional[str]` into `str | None`.", + ), + ] = False + + parameter_headings: Annotated[ + bool, + Field( + group="headings", + description="Whether to render headings for parameters (therefore showing parameters in the ToC).", + ), + ] = False + + preload_modules: Annotated[ + list[str], + Field( + group="general", + description="""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. + """, + ), + ] = field(default_factory=list) + + relative_crossrefs: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to enable the relative crossref syntax.", + ), + ] = False + + scoped_crossrefs: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to enable the scoped crossref ability.", + ), + ] = False + + separate_signature: Annotated[ + bool, + Field( + group="signatures", + description="""Whether to put the whole signature in a code block below the heading. + + If Black or Ruff are installed, the signature is also formatted using them. + """, + ), + ] = False + + show_bases: Annotated[ + bool, + Field( + group="general", + description="Show the base classes of a class.", + ), + ] = True + + show_category_heading: Annotated[ + bool, + Field( + group="headings", + description="When grouped by categories, show a heading for each category.", + ), + ] = False + + show_docstring_attributes: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Attributes' section in the object's docstring.", + ), + ] = True + + show_docstring_classes: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Classes' section in the object's docstring.", + ), + ] = True + + show_docstring_description: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the textual block (including admonitions) in the object's docstring.", + ), + ] = True + + show_docstring_examples: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Examples' section in the object's docstring.", + ), + ] = True + + show_docstring_functions: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Functions' or 'Methods' sections in the object's docstring.", + ), + ] = True + + show_docstring_modules: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Modules' section in the object's docstring.", + ), + ] = True + + show_docstring_other_parameters: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Other Parameters' section in the object's docstring.", + ), + ] = True + + show_docstring_parameters: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Parameters' section in the object's docstring.", + ), + ] = True + + show_docstring_raises: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Raises' section in the object's docstring.", + ), + ] = True + + show_docstring_receives: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Receives' section in the object's docstring.", + ), + ] = True + + show_docstring_returns: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Returns' section in the object's docstring.", + ), + ] = True + + show_docstring_warns: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Warns' section in the object's docstring.", + ), + ] = True + + show_docstring_yields: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to display the 'Yields' section in the object's docstring.", + ), + ] = True + + show_if_no_docstring: Annotated[ + bool, + Field( + group="docstrings", + description="Show the object heading even if it has no docstring or children with docstrings.", + ), + ] = False + + show_inheritance_diagram: Annotated[ + bool, + Field( + group="docstrings", + description="Show the inheritance diagram of a class using Mermaid.", + ), + ] = False + + show_labels: Annotated[ + bool, + Field( + group="docstrings", + description="Whether to show labels of the members.", + ), + ] = True + + show_object_full_path: Annotated[ + bool, + Field( + group="docstrings", + description="Show the full Python path of every object.", + ), + ] = False + + show_root_full_path: Annotated[ + bool, + Field( + group="docstrings", + description="Show the full Python path for the root object heading.", + ), + ] = True + + show_root_heading: Annotated[ + bool, + Field( + group="headings", + description="""Show the heading of the object at the root of the documentation tree. + + The root object is the object referenced by the identifier after `:::`. + """, + ), + ] = False + + show_root_members_full_path: Annotated[ + bool, + Field( + group="headings", + description="Show the full Python path of the root members.", + ), + ] = False + + show_root_toc_entry: Annotated[ + bool, + Field( + group="headings", + description="If the root heading is not shown, at least add a ToC entry for it.", + ), + ] = True + + show_signature_annotations: Annotated[ + bool, + Field( + group="signatures", + description="Show the type annotations in methods and functions signatures.", + ), + ] = False + + show_signature: Annotated[ + bool, + Field( + group="signatures", + description="Show methods and functions signatures.", + ), + ] = True + + show_source: Annotated[ + bool, + Field( + group="general", + description="Show the source code of this object.", + ), + ] = True + + show_submodules: Annotated[ + bool, + Field( + group="members", + description="When rendering a module, show its submodules recursively.", + ), + ] = False + + show_symbol_type_heading: Annotated[ + bool, + Field( + group="headings", + description="Show the symbol type in headings (e.g. mod, class, meth, func and attr).", + ), + ] = False + + show_symbol_type_toc: Annotated[ + bool, + Field( + group="headings", + description="Show the symbol type in the Table of Contents (e.g. mod, class, methd, func and attr).", + ), + ] = False + + signature_crossrefs: Annotated[ + bool, + Field( + group="signatures", + description="Whether to render cross-references for type annotations in signatures.", + ), + ] = False + + summary: Annotated[ + bool | SummaryOption, + Field( + group="members", + description="Whether to render summaries of modules, classes, functions (methods) and attributes.", + ), + ] = field(default_factory=SummaryOption) + + toc_label: Annotated[ + str, + Field( + group="headings", + description="A custom string to override the autogenerated toc label of the root object.", + ), + ] = "" + + unwrap_annotated: Annotated[ + bool, + Field( + group="signatures", + description="Whether to unwrap `Annotated` types to show only the type without the annotations.", + ), + ] = False + + extra: Annotated[ + dict[str, Any], + Field( + group="general", + description="Extra options.", + ), + ] = field(default_factory=dict) + + @classmethod + def _extract_extra(cls, data: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]: + field_names = {field.name for field in fields(cls)} + copy = data.copy() + return {name: copy.pop(name) for name in data if name not in field_names}, copy + + # YORE: Bump 2: Remove block. + def __init__(self, **kwargs: Any) -> None: + """Initialize the instance.""" + extra_fields = self._extract_extra(kwargs) + for name, value in kwargs.items(): + object.__setattr__(self, name, value) + if extra_fields: + object.__setattr__(self, "_extra", extra_fields) + + @classmethod + def coerce(cls, **data: Any) -> MutableMapping[str, Any]: + """Coerce data.""" + if "docstring_options" in data: + docstring_style = data.get("docstring_style", "google") + docstring_options = data["docstring_options"] + if docstring_options is not None: + if docstring_style == "auto": + docstring_options = AutoStyleOptions.from_data(**docstring_options) + elif docstring_style == "google": + docstring_options = GoogleStyleOptions(**docstring_options) + elif docstring_style == "numpy": + docstring_options = NumpyStyleOptions(**docstring_options) + elif docstring_style == "sphinx": + docstring_options = SphinxStyleOptions(**docstring_options) + data["docstring_options"] = docstring_options + if "summary" in data: + summary = data["summary"] + if summary is True: + summary = SummaryOption(attributes=True, functions=True, classes=True, modules=True) + elif summary is False: + summary = SummaryOption(attributes=False, functions=False, classes=False, modules=False) + else: + summary = SummaryOption(**summary) + data["summary"] = summary + return data + + @classmethod + def from_data(cls, **data: Any) -> Self: + """Create an instance from a dictionary.""" + return cls(**cls.coerce(**data)) + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class PythonOptions(PythonInputOptions): # type: ignore[override,unused-ignore] + """Final options passed as template context.""" + + filters: list[tuple[re.Pattern, bool]] = field(default_factory=list) # type: ignore[assignment] + """A list of filters applied to filter objects based on their name.""" + + summary: SummaryOption = field(default_factory=SummaryOption) + """Whether to render summaries of modules, classes, functions (methods) and attributes.""" + + @classmethod + def coerce(cls, **data: Any) -> MutableMapping[str, Any]: + """Create an instance from a dictionary.""" + if "filters" in data: + data["filters"] = [ + (re.compile(filtr.lstrip("!")), filtr.startswith("!")) for filtr in data["filters"] or () + ] + return super().coerce(**data) + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class Inventory: + """An inventory.""" + + url: Annotated[ + str, + Field( + parent="inventories", + description="The URL of the inventory.", + ), + ] + + base: Annotated[ + str | None, + Field( + parent="inventories", + description="The base URL of the inventory.", + ), + ] = None + + domains: Annotated[ + list[str], + Field( + parent="inventories", + description="The domains to load from the inventory.", + ), + ] = field(default_factory=lambda: ["py"]) + + @property + def _config(self) -> dict[str, Any]: + return {"base": self.base, "domains": self.domains} + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class PythonInputConfig: + """Python handler configuration.""" + + inventories: Annotated[ + list[str | Inventory], + Field(description="The inventories to load."), + ] = field(default_factory=list) + + paths: Annotated[ + list[str], + Field(description="The paths in which to search for Python packages."), + ] = field(default_factory=lambda: ["."]) + + load_external_modules: Annotated[ + bool | None, + Field(description="Whether to always load external modules/packages."), + ] = None + + options: Annotated[ + PythonInputOptions, + Field(description="Configuration options for collecting and rendering objects."), + ] = field(default_factory=PythonInputOptions) + + locale: Annotated[ + str | None, + Field(description="The locale to use when translating template strings."), + ] = None + + @classmethod + def coerce(cls, **data: Any) -> MutableMapping[str, Any]: + """Coerce data.""" + return data + + @classmethod + def from_data(cls, **data: Any) -> Self: + """Create an instance from a dictionary.""" + return cls(**cls.coerce(**data)) + + +# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line. +@dataclass(**_dataclass_options) # type: ignore[call-overload] +class PythonConfig(PythonInputConfig): # type: ignore[override,unused-ignore] + """Python handler configuration.""" + + inventories: list[Inventory] = field(default_factory=list) # type: ignore[assignment] + options: dict[str, Any] = field(default_factory=dict) # type: ignore[assignment] + + @classmethod + def coerce(cls, **data: Any) -> MutableMapping[str, Any]: + """Coerce data.""" + if "inventories" in data: + data["inventories"] = [ + Inventory(url=inv) if isinstance(inv, str) else Inventory(**inv) for inv in data["inventories"] + ] + return data diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 5725d566..bf22876d 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -5,12 +5,12 @@ import glob import os import posixpath -import re import sys -from collections import ChainMap from contextlib import suppress +from dataclasses import asdict from pathlib import Path from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar +from warnings import warn from griffe import ( AliasResolutionError, @@ -21,17 +21,18 @@ load_extensions, patch_loggers, ) -from mkdocstrings.extension import PluginError -from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem +from mkdocs.exceptions import PluginError +from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem, HandlerOptions from mkdocstrings.inventory import Inventory from mkdocstrings.loggers import get_logger from mkdocstrings_handlers.python import rendering +from mkdocstrings_handlers.python.config import PythonConfig, PythonOptions if TYPE_CHECKING: - from collections.abc import Iterator, Mapping, Sequence + from collections.abc import Iterator, Mapping, MutableMapping, Sequence - from markdown import Markdown + from mkdocs.config.defaults import MkDocsConfig if sys.version_info >= (3, 11): @@ -55,215 +56,78 @@ def chdir(path: str) -> Iterator[None]: # noqa: D103 patch_loggers(get_logger) +def _warn_extra_options(names: Sequence[str]) -> None: + warn( + "Passing extra options directly under `options` is deprecated. " + "Instead, pass them under `options.extra`, and update your templates. " + f"Current extra (unrecognized) options: {', '.join(sorted(names))}", + DeprecationWarning, + stacklevel=3, + ) + + class PythonHandler(BaseHandler): """The Python handler class.""" - name: str = "python" + name: ClassVar[str] = "python" """The handler's name.""" - domain: str = "py" # to match Sphinx's default domain + + domain: ClassVar[str] = "py" """The cross-documentation domain/language for this handler.""" - enable_inventory: bool = True + + enable_inventory: ClassVar[bool] = True """Whether this handler is interested in enabling the creation of the `objects.inv` Sphinx inventory file.""" - fallback_theme = "material" + + fallback_theme: ClassVar[str] = "material" """The fallback theme.""" - fallback_config: ClassVar[dict] = {"fallback": True} - """The configuration used to collect item during autorefs fallback.""" - default_config: ClassVar[dict] = { - "find_stubs_package": False, - "docstring_style": "google", - "docstring_options": {}, - "show_symbol_type_heading": False, - "show_symbol_type_toc": False, - "show_root_heading": False, - "show_root_toc_entry": True, - "show_root_full_path": True, - "show_root_members_full_path": False, - "show_object_full_path": False, - "show_category_heading": False, - "show_if_no_docstring": False, - "show_signature": True, - "show_signature_annotations": False, - "signature_crossrefs": False, - "separate_signature": False, - "line_length": 60, - "merge_init_into_class": False, - "relative_crossrefs": False, - "scoped_crossrefs": False, - "show_docstring_attributes": True, - "show_docstring_functions": True, - "show_docstring_classes": True, - "show_docstring_modules": 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_inheritance_diagram": False, - "show_submodules": False, - "group_by_category": True, - "heading": "", - "toc_label": "", - "heading_level": 2, - "members_order": rendering.Order.alphabetical.value, - "docstring_section_style": "table", - "members": None, - "inherited_members": False, - "filters": ["!^_[^_]"], - "annotations_path": "brief", - "preload_modules": None, - "allow_inspection": True, - "force_inspection": False, - "summary": False, - "show_labels": True, - "unwrap_annotated": False, - "parameter_headings": False, - "modernize_annotations": False, - } - """Default handler configuration. - - Attributes: General options: - find_stubs_package (bool): Whether to load stubs package (package-stubs) when extracting docstrings. Default `False`. - allow_inspection (bool): Whether to allow inspecting modules when visiting them is not possible. Default: `True`. - force_inspection (bool): Whether to force using dynamic analysis when loading data. Default: `False`. - show_bases (bool): Show the base classes of a class. Default: `True`. - show_inheritance_diagram (bool): Show the inheritance diagram of a class using Mermaid. Default: `False`. - 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`. - - Attributes: Headings options: - heading (str): A custom string to override the autogenerated heading of the root object. - toc_label (str): A custom string to override the autogenerated toc label of the root object. - heading_level (int): The initial heading level to use. Default: `2`. - parameter_headings (bool): Whether to render headings for parameters (therefore showing parameters in the ToC). Default: `False`. - show_root_heading (bool): Show the heading of the object at the root of the documentation tree - (i.e. the object referenced by the identifier after `:::`). Default: `False`. - show_root_toc_entry (bool): If the root heading is not shown, at least add a ToC entry for it. Default: `True`. - show_root_full_path (bool): Show the full Python path for the root object heading. Default: `True`. - show_root_members_full_path (bool): Show the full Python path of the root members. Default: `False`. - show_object_full_path (bool): Show the full Python path of every object. Default: `False`. - show_category_heading (bool): When grouped by categories, show a heading for each category. Default: `False`. - show_symbol_type_heading (bool): Show the symbol type in headings (e.g. mod, class, meth, func and attr). Default: `False`. - show_symbol_type_toc (bool): Show the symbol type in the Table of Contents (e.g. mod, class, methd, func and attr). Default: `False`. - - Attributes: Members options: - inherited_members (list[str] | bool | None): A boolean, or an explicit list of inherited members to render. - If true, select all inherited members, which can then be filtered with `members`. - If false or empty list, do not select any inherited member. Default: `False`. - members (list[str] | bool | None): A boolean, or an explicit list of members to render. - If true, select all members without further filtering. - If false or empty list, do not render members. - If none, select all members and apply further filtering with filters and docstrings. Default: `None`. - members_order (str): The members ordering to use. Options: `alphabetical` - order by the members names, - `source` - order members as they appear in the source file. Default: `"alphabetical"`. - filters (list[str] | None): A list of filters applied to filter objects based on their name. - A filter starting with `!` will exclude matching objects instead of including them. - The `members` option takes precedence over `filters` (filters will still be applied recursively - to lower members in the hierarchy). Default: `["!^_[^_]"]`. - group_by_category (bool): Group the object's children by categories: attributes, classes, functions, and modules. Default: `True`. - show_submodules (bool): When rendering a module, show its submodules recursively. Default: `False`. - summary (bool | dict[str, bool]): Whether to render summaries of modules, classes, functions (methods) and attributes. - show_labels (bool): Whether to show labels of the members. Default: `True`. - - Attributes: Docstrings options: - docstring_style (str): The docstring style to use: `google`, `numpy`, `sphinx`, or `None`. Default: `"google"`. - docstring_options (dict): The options for the docstring parser. See [docstring parsers](https://mkdocstrings.github.io/griffe/reference/docstrings/) and their options in Griffe docs. - docstring_section_style (str): The style used to render docstring sections. Options: `table`, `list`, `spacy`. Default: `"table"`. - merge_init_into_class (bool): Whether to merge the `__init__` method into the class' signature and docstring. Default: `False`. - relative_crossrefs (bool): Whether to enable the relative crossref syntax. Default: `False`. - scoped_crossrefs (bool): Whether to enable the scoped crossref ability. 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_functions (bool): Whether to display the "Functions" or "Methods" sections in the object's docstring. Default: `True`. - show_docstring_classes (bool): Whether to display the "Classes" section in the object's docstring. Default: `True`. - show_docstring_modules (bool): Whether to display the "Modules" 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"`. - line_length (int): Maximum line length when formatting code/signatures. Default: `60`. - show_signature (bool): Show methods and functions signatures. Default: `True`. - show_signature_annotations (bool): Show the type annotations in methods and functions signatures. Default: `False`. - signature_crossrefs (bool): Whether to render cross-references for type annotations in signatures. Default: `False`. - separate_signature (bool): Whether to put the whole signature in a code block below the heading. - If a formatter (Black or Ruff) is installed, the signature is also formatted using it. Default: `False`. - unwrap_annotated (bool): Whether to unwrap `Annotated` types to show only the type without the annotations. Default: `False`. - modernize_annotations (bool): Whether to modernize annotations, for example `Optional[str]` into `str | None`. Default: `False`. - """ - def __init__( - self, - *args: Any, - config_file_path: str | None = None, - paths: list[str] | None = None, - locale: str = "en", - load_external_modules: bool | None = None, - **kwargs: Any, - ) -> None: + def __init__(self, config: PythonConfig, base_dir: Path, **kwargs: Any) -> None: """Initialize the handler. Parameters: - *args: Handler name, theme and custom templates. - config_file_path: The MkDocs configuration file path. - paths: A list of paths to use as Griffe search paths. - locale: The locale to use when rendering content. - load_external_modules: Load external modules when resolving aliases. - **kwargs: Same thing, but with keyword arguments. + config: The handler configuration. + base_dir: The base directory of the project. + **kwargs: Arguments passed to the parent constructor. """ - super().__init__(*args, **kwargs) + super().__init__(**kwargs) + + self.config = config + self.base_dir = base_dir + + # YORE: Bump 2: Replace block with `self.global_options = config.options`. + global_extra, global_options = PythonOptions._extract_extra(config.options) + if global_extra: + _warn_extra_options(global_extra.keys()) # type: ignore[arg-type] + self._global_extra = global_extra + self.global_options = global_options # Warn if user overrides base templates. - if custom_templates := kwargs.get("custom_templates", ()): - config_dir = Path(config_file_path or "./mkdocs.yml").parent - for theme_dir in config_dir.joinpath(custom_templates, "python").iterdir(): + if self.custom_templates: + for theme_dir in base_dir.joinpath(self.custom_templates, "python").iterdir(): if theme_dir.joinpath("_base").is_dir(): logger.warning( f"Overriding base template '{theme_dir.name}/_base/