From 24afd5d6f083495053f72238cfd0e2238d714594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sun, 9 Feb 2025 13:38:02 +0100 Subject: [PATCH 1/3] docs: Wrap schema input configuration under a `python` property Issue-246: https://github.com/mkdocstrings/python/issues/246 --- scripts/mkdocs_hooks.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index bfa74e5c..805055e0 100644 --- a/scripts/mkdocs_hooks.py +++ b/scripts/mkdocs_hooks.py @@ -1,7 +1,7 @@ """Generate a JSON schema of the Python handler configuration.""" import json -from dataclasses import fields +from dataclasses import dataclass, fields from os.path import join from typing import Any @@ -25,7 +25,12 @@ def on_post_build(config: MkDocsConfig, **kwargs: Any) -> None: # noqa: ARG001 if TypeAdapter is None: logger.info("Pydantic is not installed, skipping JSON schema generation") return - adapter = TypeAdapter(PythonInputConfig) + + @dataclass + class PythonHandlerSchema: + python: PythonInputConfig + + adapter = TypeAdapter(PythonHandlerSchema) schema = adapter.json_schema() schema["$schema"] = "https://json-schema.org/draft-07/schema" with open(join(config.site_dir, "schema.json"), "w") as file: From f07bf58a7358dea106032c7da27098e7617eefa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 11 Feb 2025 16:41:47 +0100 Subject: [PATCH 2/3] feat: Support cross-referencing constructor parameters in instance attribute values --- src/mkdocstrings_handlers/python/handler.py | 9 +++- src/mkdocstrings_handlers/python/rendering.py | 51 +++++++++++++------ .../material/_base/expression.html.jinja | 5 +- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index e6bd3297..30cd2058 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -305,6 +305,11 @@ def update_env(self, config: Any) -> None: # noqa: ARG002 self.env.tests["existing_template"] = lambda template_name: template_name in self.env.list_templates() def get_aliases(self, identifier: str) -> tuple[str, ...]: # noqa: D102 (ignore missing docstring) + if "(" in identifier: + identifier, parameter = identifier.split("(", 1) + parameter.removesuffix(")") + else: + parameter = "" try: data = self._modules_collection[identifier] except (KeyError, AliasResolutionError): @@ -315,7 +320,9 @@ def get_aliases(self, identifier: str) -> tuple[str, ...]: # noqa: D102 (ignore if alias not in aliases: aliases.append(alias) except AliasResolutionError: - return tuple(aliases) + pass + if parameter: + return tuple(f"{alias}({parameter})" for alias in aliases) return tuple(aliases) def normalize_extension_paths(self, extensions: Sequence) -> Sequence: diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index e298997a..b5c893c0 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -32,7 +32,7 @@ from mkdocstrings.loggers import get_logger if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Iterator, Sequence from griffe import Attribute, Class, Function, Module from jinja2 import Environment, Template @@ -326,26 +326,47 @@ def repl(match: Match) -> str: return Markup(text).format(**variables) -def do_split_path(path: str, full_path: str) -> list[tuple[str, str]]: +_split_path_re = re.compile(r"([.(]?)([\w]+)(\))?") +_splitable_re = re.compile(r"[().]") + + +def do_split_path(path: str, full_path: str) -> Iterator[tuple[str, str, str, str]]: """Split object paths for building cross-references. Parameters: path: The path to split. + full_path: The full path, used to compute correct paths for each part of the path. - Returns: - A list of pairs (title, full path). + Yields: + 4-tuples: prefix, word, full path, suffix. """ - if "." not in path: - return [(path, full_path)] - pairs = [] - full_path = "" - for part in path.split("."): - if full_path: - full_path += f".{part}" - else: - full_path = part - pairs.append((part, full_path)) - return pairs + # Path is a single word, yield full path directly. + if not _splitable_re.search(path): + yield ("", path, full_path, "") + return + + current_path = "" + if path == full_path: + # Split full path and yield directly without storing data in a dict. + for match in _split_path_re.finditer(full_path): + prefix, word, suffix = match.groups() + current_path = f"{current_path}{prefix}{word}{suffix or ''}" if current_path else word + yield prefix or "", word, current_path, suffix or "" + return + + # Split full path first to store tuples in a dict. + elements = {} + for match in _split_path_re.finditer(full_path): + prefix, word, suffix = match.groups() + current_path = f"{current_path}{prefix}{word}{suffix or ''}" if current_path else word + elements[word] = (prefix or "", word, current_path, suffix or "") + + # Then split path and pick tuples from the dict. + first = True + for match in _split_path_re.finditer(path): + prefix, word, current_path, suffix = elements[match.group(2)] + yield "" if first else prefix, word, current_path, suffix + first = False def _keep_object(name: str, filters: Sequence[tuple[Pattern, bool]]) -> bool: diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja index 5d7c07d5..781d46c7 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja @@ -31,7 +31,8 @@ which is a tree-like structure representing a Python expression. {%- elif annotation_path == "full" -%} {%- set annotation = full -%} {%- endif -%} - {%- for title, path in annotation|split_path(full) -%} + {%- for prefix, title, path, suffix in annotation|split_path(full) -%} + {{ prefix }} {%- if not signature -%} {#- Always render cross-references outside of signatures. We don't need to stash them. -#} {{ title }} @@ -44,7 +45,7 @@ which is a tree-like structure representing a Python expression. {#- We're in a signature but cross-references are disabled, we just render the title. -#} {{ title }} {%- endif -%} - {%- if not loop.last -%}.{%- endif -%} + {{ suffix }} {%- endfor -%} {%- endwith -%} {%- endmacro -%} From ab3b227e8ee1e84d8d34aee6ed5186fd26244132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Tue, 11 Feb 2025 17:04:21 +0100 Subject: [PATCH 3/3] chore: Prepare release 1.15.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2043c704..ec57bf03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.15.0](https://github.com/mkdocstrings/python/releases/tag/1.15.0) - 2025-02-11 + +[Compare with 1.14.6](https://github.com/mkdocstrings/python/compare/1.14.6...1.15.0) + +### Features + +- Support cross-referencing constructor parameters in instance attribute values ([f07bf58](https://github.com/mkdocstrings/python/commit/f07bf58a7358dea106032c7da27098e7617eefa0) by Timothée Mazzucotelli). + ## [1.14.6](https://github.com/mkdocstrings/python/releases/tag/1.14.6) - 2025-02-07 [Compare with 1.14.5](https://github.com/mkdocstrings/python/compare/1.14.5...1.14.6)