Title: regression(2.0.9): relative cross-references in re-exported and inherited objects expand against private modules · Issue #346 · mkdocstrings/python · GitHub
Open Graph Title: regression(2.0.9): relative cross-references in re-exported and inherited objects expand against private modules · Issue #346 · mkdocstrings/python
X Title: regression(2.0.9): relative cross-references in re-exported and inherited objects expand against private modules · Issue #346 · mkdocstrings/python
Description: Description of the bug Since 2.0.9, relative cross-references are expanded against the module where an object is defined rather than the path where it's documented. This breaks the very common layout of defining things in private modules...
Open Graph Description: Description of the bug Since 2.0.9, relative cross-references are expanded against the module where an object is defined rather than the path where it's documented. This breaks the very common layo...
X Description: Description of the bug Since 2.0.9, relative cross-references are expanded against the module where an object is defined rather than the path where it's documented. This breaks the very common ...
Opengraph URL: https://github.com/mkdocstrings/python/issues/346
X: @github
Domain: redirect.github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"regression(2.0.9): relative cross-references in re-exported and inherited objects expand against private modules","articleBody":"### Description of the bug\n\nSince 2.0.9, relative cross-references are expanded against the module where an object is **defined** rather than the path where it's **documented**. This breaks the very common layout of defining things in private modules and re-exporting them from a public one, which used to work fine up to 2.0.8.\n\nThe cause is a9a4ca3 (#342), the fix for #341, which I reported. It anchors the dot-walk on `docstring.parent` whenever it's set. That's what #341 needed for members inherited from another package, but `docstring.parent` is also set for every alias, and there it points at the private defining object. I mentioned that risk at the end of #341, but I didn't realize how common the affected case is, sorry about that.\n\nThere are three variants, all shown by the reproducer below:\n\n1. **A re-exported object**: `pkg.Base` is an alias to `pkg._base.Base`, so `[..Derived]` in its docstring now expands to `pkg._base.Derived` instead of `pkg.Derived`.\n2. **A member of a re-exported class**: `pkg.Base.method` gets `pkg._base.helper` instead of `pkg.helper`.\n3. **An inherited member whose base class is defined in a private module**: `pkg.Derived.method` gets `pkg._base.helper` too.\n\nReferences only keep working when the target happens to be defined in the same private module, because then the expanded canonical path still matches an anchor. That makes the breakage look random from the outside.\n\n### To Reproduce\n\n```bash\nmkdir -p repro/pkg repro/docs\ncd repro\n\ncat > pkg/__init__.py \u003c\u003c'EOF'\n\"\"\"The package.\"\"\"\n\nfrom pkg._base import Base\nfrom pkg._derived import Derived\nfrom pkg._helpers import helper\n\n__all__ = [\"Base\", \"Derived\", \"helper\"]\nEOF\n\ncat > pkg/_helpers.py \u003c\u003c'EOF'\n\"\"\"Private module defining helpers.\"\"\"\n\n\ndef helper() -> None:\n \"\"\"A helper.\"\"\"\nEOF\n\ncat > pkg/_base.py \u003c\u003c'EOF'\n\"\"\"Private module defining the base class.\"\"\"\n\n\nclass Base:\n \"\"\"A base class, see also [`Derived`][..Derived].\"\"\"\n\n def method(self) -> None:\n \"\"\"A method, see also [`helper`][...helper].\"\"\"\nEOF\n\ncat > pkg/_derived.py \u003c\u003c'EOF'\n\"\"\"Private module defining the derived class.\"\"\"\n\nfrom pkg._base import Base\n\n\nclass Derived(Base):\n \"\"\"A derived class.\"\"\"\nEOF\n\nprintf '# Package\\n\\n::: pkg\\n' > docs/index.md\n\ncat > mkdocs.yml \u003c\u003c'EOF'\nsite_name: pkg\nstrict: true\nplugins:\n- mkdocstrings:\n handlers:\n python:\n options:\n inherited_members: true\n relative_crossrefs: true\nEOF\n\nfor version in 2.0.8 2.0.9; do\n echo \"### mkdocstrings-python $version\"\n python -m venv .venv-$version\n .venv-$version/bin/pip install -q --disable-pip-version-check mkdocs mkdocstrings mkdocstrings-python==$version\n PYTHONPATH=. .venv-$version/bin/mkdocs build 2>&1 | grep -v '^INFO'\ndone\n```\n\nOutput:\n\n```\n### mkdocstrings-python 2.0.8\n### mkdocstrings-python 2.0.9\nWARNING - mkdocs_autorefs: index.md: from /tmp/repro/pkg/_base.py:5: (pkg.Base) Could not find cross-reference target 'pkg._base.Derived'\nWARNING - mkdocs_autorefs: index.md: from /tmp/repro/pkg/_base.py:8: (pkg.Base.method) Could not find cross-reference target 'pkg._base.helper'\nWARNING - mkdocs_autorefs: index.md: from /tmp/repro/pkg/_base.py:8: (pkg.Derived.method) Could not find cross-reference target 'pkg._base.helper'\n\nAborted with 3 warnings in strict mode!\n```\n\n### Full traceback\n\nThere is no traceback, only the warnings above, which abort the build in strict mode.\n\n### Expected behavior\n\nThe same as in 2.0.8: relative references expand from the path where the object is documented, `pkg.Derived` and `pkg.helper` here.\n\n### Environment information\n\n- __System__: Linux-6.12.111+deb13-amd64-x86_64-with-glibc2.42\n- __Python__: cpython 3.12.14\n- __Installed packages__:\n - `mkdocs` v1.6.1\n - `mkdocstrings` v1.0.6\n - `mkdocstrings-python` v2.0.9\n - `mkdocs-autorefs` v1.4.4\n - `griffelib` v2.3.0\n\n### Additional context\n\nThis breaks the docs builds of several projects in the [frequenz-floss](https://github.com/frequenz-floss) organization when updating to 2.0.9. For example, frequenz-floss/frequenz-core-python#201 (3 warnings), frequenz-floss/frequenz-channels-python#560 (28), frequenz-floss/frequenz-dispatch-python#306 (6) and frequenz-floss/frequenz-client-common-python#291 (330). All of them build cleanly with 2.0.8.\n\nAbout a fix, I looked into a narrow one first: keeping the new anchor only for inherited aliases (`self.current_object.inherited or not self.current_object.is_alias`). That fixes variants 1 and 2, but not variant 3, which is most of the client-common warnings. For inherited members, the defining object is the right tree but the wrong path whenever the base class lives in a private module, and I couldn't find a reliable way to get its public path. Restricting the new anchor to members inherited from *another package* doesn't work either: `Alias.package` returns the target's package, and namespace packages like `frequenz.*` share the same top module.\n\nSo I'd suggest reverting a9a4ca3 if we don't find a proper fix soon, which brings back the 2.0.8 behavior and with it the cross-package case of #341. That seems like the smaller problem: it only affects relative references in docstrings inherited from another package, ~~it has the workaround discussed in #344,~~ and a proper fix would need the kind of cross-reference rework that, as you said there, is better left to Zensical. This regression, on the other hand, affects the most common package layout. I can send a PR with the revert plus regression tests for the three variants above, if you agree.\n\n","author":{"url":"https://github.com/llucax","@type":"Person","name":"llucax"},"datePublished":"2026-10-02T13:11:12Z","url":"https://github.com/mkdocstrings/python/issues/346"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:0a599789-ae47-bd15-b5f4-bd1567873144 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | DF40:2536B8:76C843:9CD378:6AF84401 |
| html-safe-nonce | eb41604d2d209fe4535ad1ab61f3ced409e51dc25f9e74c620415f4546d14b87 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJERjQwOjI1MzZCODo3NkM4NDM6OUNEMzc4OjZBRjg0NDAxIiwidmlzaXRvcl9pZCI6Ijg4NTU3MzQ4MDI4NjI3NDU2MSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 2f5ccc3770e6a89d0c04f8590ae8b5abbf970fe6b9a4f2f895286a1535ce1c0e |
| hovercard-subject-tag | issue:5677891277 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/mkdocstrings/python/346/issue_layout |
| twitter:image | https://opengraph.githubassets.com/3ce4c81a39411b9eab50806d04c92d35d0788f1867bb3969e8d6affbfcc98401/mkdocstrings/python/issues/346 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/3ce4c81a39411b9eab50806d04c92d35d0788f1867bb3969e8d6affbfcc98401/mkdocstrings/python/issues/346 |
| og:image:alt | Description of the bug Since 2.0.9, relative cross-references are expanded against the module where an object is defined rather than the path where it's documented. This breaks the very common layo... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | llucax |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5713ecd83001b2f55c3509fb7a292bf7d66d0f167c7ae87a9f7eaec5a52bf8db |
| turbo-cache-control | no-preview |
| go-import | github.com/mkdocstrings/python git https://github.com/mkdocstrings/python.git |
| octolytics-dimension-user_id | 75664361 |
| octolytics-dimension-user_login | mkdocstrings |
| octolytics-dimension-repository_id | 422878404 |
| octolytics-dimension-repository_nwo | mkdocstrings/python |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 422878404 |
| octolytics-dimension-repository_network_root_nwo | mkdocstrings/python |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 653be32ff5c755082a4db662491bfbbcfc9112c1 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width