From 9051b8dc91b1f96df45f3852bffbfca169fc1659 Mon Sep 17 00:00:00 2001 From: Matthias Schoettle Date: Mon, 27 Apr 2026 21:32:27 -0400 Subject: [PATCH] fix: replace legacy python handler with new mkdocstrings-python handler --- docs/gen_reference_nav.py | 17 +++++++--- docs/mock_django.py | 68 --------------------------------------- mkdocs.yml | 15 ++------- pyproject.toml | 3 +- uv.lock | 41 +++++++++++------------ 5 files changed, 35 insertions(+), 109 deletions(-) delete mode 100644 docs/mock_django.py diff --git a/docs/gen_reference_nav.py b/docs/gen_reference_nav.py index 3973a9083..376aa715d 100644 --- a/docs/gen_reference_nav.py +++ b/docs/gen_reference_nav.py @@ -20,23 +20,30 @@ nav = mkdocs_gen_files.Nav() # type: ignore[no-untyped-call] for path in sorted(Path(CODE_ROOT).glob('**/*.py')): + module_path = path.relative_to('.').with_suffix('') doc_path = Path('reference', path.relative_to('.')).with_suffix('.md') + + parts = tuple(module_path.parts) + + if parts[-1] == '__init__': + parts = parts[:-1] + doc_path = doc_path.with_name('index.md') + # fully qualified name - ident = '.'.join(path.relative_to('.').with_suffix('').parts) + ident = '.'.join(parts) # skip database migrations - if '.migrations' in ident: + if 'migrations' in parts: continue # add mapping from Python file name to generated Markdown page # the path needs to be relative to the SUMMARY.md file # i.e., without "reference/" prefix if the index is in the same directory - parts = tuple(path.parts) - nav[parts] = str(path.relative_to('.').with_suffix('.md')) + nav[parts] = doc_path.relative_to('reference').as_posix() # write Markdown file for module with mkdocs_gen_files.open(doc_path, 'w') as fd: - print(f'::: {ident}', file=fd) + fd.write(f'::: {ident}\n') mkdocs_gen_files.set_edit_path(doc_path, Path('..', path)) diff --git a/docs/mock_django.py b/docs/mock_django.py deleted file mode 100644 index b6f0435b0..000000000 --- a/docs/mock_django.py +++ /dev/null @@ -1,68 +0,0 @@ -# noqa: INP001 -# SPDX-FileCopyrightText: Copyright (C) 2022 Opal Health Informatics Group at the Research Institute of the McGill University Health Centre -# -# SPDX-License-Identifier: AGPL-3.0-or-later - -"""Mocks pytkdocs to workaround issues with Django and Django REST framework.""" - -from typing import Any -from unittest.mock import MagicMock - -from pytkdocs.loader import Loader, ObjectNode, split_attr_name -from pytkdocs.objects import Object - -# keep original add_fields in order to be able to call it in certain cases when mocking -original_add_fields = Loader.add_fields -origin_detect_field_model = Loader.detect_field_model - - -def add_fields(loader: Loader, node: ObjectNode, root_object: Object, *args: Any, **kwargs: Any) -> None: - """ - Mock the `add_fields` method to not add fields for `Meta` special classes. - - See: https://github.com/mkdocstrings/mkdocstrings/issues/141 - - Args: - loader: the `pytkdocs` loader instance - node: the current object node - root_object: the current object - args: additional arguments - kwargs: additional keyword arguments - """ - if not str(root_object).endswith('.Meta.model'): - original_add_fields(loader, node, root_object, *args, **kwargs) - - -def detect_field_model(loader: Loader, attr_name: str, direct_members: Any, all_members: Any) -> bool: - """ - Mock the `detect_field_model` method to ignore errors with factory-boy `DjangoModelFactory`. - - Causes `AttributeError: 'DjangoOptions' object has no attribute 'get_fields'` - - See: https://github.com/mkdocstrings/pytkdocs/issues/142 - - Args: - loader: the `pytkdocs` loader instance - attr_name: The name of the attribute to detect, can contain dots - direct_members: The direct members of the class - all_members: All members of the class - - Returns: - Whether the attribute is present. - """ - first_order_attr_name, remainder = split_attr_name(attr_name) - - if ( - remainder - and first_order_attr_name in all_members - and not hasattr(all_members[first_order_attr_name], remainder) - ): - return False - - return origin_detect_field_model(loader, attr_name, direct_members, all_members) - - -# mock in order to avoid missing metadata error caused by Django REST framework -Loader.get_marshmallow_field_documentation = MagicMock() # type: ignore[method-assign] -Loader.add_fields = add_fields # type: ignore[assignment] -Loader.detect_field_model = detect_field_model # type: ignore[assignment] diff --git a/mkdocs.yml b/mkdocs.yml index 7385b2e0a..f313fbc5e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -76,24 +76,13 @@ plugins: - gen-files: scripts: - docs/gen_reference_nav.py - # # auto-generate navigation structure for code reference + # auto-generate navigation structure for code reference - literate-nav: nav_file: SUMMARY.md - mkdocstrings: - default_handler: python handlers: python: - setup_commands: - - import os - - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.development") - - import django - - django.setup() - # mock pytkdocs to workaround Django and Django REST framework issues - - import docs.mock_django - options: - # filter out Pydantic internal attributes - filters: ['!^__'] - import: + inventories: # import Sphinx objects inventories to support referencing other elements in docs - https://docs.python.org/3/objects.inv - https://docs.djangoproject.com/en/dev/_objects/ diff --git a/pyproject.toml b/pyproject.toml index fd4719996..2209f6ed3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,8 @@ docs = [ "mkdocs-gen-files==0.6.0", "mkdocs-literate-nav==0.6.2", "mkdocs-material==9.7.6", - "mkdocstrings[python-legacy]==1.0.4", + "mkdocstrings==1.0.4", + "mkdocstrings-python==2.0.3", ] [tool.uv] diff --git a/uv.lock b/uv.lock index 138f8830e..666b3578c 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-04-20T23:49:33.786538Z" +exclude-newer = "2026-04-21T01:09:30.155458Z" exclude-newer-span = "P7D" [[package]] @@ -93,7 +93,8 @@ docs = [ { name = "mkdocs-gen-files" }, { name = "mkdocs-literate-nav" }, { name = "mkdocs-material" }, - { name = "mkdocstrings", extra = ["python-legacy"] }, + { name = "mkdocstrings" }, + { name = "mkdocstrings-python" }, ] [package.metadata] @@ -172,7 +173,8 @@ docs = [ { name = "mkdocs-gen-files", specifier = "==0.6.0" }, { name = "mkdocs-literate-nav", specifier = "==0.6.2" }, { name = "mkdocs-material", specifier = "==9.7.6" }, - { name = "mkdocstrings", extras = ["python-legacy"], specifier = "==1.0.4" }, + { name = "mkdocstrings", specifier = "==1.0.4" }, + { name = "mkdocstrings-python", specifier = "==2.0.3" }, ] [[package]] @@ -1002,6 +1004,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "griffelib" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, +] + [[package]] name = "gunicorn" version = "25.3.0" @@ -1319,23 +1330,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b", size = 35560, upload-time = "2026-04-15T09:16:51.436Z" }, ] -[package.optional-dependencies] -python-legacy = [ - { name = "mkdocstrings-python-legacy" }, -] - [[package]] -name = "mkdocstrings-python-legacy" -version = "0.2.7" +name = "mkdocstrings-python" +version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "griffelib" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, - { name = "pytkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/4b/3b1724ae9d82646c81f9dda3a7e3a0101f433e0b9e4344157b6e3a4e48b2/mkdocstrings_python_legacy-0.2.7.tar.gz", hash = "sha256:1aa8a277a332fb0d49be3786de3fa18af7d8792e8d611f6ba8d550dc3a1ff8a1", size = 99605, upload-time = "2025-05-22T10:58:41.657Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/25/b353bc04f24502ea0afe129b3045b73f3297e828e4baaf33c286417a9657/mkdocstrings_python_legacy-0.2.7-py3-none-any.whl", hash = "sha256:c24a0a5867749fd826dfa190e10afcbd4c415cbcb2f805fc4791472b4bf221e9", size = 29263, upload-time = "2025-05-22T10:58:40.161Z" }, + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, ] [[package]] @@ -1922,15 +1928,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/bd/ccd7416fdb30f104ddf6cfd8ee9f699441c7d9880a26f9b3089438adee05/python_ipware-3.0.0-py3-none-any.whl", hash = "sha256:fc936e6e7ec9fcc107f9315df40658f468ac72f739482a707181742882e36b60", size = 10761, upload-time = "2024-04-19T20:00:57.171Z" }, ] -[[package]] -name = "pytkdocs" -version = "0.16.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/fc/f991897d8f8ecdeb61c4835507c5b07bc6bb6a024558ffe78cc5bb59fb38/pytkdocs-0.16.5.tar.gz", hash = "sha256:103cdb3f0298c392bc340ae9aa7cd8685b163ae2aec65660dd0018978ddf4fa7", size = 84010, upload-time = "2025-03-09T17:32:42.398Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/29/b2be72ba559df54647e6ad2ff6cf1a94d488857ee67eeae28b252a6e734d/pytkdocs-0.16.5-py3-none-any.whl", hash = "sha256:7feb2535a8582d891fa5353cf2f4ecc7840915e631d322dbcbc1c8cc605da23a", size = 38823, upload-time = "2025-03-09T17:32:40.9Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3"