From 3b9dba2709a8668e379c6ce1536cb1714971b3f4 Mon Sep 17 00:00:00 2001 From: James McDonnell <447668+ElementalWarrior@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:02:10 -0700 Subject: [PATCH 01/10] refactor: Declare default CSS symbol colors under :host as well This is apparently needed when the docs are served under a shadow DOM (for example, Backstage seems to do this, and `:root` variables aren't picked up). See https://developer.mozilla.org/en-US/docs/Web/CSS/:host. PR-186: https://github.com/mkdocstrings/python/pull/186 --- src/mkdocstrings_handlers/python/templates/material/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mkdocstrings_handlers/python/templates/material/style.css b/src/mkdocstrings_handlers/python/templates/material/style.css index 154be85d..9547fa5a 100644 --- a/src/mkdocstrings_handlers/python/templates/material/style.css +++ b/src/mkdocstrings_handlers/python/templates/material/style.css @@ -31,7 +31,7 @@ } /* Symbols in Navigation and ToC. */ -:root, +:root, :host, [data-md-color-scheme="default"] { --doc-symbol-attribute-fg-color: #953800; --doc-symbol-function-fg-color: #8250df; From cbdf29441b192631031070305268ac884f5b1edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 12 Oct 2024 17:59:18 +0200 Subject: [PATCH 02/10] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/workflows/ci.yml | 29 ++++++---- .github/workflows/release.yml | 20 +++---- .gitignore | 1 + .gitpod.dockerfile | 6 -- .gitpod.yml | 13 ----- CONTRIBUTING.md | 5 +- config/ruff.toml | 2 +- devdeps.txt | 32 ----------- duties.py | 22 +++++-- mkdocs.yml | 5 +- pyproject.toml | 41 +++++++++++++- scripts/gen_credits.py | 12 ++-- scripts/insiders.py | 5 +- scripts/make | 104 ++++++++++++++-------------------- 15 files changed, 142 insertions(+), 157 deletions(-) delete mode 100644 .gitpod.dockerfile delete mode 100644 .gitpod.yml delete mode 100644 devdeps.txt diff --git a/.copier-answers.yml b/.copier-answers.yml index 90ce2e79..1dc4ac4d 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 1.1.4 +_commit: 1.2.0 _src_path: gh:mkdocstrings/handler-template author_email: dev@pawamoy.fr author_fullname: Timothée Mazzucotelli diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e422aeb8..6940069d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,13 +29,16 @@ jobs: - name: Fetch all tags run: git fetch --depth=1 --tags - - name: Set up Python + - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - - name: Install uv - run: pip install uv + - name: Setup uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + cache-dependency-glob: pyproject.toml - name: Install dependencies run: make setup @@ -63,11 +66,11 @@ jobs: echo 'jobs=[ {"os": "macos-latest"}, {"os": "windows-latest"}, - {"python-version": "3.9"}, {"python-version": "3.10"}, {"python-version": "3.11"}, {"python-version": "3.12"}, - {"python-version": "3.13"} + {"python-version": "3.13"}, + {"python-version": "3.14"} ]' | tr -d '[:space:]' >> $GITHUB_OUTPUT else echo 'jobs=[ @@ -87,31 +90,35 @@ jobs: - macos-latest - windows-latest python-version: - - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" + - "3.14" resolution: - highest - lowest-direct exclude: ${{ fromJSON(needs.exclude-test-jobs.outputs.jobs) }} runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.python-version == '3.13' }} + continue-on-error: ${{ matrix.python-version == '3.14' }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up Python + - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - - name: Install uv - run: pip install uv + - name: Setup uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + cache-dependency-glob: pyproject.toml + cache-suffix: py${{ matrix.python-version }} - name: Install dependencies env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d82736f7..45bcf5a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,32 +14,30 @@ jobs: - name: Fetch all tags run: git fetch --depth=1 --tags - name: Setup Python - uses: actions/setup-python@v4 - - name: Install build - if: github.repository_owner == 'pawamoy-insiders' - run: python -m pip install build + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Setup uv + uses: astral-sh/setup-uv@v3 - name: Build dists if: github.repository_owner == 'pawamoy-insiders' - run: python -m build + run: uv tool run --from build pyproject-build - name: Upload dists artifact uses: actions/upload-artifact@v4 if: github.repository_owner == 'pawamoy-insiders' with: name: python-insiders path: ./dist/* - - name: Install git-changelog - if: github.repository_owner != 'pawamoy-insiders' - run: pip install git-changelog - name: Prepare release notes if: github.repository_owner != 'pawamoy-insiders' - run: git-changelog --release-notes > release-notes.md + run: uv tool run git-changelog --release-notes > release-notes.md - name: Create release with assets - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: github.repository_owner == 'pawamoy-insiders' with: files: ./dist/* - name: Create release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: github.repository_owner != 'pawamoy-insiders' with: body_path: release-notes.md diff --git a/.gitignore b/.gitignore index 41fee62d..9fea0472 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ /.pdm-build/ /htmlcov/ /site/ +uv.lock # cache .cache/ diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile deleted file mode 100644 index 1590b415..00000000 --- a/.gitpod.dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM gitpod/workspace-full -USER gitpod -ENV PIP_USER=no -RUN pip3 install pipx; \ - pipx install uv; \ - pipx ensurepath diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 23a3c2b7..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,13 +0,0 @@ -vscode: - extensions: - - ms-python.python - -image: - file: .gitpod.dockerfile - -ports: -- port: 8000 - onOpen: notify - -tasks: -- init: make setup diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bbc08404..3e3dc294 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,12 +23,11 @@ make setup > You can install it with: > > ```bash -> python3 -m pip install --user pipx -> pipx install uv +> curl -LsSf https://astral.sh/uv/install.sh | sh > ``` > > Now you can try running `make setup` again, -> or simply `uv install`. +> or simply `uv sync`. You now have the dependencies installed. diff --git a/config/ruff.toml b/config/ruff.toml index e3c9ec30..4c91b364 100644 --- a/config/ruff.toml +++ b/config/ruff.toml @@ -1,4 +1,4 @@ -target-version = "py38" +target-version = "py39" line-length = 120 [lint] diff --git a/devdeps.txt b/devdeps.txt deleted file mode 100644 index e0afd7e2..00000000 --- a/devdeps.txt +++ /dev/null @@ -1,32 +0,0 @@ -# dev -editables>=0.5 - -# maintenance -build>=1.2 -git-changelog>=2.5 -twine>=5.0; python_version < '3.13' - -# ci -duty>=1.4 -ruff>=0.4 -pytest>=8.2 -pytest-cov>=5.0 -pytest-randomly>=3.15 -pytest-xdist>=3.6 -mypy>=1.10 -types-markdown>=3.6 -types-pyyaml>=6.0 - -# docs -black>=24.4 -markdown-callouts>=0.4 -markdown-exec>=1.8 -mkdocs>=1.6 -mkdocs-coverage>=1.0 -mkdocs-gen-files>=0.5 -mkdocs-git-committers-plugin-2>=2.3 -mkdocs-literate-nav>=0.6 -mkdocs-material>=9.5 -mkdocs-minify-plugin>=0.8 -mkdocstrings[python]>=0.25 -tomli>=2.0; python_version < '3.11' diff --git a/duties.py b/duties.py index f1909cc1..3864e74e 100644 --- a/duties.py +++ b/duties.py @@ -7,11 +7,13 @@ from contextlib import contextmanager from importlib.metadata import version as pkgversion from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING from duty import duty, tools if TYPE_CHECKING: + from collections.abc import Iterator + from duty.context import Context @@ -53,7 +55,7 @@ def changelog(ctx: Context, bump: str = "") -> None: ctx.run(tools.git_changelog(bump=bump or None), title="Updating changelog") -@duty(pre=["check_quality", "check_types", "check_docs", "check-api"]) +@duty(pre=["check-quality", "check-types", "check-docs", "check-api"]) def check(ctx: Context) -> None: """Check it all!""" @@ -116,23 +118,33 @@ def docs(ctx: Context, *cli_args: str, host: str = "127.0.0.1", port: int = 8000 @duty -def docs_deploy(ctx: Context) -> None: - """Deploy the documentation to GitHub pages.""" +def docs_deploy(ctx: Context, *, force: bool = False) -> None: + """Deploy the documentation to GitHub pages. + + Parameters: + force: Whether to force deployment, even from non-Insiders version. + """ os.environ["DEPLOY"] = "true" with material_insiders() as insiders: if not insiders: ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!") - origin = ctx.run("git config --get remote.origin.url", silent=True) + origin = ctx.run("git config --get remote.origin.url", silent=True, allow_overrides=False) if "pawamoy-insiders/mkdocstrings-python" in origin: ctx.run( "git remote add upstream git@github.com:mkdocstrings/python", silent=True, nofail=True, + allow_overrides=False, ) ctx.run( tools.mkdocs.gh_deploy(remote_name="upstream", force=True), title="Deploying documentation", ) + elif force: + ctx.run( + tools.mkdocs.gh_deploy(force=True), + title="Deploying documentation", + ) else: ctx.run( lambda: False, diff --git a/mkdocs.yml b/mkdocs.yml index 19aa90d9..2d546126 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -180,9 +180,10 @@ plugins: signature_crossrefs: true summary: true unwrap_annotated: true -- git-committers: +- git-revision-date-localized: enabled: !ENV [DEPLOY, false] - repository: mkdocstrings/python + enable_creation_date: true + type: timeago - minify: minify_html: !ENV [DEPLOY, false] - group: diff --git a/pyproject.toml b/pyproject.toml index 0eccf7fe..2d5640c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = "A Python handler for mkdocstrings." authors = [{name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr"}] license = {text = "ISC"} readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" keywords = [] dynamic = ["version"] classifiers = [ @@ -17,12 +17,12 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Documentation", "Topic :: Software Development", "Topic :: Utilities", @@ -58,7 +58,6 @@ source-includes = [ "scripts", "share", "tests", - "devdeps.txt", "duties.py", "mkdocs.yml", "*.md", @@ -69,3 +68,39 @@ source-includes = [ data = [ {path = "share/**/*", relative-to = "."}, ] + +[tool.uv] +dev-dependencies = [ + # dev + "editables>=0.5", + + # maintenance + "build>=1.2", + "git-changelog>=2.5", + "twine>=5.1", + + # ci + "duty>=1.4", + "ruff>=0.4", + "pytest>=8.2", + "pytest-cov>=5.0", + "pytest-randomly>=3.15", + "pytest-xdist>=3.6", + "mypy>=1.10", + "types-markdown>=3.6", + "types-pyyaml>=6.0", + + # docs + "black>=24.4", + "markdown-callouts>=0.4", + "markdown-exec>=1.8", + "mkdocs>=1.6", + "mkdocs-coverage>=1.0", + "mkdocs-gen-files>=0.5", + "mkdocs-git-revision-date-localized-plugin>=1.2", + "mkdocs-literate-nav>=0.6", + "mkdocs-material>=9.5", + "mkdocs-minify-plugin>=0.8", + # YORE: EOL 3.10: Remove line. + "tomli>=2.0; python_version < '3.11'", +] \ No newline at end of file diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index b2f6d3e4..51ebe2f3 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -5,17 +5,18 @@ import os import sys from collections import defaultdict +from collections.abc import Iterable from importlib.metadata import distributions from itertools import chain from pathlib import Path from textwrap import dedent -from typing import Dict, Iterable, Union +from typing import Union from jinja2 import StrictUndefined from jinja2.sandbox import SandboxedEnvironment from packaging.requirements import Requirement -# TODO: Remove once support for Python 3.10 is dropped. +# YORE: EOL 3.10: Replace block with line 2. if sys.version_info >= (3, 11): import tomllib else: @@ -26,11 +27,10 @@ pyproject = tomllib.load(pyproject_file) project = pyproject["project"] project_name = project["name"] -with project_dir.joinpath("devdeps.txt").open() as devdeps_file: - devdeps = [line.strip() for line in devdeps_file if line.strip() and not line.strip().startswith(("-e", "#"))] +devdeps = [dep for dep in pyproject["tool"]["uv"]["dev-dependencies"] if not dep.startswith("-e")] -PackageMetadata = Dict[str, Union[str, Iterable[str]]] -Metadata = Dict[str, PackageMetadata] +PackageMetadata = dict[str, Union[str, Iterable[str]]] +Metadata = dict[str, PackageMetadata] def _merge_fields(metadata: dict) -> PackageMetadata: diff --git a/scripts/insiders.py b/scripts/insiders.py index 15212486..849c6314 100644 --- a/scripts/insiders.py +++ b/scripts/insiders.py @@ -10,13 +10,16 @@ from datetime import date, datetime, timedelta from itertools import chain from pathlib import Path -from typing import Iterable, cast +from typing import TYPE_CHECKING, cast from urllib.error import HTTPError from urllib.parse import urljoin from urllib.request import urlopen import yaml +if TYPE_CHECKING: + from collections.abc import Iterable + logger = logging.getLogger(f"mkdocs.logs.{__name__}") diff --git a/scripts/make b/scripts/make index d898022e..ac430624 100755 --- a/scripts/make +++ b/scripts/make @@ -9,12 +9,10 @@ import subprocess import sys from contextlib import contextmanager from pathlib import Path +from textwrap import dedent from typing import Any, Iterator -PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.8 3.9 3.10 3.11 3.12 3.13").split() - -exe = "" -prefix = "" +PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split() def shell(cmd: str, capture_output: bool = False, **kwargs: Any) -> str | None: @@ -37,17 +35,13 @@ def environ(**kwargs: str) -> Iterator[None]: os.environ.update(original) -def uv_install() -> None: +def uv_install(venv: Path) -> None: """Install dependencies using uv.""" - uv_opts = "" - if "UV_RESOLUTION" in os.environ: - uv_opts = f"--resolution={os.getenv('UV_RESOLUTION')}" - requirements = shell(f"uv pip compile {uv_opts} pyproject.toml devdeps.txt", capture_output=True) - shell("uv pip install -r -", input=requirements, text=True) - if "CI" not in os.environ: - shell("uv pip install --no-deps -e .") - else: - shell("uv pip install --no-deps .") + with environ(UV_PROJECT_ENVIRONMENT=str(venv), PYO3_USE_ABI3_FORWARD_COMPATIBILITY="1"): + if "CI" in os.environ: + shell("uv sync --no-editable") + else: + shell("uv sync") def setup() -> None: @@ -59,7 +53,7 @@ def setup() -> None: default_venv = Path(".venv") if not default_venv.exists(): shell("uv venv --python python") - uv_install() + uv_install(default_venv) if PYTHON_VERSIONS: for version in PYTHON_VERSIONS: @@ -67,39 +61,22 @@ def setup() -> None: venv_path = Path(f".venvs/{version}") if not venv_path.exists(): shell(f"uv venv --python {version} {venv_path}") - with environ(VIRTUAL_ENV=str(venv_path.resolve())): - uv_install() - - -def activate(path: str) -> None: - """Activate a virtual environment.""" - global exe, prefix # noqa: PLW0603 - - if (bin := Path(path, "bin")).exists(): - activate_script = bin / "activate_this.py" - elif (scripts := Path(path, "Scripts")).exists(): - activate_script = scripts / "activate_this.py" - exe = ".exe" - prefix = f"{path}/Scripts/" - else: - raise ValueError(f"make: activate: Cannot find activation script in {path}") - - if not activate_script.exists(): - raise ValueError(f"make: activate: Cannot find activation script in {path}") - - exec(activate_script.read_text(), {"__file__": str(activate_script)}) # noqa: S102 + with environ(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())): + uv_install(venv_path) -def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None: +def run(version: str, cmd: str, *args: str, no_sync: bool = False, **kwargs: Any) -> None: """Run a command in a virtual environment.""" kwargs = {"check": True, **kwargs} + uv_run = ["uv", "run"] + if no_sync: + uv_run.append("--no-sync") if version == "default": - activate(".venv") - subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510 + with environ(UV_PROJECT_ENVIRONMENT=".venv"): + subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 else: - activate(f".venvs/{version}") - os.environ["MULTIRUN"] = "1" - subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510 + with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"): + subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510 def multirun(cmd: str, *args: str, **kwargs: Any) -> None: @@ -124,10 +101,10 @@ def clean() -> None: for path in paths_to_clean: shell(f"rm -rf {path}") - cache_dirs = [".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"] - for dirpath in Path(".").rglob("*"): - if any(dirpath.match(pattern) for pattern in cache_dirs) and not (dirpath.match(".venv") or dirpath.match(".venvs")): - shutil.rmtree(path, ignore_errors=True) + cache_dirs = {".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"} + for dirpath in Path(".").rglob("*/"): + if dirpath.parts[0] not in (".venv", ".venvs") and dirpath.name in cache_dirs: + shutil.rmtree(dirpath, ignore_errors=True) def vscode() -> None: @@ -143,22 +120,25 @@ def main() -> int: if len(args) > 1: run("default", "duty", "--help", args[1]) else: - print("Available commands") # noqa: T201 - print(" help Print this help. Add task name to print help.") # noqa: T201 - print(" setup Setup all virtual environments (install dependencies).") # noqa: T201 - print(" run Run a command in the default virtual environment.") # noqa: T201 - print(" multirun Run a command for all configured Python versions.") # noqa: T201 - print(" allrun Run a command in all virtual environments.") # noqa: T201 - print(" 3.x Run a command in the virtual environment for Python 3.x.") # noqa: T201 - print(" clean Delete build artifacts and cache files.") # noqa: T201 - print(" vscode Configure VSCode to work on this project.") # noqa: T201 - try: - run("default", "python", "-V", capture_output=True) - except (subprocess.CalledProcessError, ValueError): - pass - else: - print("\nAvailable tasks") # noqa: T201 - run("default", "duty", "--list") + print( + dedent( + """ + Available commands + help Print this help. Add task name to print help. + setup Setup all virtual environments (install dependencies). + run Run a command in the default virtual environment. + multirun Run a command for all configured Python versions. + allrun Run a command in all virtual environments. + 3.x Run a command in the virtual environment for Python 3.x. + clean Delete build artifacts and cache files. + vscode Configure VSCode to work on this project. + """ + ), + flush=True, + ) # noqa: T201 + if os.path.exists(".venv"): + print("\nAvailable tasks", flush=True) # noqa: T201 + run("default", "duty", "--list", no_sync=True) return 0 while args: From 6615c91cdc035bc0c2fdd12f3952ff84f5e1c04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 12 Oct 2024 18:06:09 +0200 Subject: [PATCH 03/10] build: Drop support for Python 3.8 --- src/mkdocstrings_handlers/python/handler.py | 4 +++- src/mkdocstrings_handlers/python/rendering.py | 16 +++++----------- tests/conftest.py | 3 ++- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index ef93ee3b..83315d10 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -10,7 +10,7 @@ from collections import ChainMap from contextlib import suppress from pathlib import Path -from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, Iterator, Mapping, Sequence +from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar from griffe import ( AliasResolutionError, @@ -29,6 +29,8 @@ from mkdocstrings_handlers.python import rendering if TYPE_CHECKING: + from collections.abc import Iterator, Mapping, Sequence + from markdown import Markdown diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index 2c4a4893..09be07ca 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -10,7 +10,8 @@ import warnings from functools import lru_cache, partial from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Match, Pattern, Sequence +from re import Match, Pattern +from typing import TYPE_CHECKING, Any, Callable from griffe import ( Alias, @@ -26,6 +27,8 @@ from mkdocstrings.loggers import get_logger if TYPE_CHECKING: + from collections.abc import Sequence + from griffe import Attribute, Class, Function, Module from jinja2 import Environment, Template from jinja2.runtime import Context @@ -477,16 +480,7 @@ def do_get_template(env: Environment, obj: str | Object) -> str | Template: template = env.get_template(f"{name}.html") except TemplateNotFound: return f"{name}.html.jinja" - # TODO: Remove once support for Python 3.8 is dropped. - if sys.version_info < (3, 9): - try: - Path(template.filename).relative_to(Path(__file__).parent) # type: ignore[arg-type] - except ValueError: - our_template = False - else: - our_template = True - else: - our_template = Path(template.filename).is_relative_to(Path(__file__).parent) # type: ignore[arg-type] + our_template = Path(template.filename).is_relative_to(Path(__file__).parent) # type: ignore[arg-type] if our_template: return f"{name}.html.jinja" # TODO: Switch to a warning log after some time. diff --git a/tests/conftest.py b/tests/conftest.py index f7b28105..88105e4c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,13 +3,14 @@ from __future__ import annotations from collections import ChainMap -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any import pytest from markdown.core import Markdown from mkdocs.config.defaults import MkDocsConfig if TYPE_CHECKING: + from collections.abc import Iterator from pathlib import Path from mkdocs import config From 0176b83f21ae02d345489c93cca3baf51f8bc58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 12 Oct 2024 18:17:19 +0200 Subject: [PATCH 04/10] feat: Parameter headings, more automatic cross-references --- src/mkdocstrings_handlers/python/handler.py | 4 +- src/mkdocstrings_handlers/python/rendering.py | 72 ++++++++----------- .../_base/docstring/parameters.html.jinja | 46 +++++++++++- .../material/_base/expression.html.jinja | 34 ++++++++- .../material/_base/signature.html.jinja | 47 ++++++++++-- .../python/templates/material/style.css | 24 +++++++ 6 files changed, 173 insertions(+), 54 deletions(-) diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index 83315d10..aa690bca 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -118,6 +118,7 @@ class PythonHandler(BaseHandler): "summary": False, "show_labels": True, "unwrap_annotated": False, + "parameter_headings": False, } """Default handler configuration. @@ -138,6 +139,7 @@ class PythonHandler(BaseHandler): Attributes: Headings options: 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`. @@ -426,7 +428,7 @@ def update_env(self, md: Markdown, config: dict) -> None: self.env.filters["format_signature"] = rendering.do_format_signature self.env.filters["format_attribute"] = rendering.do_format_attribute self.env.filters["filter_objects"] = rendering.do_filter_objects - self.env.filters["stash_crossref"] = lambda ref, length: ref + self.env.filters["stash_crossref"] = rendering.do_stash_crossref self.env.filters["get_template"] = rendering.do_get_template self.env.filters["as_attributes_section"] = rendering.do_as_attributes_section self.env.filters["as_functions_section"] = rendering.do_as_functions_section diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index 09be07ca..9c01f172 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -83,24 +83,26 @@ def do_format_code(code: str, line_length: int) -> str: return formatter(code, line_length) -_stash_key_alphabet = string.ascii_letters + string.digits +class _StashCrossRefFilter: + stash: ClassVar[dict[str, str]] = {} + @staticmethod + def _gen_key(length: int) -> str: + return "_" + "".join(random.choice(string.ascii_letters + string.digits) for _ in range(max(1, length - 1))) # noqa: S311 -def _gen_key(length: int) -> str: - return "_" + "".join(random.choice(_stash_key_alphabet) for _ in range(max(1, length - 1))) # noqa: S311 + def _gen_stash_key(self, length: int) -> str: + key = self._gen_key(length) + while key in self.stash: + key = self._gen_key(length) + return key + def __call__(self, crossref: str, *, length: int) -> str: + key = self._gen_stash_key(length) + self.stash[key] = crossref + return key -def _gen_stash_key(stash: dict[str, str], length: int) -> str: - key = _gen_key(length) - while key in stash: - key = _gen_key(length) - return key - -def _stash_crossref(stash: dict[str, str], crossref: str, *, length: int) -> str: - key = _gen_stash_key(stash, length) - stash[key] = crossref - return key +do_stash_crossref = _StashCrossRefFilter() def _format_signature(name: Markup, signature: str, line_length: int) -> str: @@ -129,7 +131,7 @@ def do_format_signature( line_length: int, *, annotations: bool | None = None, - crossrefs: bool = False, + crossrefs: bool = False, # noqa: ARG001 ) -> str: """Format a signature using Black. @@ -147,12 +149,6 @@ def do_format_signature( env = context.environment # TODO: Stop using `do_get_template` when `*.html` templates are removed. template = env.get_template(do_get_template(env, "signature")) - config_annotations = context.parent["config"]["show_signature_annotations"] - old_stash_ref_filter = env.filters["stash_crossref"] - - stash: dict[str, str] = {} - if (annotations or config_annotations) and crossrefs: - env.filters["stash_crossref"] = partial(_stash_crossref, stash) if annotations is None: new_context = context.parent @@ -160,11 +156,8 @@ def do_format_signature( new_context = dict(context.parent) new_context["config"] = dict(new_context["config"]) new_context["config"]["show_signature_annotations"] = annotations - try: - signature = template.render(new_context, function=function, signature=True) - finally: - env.filters["stash_crossref"] = old_stash_ref_filter + signature = template.render(new_context, function=function, signature=True) signature = _format_signature(callable_path, signature, line_length) signature = str( env.filters["highlight"]( @@ -184,9 +177,10 @@ def do_format_signature( if signature.find('class="nf"') == -1: signature = signature.replace('class="n"', 'class="nf"', 1) - if stash: + if stash := env.filters["stash_crossref"].stash: for key, value in stash.items(): signature = re.sub(rf"\b{key}\b", value, signature) + stash.clear() return signature @@ -198,7 +192,7 @@ def do_format_attribute( attribute: Attribute, line_length: int, *, - crossrefs: bool = False, + crossrefs: bool = False, # noqa: ARG001 ) -> str: """Format an attribute using Black. @@ -216,23 +210,14 @@ def do_format_attribute( # TODO: Stop using `do_get_template` when `*.html` templates are removed. template = env.get_template(do_get_template(env, "expression")) annotations = context.parent["config"]["show_signature_annotations"] - separate_signature = context.parent["config"]["separate_signature"] - old_stash_ref_filter = env.filters["stash_crossref"] - stash: dict[str, str] = {} - if separate_signature and crossrefs: - env.filters["stash_crossref"] = partial(_stash_crossref, stash) - - try: - signature = str(attribute_path).strip() - if annotations and attribute.annotation: - annotation = template.render(context.parent, expression=attribute.annotation, signature=True) - signature += f": {annotation}" - if attribute.value: - value = template.render(context.parent, expression=attribute.value, signature=True) - signature += f" = {value}" - finally: - env.filters["stash_crossref"] = old_stash_ref_filter + signature = str(attribute_path).strip() + if annotations and attribute.annotation: + annotation = template.render(context.parent, expression=attribute.annotation, signature=True) + signature += f": {annotation}" + if attribute.value: + value = template.render(context.parent, expression=attribute.value, signature=True) + signature += f" = {value}" signature = do_format_code(signature, line_length) signature = str( @@ -244,9 +229,10 @@ def do_format_attribute( ), ) - if stash: + if stash := env.filters["stash_crossref"].stash: for key, value in stash.items(): signature = re.sub(rf"\b{key}\b", value, signature) + stash.clear() return signature diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja index 8b0556f3..fef553b1 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja @@ -34,7 +34,21 @@ Context: {% for parameter in section.value %} - {{ parameter.name }} + + {% if config.parameter_headings %} + {% filter heading( + heading_level + 1, + role="param", + id=html_id ~ "(" ~ parameter.name ~ ")", + class="doc doc-heading doc-heading-parameter", + toc_label=(' '|safe if config.show_symbol_type_toc else '') + parameter.name, + ) %} + {{ parameter.name }} + {% endfilter %} + {% else %} + {{ parameter.name }} + {% endif %} + {% if parameter.annotation %} {% with expression = parameter.annotation %} @@ -68,7 +82,19 @@ Context:
    {% for parameter in section.value %}
  • - {{ parameter.name }} + {% if config.parameter_headings %} + {% filter heading( + heading_level + 1, + role="param", + id=html_id ~ "(" ~ parameter.name ~ ")", + class="doc doc-heading doc-heading-parameter", + toc_label=(' '|safe if config.show_symbol_type_toc else '') + parameter.name, + ) %} + {{ parameter.name }} + {% endfilter %} + {% else %} + {{ parameter.name }} + {% endif %} {% if parameter.annotation %} {% with expression = parameter.annotation %} ({% include "expression"|get_template with context %} @@ -100,7 +126,21 @@ Context: {% for parameter in section.value %} - {{ parameter.name }} + + {% if config.parameter_headings %} + {% filter heading( + heading_level + 1, + role="param", + id=html_id ~ "(" ~ parameter.name ~ ")", + class="doc doc-heading doc-heading-parameter", + toc_label=(' '|safe if config.show_symbol_type_toc else '') + parameter.name, + ) %} + {{ parameter.name }} + {% endfilter %} + {% else %} + {{ parameter.name }} + {% endif %} +
    {{ parameter.description|convert_markdown(heading_level, html_id, autoref_hook=autoref_hook) }} 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 f150afd1..c34c0be5 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja @@ -32,10 +32,14 @@ which is a tree-like structure representing a Python expression. {%- set annotation = full -%} {%- endif -%} {%- for title, path in annotation|split_path(full) -%} - {%- if not signature or config.signature_crossrefs -%} - {%- filter stash_crossref(length=title|length) -%} + {%- if config.signature_crossrefs -%} + {%- if signature -%} + {%- filter stash_crossref(length=title|length) -%} + {{ title }} + {%- endfilter -%} + {%- else -%} {{ title }} - {%- endfilter -%} + {%- endif -%} {%- else -%} {{ title }} {%- endif -%} @@ -44,6 +48,28 @@ which is a tree-like structure representing a Python expression. {%- endwith -%} {%- endmacro -%} +{%- macro param_crossref(expression) -%} + {#- Render a cross-reference to a parameter heading. + + Parameters: + expression (griffe.expressions.Expr): The expression to render. + + Returns: + The autorefs cross-reference, or the parameter name. + -#} + {%- if config.signature_crossrefs -%} + {%- if signature -%} + {%- filter stash_crossref(length=expression.name|length) -%} + {{ expression.name }} + {%- endfilter -%} + {%- else -%} + {{ expression.name }} + {%- endif -%} + {%- else -%} + {{ expression.name }} + {%- endif -%} +{%- endmacro -%} + {%- macro render(expression, annotations_path) -%} {#- Render an expression. @@ -79,6 +105,8 @@ which is a tree-like structure representing a Python expression. {{ render(element, annotations_path) }} {%- endfor -%} {%- endif -%} + {%- elif expression.classname == "ExprKeyword" -%} + {{ param_crossref(expression) }}={{ render(expression.value, annotations_path) }} {%- else -%} {%- for element in expression -%} {{ render(element, annotations_path) }} diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja index 1107458c..04379d0d 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja @@ -25,6 +25,7 @@ Context: render_kw_only_separator=True, annotation="", equal="=", + default=False, ) -%} ( @@ -60,7 +61,9 @@ Context: {#- Prepare default value. -#} {%- if parameter.default is not none and parameter.kind.value != "variadic positional" and parameter.kind.value != "variadic keyword" -%} - {%- set default = ns.equal + parameter.default|safe -%} + {%- set ns.default = True -%} + {%- else -%} + {%- set ns.default = False -%} {%- endif -%} {#- TODO: Move inside kind handling above? -#} @@ -68,9 +71,45 @@ Context: {%- set ns.render_kw_only_separator = False -%} {%- endif -%} - {#- Render name, annotation and default. -#} - {% if parameter.kind.value == "variadic positional" %}*{% elif parameter.kind.value == "variadic keyword" %}**{% endif -%} - {{ parameter.name }}{{ ns.annotation }}{{ default }} + {#- Prepare name. -#} + {%- set param_name -%} + {%- if parameter.kind.value == "variadic positional" -%} + * + {%- elif parameter.kind.value == "variadic keyword" -%} + ** + {%- endif -%} + {{ parameter.name }} + {%- endset -%} + + {#- Render parameter name with optional cross-reference to its heading. -#} + {%- if config.separate_signature and config.parameter_headings and config.signature_crossrefs -%} + {%- filter stash_crossref(length=param_name|length) -%} + {%- with func_path = function.path -%} + {%- if config.merge_init_into_class and func_path.endswith(".__init__") -%} + {%- set func_path = func_path[:-9] -%} + {%- endif -%} + {{ param_name }} + {%- endwith -%} + {%- endfilter -%} + {%- else -%} + {{ param_name }} + {%- endif -%} + + {#- Render parameter annotation. -#} + {{ ns.annotation }} + + {#- Render parameter default value. -#} + {%- if ns.default -%} + {{ ns.equal }} + {%- if config.signature_crossrefs and config.separate_signature -%} + {%- with expression = parameter.default -%} + {%- include "expression"|get_template with context -%} + {%- endwith -%} + {%- else -%} + {{ parameter.default }} + {%- endif -%} + {%- endif -%} + {%- if not loop.last %}, {% endif -%} {%- endif -%} diff --git a/src/mkdocstrings_handlers/python/templates/material/style.css b/src/mkdocstrings_handlers/python/templates/material/style.css index 9547fa5a..7e819d8b 100644 --- a/src/mkdocstrings_handlers/python/templates/material/style.css +++ b/src/mkdocstrings_handlers/python/templates/material/style.css @@ -25,6 +25,17 @@ float: right; } +/* Parameter headings must be inline, not blocks. */ +.doc-heading-parameter { + display: inline; +} + +/* Prefer space on the right, not the left of parameter permalinks. */ +.doc-heading-parameter .headerlink { + margin-left: 0 !important; + margin-right: 0.2rem; +} + /* Backward-compatibility: docstring section titles in bold. */ .doc-section-title { font-weight: bold; @@ -33,12 +44,14 @@ /* Symbols in Navigation and ToC. */ :root, :host, [data-md-color-scheme="default"] { + --doc-symbol-parameter-fg-color: #df50af; --doc-symbol-attribute-fg-color: #953800; --doc-symbol-function-fg-color: #8250df; --doc-symbol-method-fg-color: #8250df; --doc-symbol-class-fg-color: #0550ae; --doc-symbol-module-fg-color: #5cad0f; + --doc-symbol-parameter-bg-color: #df50af1a; --doc-symbol-attribute-bg-color: #9538001a; --doc-symbol-function-bg-color: #8250df1a; --doc-symbol-method-bg-color: #8250df1a; @@ -47,12 +60,14 @@ } [data-md-color-scheme="slate"] { + --doc-symbol-parameter-fg-color: #ffa8cc; --doc-symbol-attribute-fg-color: #ffa657; --doc-symbol-function-fg-color: #d2a8ff; --doc-symbol-method-fg-color: #d2a8ff; --doc-symbol-class-fg-color: #79c0ff; --doc-symbol-module-fg-color: #baff79; + --doc-symbol-parameter-bg-color: #ffa8cc1a; --doc-symbol-attribute-bg-color: #ffa6571a; --doc-symbol-function-bg-color: #d2a8ff1a; --doc-symbol-method-bg-color: #d2a8ff1a; @@ -67,6 +82,15 @@ code.doc-symbol { font-weight: bold; } +code.doc-symbol-parameter { + color: var(--doc-symbol-parameter-fg-color); + background-color: var(--doc-symbol-parameter-bg-color); +} + +code.doc-symbol-parameter::after { + content: "param"; +} + code.doc-symbol-attribute { color: var(--doc-symbol-attribute-fg-color); background-color: var(--doc-symbol-attribute-bg-color); From 0f2c25c9ed7f6c5c93ff13df214f02edfd3a4cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 12 Oct 2024 18:18:57 +0200 Subject: [PATCH 05/10] feat: Render function overloads --- .../templates/material/_base/function.html.jinja | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja index 3631b699..973c762e 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja @@ -77,8 +77,18 @@ Context: {% block signature scoped %} {#- Signature block. - This block renders the signature for the function. + This block renders the signature for the function, + as well as its overloaded signatures if any. -#} + {% if function.overloads %} +
    + {% for overload in function.overloads %} + {% filter format_signature(overload, config.line_length, annotations=True, crossrefs=config.signature_crossrefs) %} + {{ overload.name }} + {% endfilter %} + {% endfor %} +
    + {% endif %} {% if config.separate_signature %} {% filter format_signature(function, config.line_length, crossrefs=config.signature_crossrefs) %} {{ function.name }} From 7f9757d1584555edebc56f1aefe6cc8242e6c8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 12 Oct 2024 18:22:01 +0200 Subject: [PATCH 06/10] feat: Auto-summary of members --- src/mkdocstrings_handlers/python/rendering.py | 73 +++++++++++++++---- .../templates/material/_base/class.html.jinja | 8 ++ .../material/_base/module.html.jinja | 8 ++ .../material/_base/summary.html.jinja | 18 +++++ .../_base/summary/attributes.html.jinja | 13 ++++ .../material/_base/summary/classes.html.jinja | 13 ++++ .../_base/summary/functions.html.jinja | 13 ++++ .../material/_base/summary/modules.html.jinja | 13 ++++ 8 files changed, 144 insertions(+), 15 deletions(-) diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index 9c01f172..e3e9530f 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -8,13 +8,17 @@ import string import sys import warnings -from functools import lru_cache, partial +from functools import lru_cache from pathlib import Path from re import Match, Pattern -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any, Callable, ClassVar from griffe import ( Alias, + DocstringAttribute, + DocstringClass, + DocstringFunction, + DocstringModule, DocstringSectionAttributes, DocstringSectionClasses, DocstringSectionFunctions, @@ -481,9 +485,9 @@ def do_get_template(env: Environment, obj: str | Object) -> str | Template: @pass_context def do_as_attributes_section( context: Context, # noqa: ARG001 - attributes: Sequence[Attribute], # noqa: ARG001 + attributes: Sequence[Attribute], *, - check_public: bool = True, # noqa: ARG001 + check_public: bool = True, ) -> DocstringSectionAttributes: """Build an attributes section from a list of attributes. @@ -494,15 +498,26 @@ def do_as_attributes_section( Returns: An attributes docstring section. """ - return DocstringSectionAttributes([]) + return DocstringSectionAttributes( + [ + DocstringAttribute( + name=attribute.name, + description=attribute.docstring.value.split("\n", 1)[0] if attribute.docstring else "", + annotation=attribute.annotation, + value=attribute.value, # type: ignore[arg-type] + ) + for attribute in attributes + if not check_public or attribute.is_public + ], + ) @pass_context def do_as_functions_section( - context: Context, # noqa: ARG001 - functions: Sequence[Function], # noqa: ARG001 + context: Context, + functions: Sequence[Function], *, - check_public: bool = True, # noqa: ARG001 + check_public: bool = True, ) -> DocstringSectionFunctions: """Build a functions section from a list of functions. @@ -513,15 +528,25 @@ def do_as_functions_section( Returns: A functions docstring section. """ - return DocstringSectionFunctions([]) + keep_init_method = not context.parent["config"]["merge_init_into_class"] + return DocstringSectionFunctions( + [ + DocstringFunction( + name=function.name, + description=function.docstring.value.split("\n", 1)[0] if function.docstring else "", + ) + for function in functions + if (not check_public or function.is_public) and (function.name != "__init__" or keep_init_method) + ], + ) @pass_context def do_as_classes_section( context: Context, # noqa: ARG001 - classes: Sequence[Class], # noqa: ARG001 + classes: Sequence[Class], *, - check_public: bool = True, # noqa: ARG001 + check_public: bool = True, ) -> DocstringSectionClasses: """Build a classes section from a list of classes. @@ -532,15 +557,24 @@ def do_as_classes_section( Returns: A classes docstring section. """ - return DocstringSectionClasses([]) + return DocstringSectionClasses( + [ + DocstringClass( + name=cls.name, + description=cls.docstring.value.split("\n", 1)[0] if cls.docstring else "", + ) + for cls in classes + if not check_public or cls.is_public + ], + ) @pass_context def do_as_modules_section( context: Context, # noqa: ARG001 - modules: Sequence[Module], # noqa: ARG001 + modules: Sequence[Module], *, - check_public: bool = True, # noqa: ARG001 + check_public: bool = True, ) -> DocstringSectionModules: """Build a modules section from a list of modules. @@ -551,7 +585,16 @@ def do_as_modules_section( Returns: A modules docstring section. """ - return DocstringSectionModules([]) + return DocstringSectionModules( + [ + DocstringModule( + name=module.name, + description=module.docstring.value.split("\n", 1)[0] if module.docstring else "", + ) + for module in modules + if not check_public or module + ], + ) class AutorefsHook(AutorefsHookInterface): diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja index fd13661b..24046e8f 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja @@ -142,6 +142,14 @@ Context: {% endif %} {% endblock docstring %} + {% block summary scoped %} + {#- Summary block. + + This block renders auto-summaries for classes, methods, and attributes. + -#} + {% include "summary"|get_template with context %} + {% endblock summary %} + {% block source scoped %} {#- Source block. diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/module.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/module.html.jinja index ae7d88d9..2085b95d 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/module.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/module.html.jinja @@ -97,6 +97,14 @@ Context: {% endwith %} {% endblock docstring %} + {% block summary scoped %} + {#- Summary block. + + This block renders auto-summaries for classes, methods, and attributes. + -#} + {% include "summary"|get_template with context %} + {% endblock summary %} + {% block children scoped %} {#- Children block. diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary.html.jinja index 5770fdb0..508e5660 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/summary.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary.html.jinja @@ -6,3 +6,21 @@ This block can be used to log debug messages, deprecation messages, warnings, etc. -#} {% endblock logs %} + +{% with members_list = config.members if root_members else None %} + {% if config.summary.modules %} + {% include "summary/modules"|get_template with context %} + {% endif %} + + {% if config.summary.classes %} + {% include "summary/classes"|get_template with context %} + {% endif %} + + {% if config.summary.functions %} + {% include "summary/functions"|get_template with context %} + {% endif %} + + {% if config.summary.attributes %} + {% include "summary/attributes"|get_template with context %} + {% endif %} +{% endwith %} diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja index cb966fb1..65b7c5b8 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/attributes.html.jinja @@ -6,3 +6,16 @@ This block can be used to log debug messages, deprecation messages, warnings, etc. -#} {% endblock logs %} + +{% with section = obj.attributes + |filter_objects( + filters=config.filters, + members_list=members_list, + inherited_members=config.inherited_members, + keep_no_docstrings=config.show_if_no_docstring, + ) + |order_members(config.members_order, members_list) + |as_attributes_section(check_public=not members_list) + %} + {% if section %}{% include "docstring/attributes"|get_template with context %}{% endif %} +{% endwith %} diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja index 94456775..b9792ada 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/classes.html.jinja @@ -6,3 +6,16 @@ This block can be used to log debug messages, deprecation messages, warnings, etc. -#} {% endblock logs %} + +{% with section = obj.classes + |filter_objects( + filters=config.filters, + members_list=members_list, + inherited_members=config.inherited_members, + keep_no_docstrings=config.show_if_no_docstring, + ) + |order_members(config.members_order, members_list) + |as_classes_section(check_public=not members_list) + %} + {% if section %}{% include "docstring/classes"|get_template with context %}{% endif %} +{% endwith %} diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja index 5e8305aa..ad8b6f88 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/functions.html.jinja @@ -6,3 +6,16 @@ This block can be used to log debug messages, deprecation messages, warnings, etc. -#} {% endblock logs %} + +{% with section = obj.functions + |filter_objects( + filters=config.filters, + members_list=members_list, + inherited_members=config.inherited_members, + keep_no_docstrings=config.show_if_no_docstring, + ) + |order_members(config.members_order, members_list) + |as_functions_section(check_public=not members_list) + %} + {% if section %}{% include "docstring/functions"|get_template with context %}{% endif %} +{% endwith %} diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja index 04387b32..35d6e110 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/summary/modules.html.jinja @@ -6,3 +6,16 @@ This block can be used to log debug messages, deprecation messages, warnings, etc. -#} {% endblock logs %} + +{% with section = obj.modules + |filter_objects( + filters=config.filters, + members_list=members_list, + inherited_members=config.inherited_members, + keep_no_docstrings=config.show_if_no_docstring, + ) + |order_members(config.members_order.alphabetical, members_list) + |as_modules_section(check_public=not members_list) + %} + {% if section %}{% include "docstring/modules"|get_template with context %}{% endif %} +{% endwith %} From 701ba60b75be0db025f04d673599575d3963553e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 12 Oct 2024 18:26:58 +0200 Subject: [PATCH 07/10] docs: Various documentation updates --- docs/usage/configuration/headings.md | 1 - docs/usage/configuration/members.md | 1 - pyproject.toml | 1 + src/mkdocstrings_handlers/python/handler.py | 6 +++++- src/mkdocstrings_handlers/python/rendering.py | 2 ++ .../templates/readthedocs/_base/language.html.jinja | 10 +++++----- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/usage/configuration/headings.md b/docs/usage/configuration/headings.md index 63950206..467779e4 100644 --- a/docs/usage/configuration/headings.md +++ b/docs/usage/configuration/headings.md @@ -59,7 +59,6 @@ plugins: ## `parameter_headings` -[:octicons-heart-fill-24:{ .pulse } Sponsors only](../../insiders/index.md){ .insiders } — [:octicons-tag-24: Insiders 1.6.0](../../insiders/changelog.md#1.6.0) - **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }** diff --git a/docs/usage/configuration/members.md b/docs/usage/configuration/members.md index 119d8294..220a26fe 100644 --- a/docs/usage/configuration/members.md +++ b/docs/usage/configuration/members.md @@ -552,7 +552,6 @@ package ## `summary` -[:octicons-heart-fill-24:{ .pulse } Sponsors only](../../insiders/index.md){ .insiders } — [:octicons-tag-24: Insiders 1.2.0](../../insiders/changelog.md#1.2.0) - **:octicons-package-24: Type bool | dict[str, bool] :material-equal: `False`{ title="default value" }** diff --git a/pyproject.toml b/pyproject.toml index 2d5640c4..636a67fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.14", "Topic :: Documentation", "Topic :: Software Development", + "Topic :: Software Development :: Documentation", "Topic :: Utilities", "Typing :: Typed", ] diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py index aa690bca..0aac3cdc 100644 --- a/src/mkdocstrings_handlers/python/handler.py +++ b/src/mkdocstrings_handlers/python/handler.py @@ -104,6 +104,7 @@ class PythonHandler(BaseHandler): "show_docstring_yields": True, "show_source": True, "show_bases": True, + "show_inheritance_diagram": False, "show_submodules": False, "group_by_category": True, "heading_level": 2, @@ -119,6 +120,7 @@ class PythonHandler(BaseHandler): "show_labels": True, "unwrap_annotated": False, "parameter_headings": False, + "modernize_annotations": False, } """Default handler configuration. @@ -126,6 +128,7 @@ class PythonHandler(BaseHandler): 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`. 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`). @@ -200,6 +203,7 @@ class PythonHandler(BaseHandler): separate_signature (bool): Whether to put the whole signature in a code block below the heading. If Black 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__( @@ -274,7 +278,7 @@ def load_inventory( ) -> Iterator[tuple[str, str]]: """Yield items and their URLs from an inventory file streamed from `in_file`. - This implements mkdocstrings' `load_inventory` "protocol" (see [`mkdocstrings.plugin`][mkdocstrings.plugin]). + This implements mkdocstrings' `load_inventory` "protocol" (see [`mkdocstrings.plugin`][]). Arguments: in_file: The binary file-like object to read the inventory from. diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py index e3e9530f..a7ea38f7 100644 --- a/src/mkdocstrings_handlers/python/rendering.py +++ b/src/mkdocstrings_handlers/python/rendering.py @@ -612,7 +612,9 @@ def __init__(self, current_object: Object | Alias, config: dict[str, Any]) -> No config: The configuration dictionary. """ self.current_object = current_object + """The current object being rendered.""" self.config = config + """The configuration options.""" def expand_identifier(self, identifier: str) -> str: """Expand an identifier. diff --git a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/language.html.jinja b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/language.html.jinja index 5b643726..1465471d 100644 --- a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/language.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/language.html.jinja @@ -9,10 +9,10 @@ {% set lang_pth = "languages/" ~ locale | get_template %} {% if lang_pth is existing_template %} - {% import lang_pth as lang %} - {% import "languages/en"|get_template as fallback %} - {% macro t(key) %}{{ lang.t(key) or fallback.t(key) }}{% endmacro %} + {% import lang_pth as lang %} + {% import "languages/en"|get_template as fallback %} + {% macro t(key) %}{{ lang.t(key) or fallback.t(key) }}{% endmacro %} {% else %} - {% import "languages/en"|get_template as lang %} - {% macro t(key) %}{{ lang.t(key) }}{% endmacro %} + {% import "languages/en"|get_template as lang %} + {% macro t(key) %}{{ lang.t(key) }}{% endmacro %} {% endif %} From e45502207ff4cd9d830c3affccc8ebb1c3e73da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Sat, 12 Oct 2024 18:28:45 +0200 Subject: [PATCH 08/10] chore: Prepare release 1.12.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1668147..1633ebe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ 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.12.0](https://github.com/mkdocstrings/python/releases/tag/1.12.0) - 2024-10-12 + +[Compare with 1.11.1](https://github.com/mkdocstrings/python/compare/1.11.1...1.12.0) + +### Build + +- Drop support for Python 3.8 ([6615c91](https://github.com/mkdocstrings/python/commit/6615c91cdc035bc0c2fdd12f3952ff84f5e1c04e) by Timothée Mazzucotelli). + +### Features + +- Auto-summary of members ([7f9757d](https://github.com/mkdocstrings/python/commit/7f9757d1584555edebc56f1aefe6cc8242e6c8bb) by Timothée Mazzucotelli). +- Render function overloads ([0f2c25c](https://github.com/mkdocstrings/python/commit/0f2c25c9ed7f6c5c93ff13df214f02edfd3a4cb1) by Timothée Mazzucotelli). +- Parameter headings, more automatic cross-references ([0176b83](https://github.com/mkdocstrings/python/commit/0176b83f21ae02d345489c93cca3baf51f8bc58c) by Timothée Mazzucotelli). + +### Code Refactoring + +- Declare default CSS symbol colors under :host as well ([3b9dba2](https://github.com/mkdocstrings/python/commit/3b9dba2709a8668e379c6ce1536cb1714971b3f4) by James McDonnell). [PR-186](https://github.com/mkdocstrings/python/pull/186) + ## [1.11.1](https://github.com/mkdocstrings/python/releases/tag/1.11.1) - 2024-09-03 [Compare with 1.11.0](https://github.com/mkdocstrings/python/compare/1.11.0...1.11.1) From 9dee4d4f8e1258e99c19dc7b2b18d3e9090de79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 14 Oct 2024 13:33:45 +0200 Subject: [PATCH 09/10] fix: Don't escape parameter default values Issue-191: https://github.com/mkdocstrings/python/issues/191 --- .../python/templates/material/_base/signature.html.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja index 04379d0d..641b8b8d 100644 --- a/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja +++ b/src/mkdocstrings_handlers/python/templates/material/_base/signature.html.jinja @@ -106,7 +106,7 @@ Context: {%- include "expression"|get_template with context -%} {%- endwith -%} {%- else -%} - {{ parameter.default }} + {{ parameter.default|safe }} {%- endif -%} {%- endif -%} From e6b7542d11971ed51189e4ac6a04dc4233e391a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 14 Oct 2024 13:33:58 +0200 Subject: [PATCH 10/10] chore: Prepare release 1.12.1 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1633ebe3..9d9f4c0d 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.12.1](https://github.com/mkdocstrings/python/releases/tag/1.12.1) - 2024-10-14 + +[Compare with 1.12.0](https://github.com/mkdocstrings/python/compare/1.12.0...1.12.1) + +### Bug Fixes + +- Don't escape parameter default values ([9dee4d4](https://github.com/mkdocstrings/python/commit/9dee4d4f8e1258e99c19dc7b2b18d3e9090de79b) by Timothée Mazzucotelli). [Issue-191](https://github.com/mkdocstrings/python/issues/191) + ## [1.12.0](https://github.com/mkdocstrings/python/releases/tag/1.12.0) - 2024-10-12 [Compare with 1.11.1](https://github.com/mkdocstrings/python/compare/1.11.1...1.12.0)