gp-sphinx(feat[autodoc]): Component autodoc for docutils + Sphinx#53
Conversation
why: Issue #52 extends component autodoc to transforms, readers, parsers, writers, nodes, and translators. Those classes have no existing Sphinx domain objtype, so cross-references and anchors need a home before the per-type autodoc directives can land. what: - Add DocutilsDomain (name="docutils") with six object types, an XRefRole per objtype, and a grouped-by-objtype component index, following the ArgparseDomain lifecycle shape (note/clear/merge/ resolve/get_objects) for parallel-safe builds - Add DocutilsComponentDescription, a generic ObjectDescription whose signature is a dotted Python path (module as desc_addname, class as desc_name); add_target_and_index owns the anchor and notes the component into the domain - Lookup accepts fully-qualified paths and unambiguous bare class names - Register the domain in setup(); stub add_domain in the shared-stack setup test app
why: Issue #52 adds builder and domain autodoc to this package. Like the docutils component types, Builder and Domain subclasses have no existing Sphinx objtype, so cross-references and anchors need a home before autobuilder/autodomain can land. what: - Add SphinxExtDomain (name="sphinxext") with builder/domain object types, an XRefRole per objtype, and a grouped-by-objtype component index, mirroring the DocutilsDomain lifecycle shape - Add SphinxExtComponentDescription rendering dotted Python paths as module desc_addname + class desc_name, owning anchors and domain notes - Register the domain in setup(); add tests/ext/autodoc_sphinx package __init__ for parity with sibling test packages
d794d16 to
c816bd6
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #53 +/- ##
==========================================
+ Coverage 91.90% 92.38% +0.48%
==========================================
Files 233 256 +23
Lines 18223 20188 +1965
==========================================
+ Hits 16747 18651 +1904
- Misses 1476 1537 +61 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Code reviewFound 3 issues:
(compare with the explicit-toggle block, which has them:
🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
why: Issue #52 — transforms are the first of six docutils component types gaining registry-aware autodoc. django-docutils ships three Transform subclasses documented today with plain automodule; this gives them option-table-grade output with priority and phase facts. what: - Add autotransform/autotransforms directives discovering transforms from recorded app.add_transform()/app.add_post_transform() calls, with a module subclass-scan fallback for unregistered transforms - Add _components.py: the shared markup -> parse -> badges -> facts pipeline all six component types render through, emitting docutils-domain object descriptions - Badge group: filled "transform" kind badge plus outlined "priority N" badge from default_priority (SAB.TYPE_TRANSFORM, SAB.MOD_PRIORITY with light/dark palette tokens) - Register the ("docutils", "transform") layout profile so entries get the shared card treatment - Domains now construct XRefRole(warn_dangling=True) so dangling component refs warn like std:confval refs do - Demo transform + examples page sections (single, bulk, cross-reference) and a two-page xref-resolution integration scenario asserting resolve-clean, dangling-warns, and href output
why: Issue #52 — readers have no Sphinx registration call and are instantiated directly inside publishers (django-docutils), so they were previously undocumentable except via automodule. what: - Add autoreader/autoreaders directives discovering Reader subclasses by module scan; facts surface supported formats, config section, and the get_transforms() set (guarded so framework-dependent readers degrade to a dash instead of breaking the build) - Add safe_transform_names() to the shared component helpers for reader/writer transform-set facts - SAB.TYPE_READER indigo palette + ("docutils", "reader") layout profile; demo reader extending standalone.Reader with the demo transform; examples page sections and reader xref demo
why: Issue #52 — parsers carry an alias tuple and may be registered as Sphinx source parsers; neither fact was documentable before. what: - Add autoparser/autoparsers directives combining a module subclass scan with recorded app.add_source_parser() calls, so scanned parsers carry their Sphinx registration state and parsers registered from elsewhere still surface - Facts: supported aliases, config section, and the add_source_parser registration when present - SAB.TYPE_PARSER sky palette + ("docutils", "parser") layout profile; demo line parser registered via setup(); examples page sections; unit + integration coverage
why: Issue #52 — writers carry output formats and a translator class, but the translator is often assigned in __init__ rather than as a class attribute (django-docutils), making it invisible to naive introspection. what: - Add autowriter/autowriters directives discovering Writer subclasses by module scan; facts surface supported formats, the resolved translator class, config section, and the get_transforms() set - Add resolve_translator_class(): instantiate the writer to read the instance attribute, fall back to the class attribute when construction needs framework state - SAB.TYPE_WRITER teal palette + ("docutils", "writer") layout profile; demo plain-text writer assigning its translator in __init__ to exercise the defensive path; examples page sections
why: Issue #52 — custom node classes carry the richest hidden metadata: element category mixins and per-builder visit/depart handlers registered through app.add_node(). django-docutils' icon node is handled purely by a custom translator with no add_node call at all, so discovery must work without registration too. what: - Add autonode/autonodes directives combining a module Element subclass scan with recorded app.add_node() calls; handler kwargs map builder names to (visit, depart) tuples, the override keyword is skipped, and repeated registrations follow override semantics - Facts: base classes, docutils element categories (Root through Inline, via issubclass against the category mixins), and the builders handlers were registered for - SAB.TYPE_NODE fuchsia palette + ("docutils", "node") layout profile; demo_marker inline node with html handlers; examples page sections; recorder-shape NamedTuple test matrix
why: Issue #52 — the interesting fact about a translator subclass is which visit/depart handlers it overrides (django-docutils' DjangoDocutilsHTMLTranslator overrides six), and naive dir()-based introspection drowns that in hundreds of inherited handlers. what: - Add autotranslator/autotranslators directives combining a NodeVisitor subclass scan with recorded app.set_translator() calls (builder name + override flag) - Overrides fact uses vars(cls) so only the class's own visit_/ depart_ methods surface; SparseNodeVisitor-style generated handlers on intermediate bases stay out of subclass listings - Badge group: filled "translator" kind badge plus the existing STATE_OVERRIDE outline badge when registered with override=True - SAB.TYPE_TRANSLATOR blue palette + ("docutils", "translator") layout profile; demo setup() registers the demo text translator with override; examples page sections
why: Issue #52 — builders are pure Sphinx extension objects, so their autodoc lives beside config values in this package rather than in the docutils sibling. Their name/format/image-type surface was previously only documentable as a plain py:class. what: - Add autobuilder/autobuilders directives discovering builders from recorded app.add_builder() calls with a Builder subclass-scan fallback; facts surface builder name, output format, supported image types, default translator (getattr — not on the base class), parallel-safety, and epilog - Add the package's _components.py rendering pipeline targeting the sphinxext domain, with a cached replay_setup mirroring the docutils side; kept self-contained so the package installs standalone - Badge group: filled amber "builder" kind badge plus an outlined output-format badge (SAB.TYPE_BUILDER, SAB.MOD_FORMAT) - ("sphinxext", "builder") layout profile; demo archive builder; examples page sections with {sphinxext:builder} xref demo; unit + shared-scenario + two-page xref-resolution integration coverage
why: Issue #52 — domains are the final extension-point type. A domain's interesting surface (object types, roles, directives, indices, role prefix) lives in class-level dicts that plain autodoc renders as opaque attributes. what: - Add autodomain/autodomains directives discovering domains from recorded app.add_domain() calls with a Domain subclass-scan fallback; facts surface the registered name, label (str() unwraps the lazy gettext proxy), object types, roles, directives, and the domain's own indices - Badge group: filled lime "domain" kind badge plus an outlined domain-name badge (SAB.TYPE_DOMAIN, SAB.MOD_DOMAIN_NAME) - ("sphinxext", "domain") layout profile; DemoTopicDomain demo; examples page documents the docutils domain that sphinx-autodoc-docutils itself registers — the bulk form replaying a sibling package's setup()
…todoc
why: The new component directives and cross-reference roles need a
documented home — domain roles never appear in autodirectives output,
so without a reference section they would be invisible API.
what:
- Add docs/packages/sphinx-autodoc-docutils/reference.md: the package
documents its own twelve directives via autodirectives, plus a
role table for the six {docutils:*} roles and the component index
- Extend the sphinx-autodoc-sphinx reference page with the
{sphinxext:*} role table and index link
- Tutorial pages gain the component single/bulk pattern; how-to pages
gain a cross-referencing recipe noting that :no-index: entries
create no link target
- Package READMEs describe the broadened component surface
why: Issue #52 ships three user-visible deliverables in the forthcoming release: the six docutils component autodoc pairs, the builder/domain pairs, and the cross-reference domains. what: - Add three What's new deliverables to the unreleased entry with {ref} links to the live demo and reference pages
c816bd6 to
5d25e49
Compare
why: All functions need working doctests; this one predates the branch but its two new siblings in the same file are doctested, so the gap stood out in review (PR #53). what: - Add Examples covering a mapped kind, a component kind, and the unknown-kind fallback to the directive colour class
why: Review on PR #53 caught four badge families whose dark tokens landed in only one of the two dark-mode blocks, and one outlined modifier with no dark overrides at all. The stylesheet documents "Identical values" between its dark blocks but nothing enforced it, so the drift was invisible until someone diffed the blocks by hand. what: - Add tests/ext/badges/test_palettes.py parsing sab_palettes.css as text and asserting three contracts: the @media and body[data-theme="dark"] blocks declare identical token/value pairs; every var() a colour class reads resolves to a :root declaration; every light-mode token family has dark-mode coverage (family-level, so state-deprecated's transparent light-only -bg stays legal)
|
All three review issues are addressed:
Follow-ups on top:
🤖 Generated with Claude Code |
why: Review on PR #53 caught four badge families whose dark tokens landed in only one of the two dark-mode blocks, and one outlined modifier with no dark overrides at all. The stylesheet documents "Identical values" between its dark blocks but nothing enforced it, so the drift was invisible until someone diffed the blocks by hand. what: - Add tests/ext/badges/test_palettes.py parsing sab_palettes.css as text and asserting three contracts: the @media and body[data-theme="dark"] blocks declare identical token/value pairs; every var() a colour class reads resolves to a :root declaration; every light-mode token family has dark-mode coverage (family-level, so state-deprecated's transparent light-only -bg stays legal)
why: All functions need working doctests; this one predates the branch but its two new siblings in the same file are doctested, so the gap stood out in the follow-up review of PR #53. what: - Add Examples covering the config kind badge, the rebuild-mode badge, and the empty-rebuild fallback to "none"
d52a0ba to
58ea3e0
Compare
why: Fact rows render list values as one comma-joined literal blob, and names that have documented py-domain targets (classes, methods) render as dead text. Both autodoc packages need the same fix, so the helpers live in the shared layout package. what: - Add build_linked_literal(): a literal chip wrapped in a py-domain "obj" pending_xref with refwarn off — resolves when a target exists (ReferencesResolver defaults refdoc, fully-qualified targets match in exact-match mode) and silently stays a literal when it does not, so externals without an intersphinx inventory degrade cleanly - Add build_chip_paragraph(): one literal chip per value joined by ", " text nodes, em dash for empty value lists
why: Every fact value rendered as one comma-joined literal blob — formats, transform sets, and translator overrides wrapped as a single chip, and names with real py-domain targets (a consumer's automodule pages document the same classes and methods) were dead text. what: - Render list-valued facts (supported formats/aliases, categories, handler builders) as individual literal chips - Cross-link name-valued facts via the shared linked-literal helper: Python path on all six types, writer translator class, translator base class and per-method overrides (fully-qualified method targets), node base classes, and reader/writer transform sets (bare-name chips targeting qualified paths) - Replace safe_transform_names() with safe_transform_classes() so chips can carry qualified targets; add transform_chip_nodes() and linked_paragraph() to the shared component helpers - Integration: the xref scenario now automodules the demo module and asserts the Python-path fact resolves to the autodoc anchor; unresolvable chips stay literals with no warnings (covered by the existing resolve-clean assertion)
why: Same fact-presentation defects as the docutils package — image types and domain surfaces (object types, roles, directives, indices) clumped into single literals, and Python paths plus default translators dead text despite frequently having automodule targets. what: - Python path and Default translator render as linked literals via the shared helpers; the translator chip targets the qualified class path - Supported image types and the domain surface facts render as individual chips - Add linked_paragraph() to the package's component helpers and structure assertions for the linked/chipped facts
why: The unreleased component-autodoc deliverable now also covers fact presentation: per-value chips and py-domain cross-links. what: - Extend the deliverable prose with the chip rendering and graceful-degradation link behavior
why: The linked-facts pass covered the component modules but missed the original autodirective/autorole fact builders, so role callables and directive classes with real py-domain targets (consumers automodule the same objects) still rendered dead text. what: - Wrap the directive and role Python path facts in the shared linked-literal helper; py:function role targets and py:class directive targets now resolve, degrading silently elsewhere
why: The Registered by fact names the extension's setup() callable as dead text; sites that document extension surfaces in the py domain (gp-sphinx's own docs do) have a real target for it. what: - Wrap the Registered by value in the shared linked-literal helper, targeting module.setup with the call-syntax display; degrades to the existing literal where setup() is undocumented
why: The Type fact rendered names like list, dict, and bool as dead literals even though Python builtins carry py:class targets in the python intersphinx inventory every consumer site already maps. what: - Route the Type fact through the shared annotation display pipeline (build_annotation_display_paragraph) with the directive's environment, so type names become pending_xrefs that resolve locally or via intersphinx and degrade silently otherwise - Keep bare None and empty type text as plain literals: the display policy would collapse a lone None to the enum marker, and the workspace policy never links None - _config_fact_rows grows an optional env parameter; without it the Type fact renders exactly as before
why: Confval "Registered by" facts cross-reference <pkg>.setup, but only sphinx-ux-badges documented its setup function — every other package's link degraded to dead text on our own site. what: - Add an "Extension entry point" autofunction section to the ten reference pages missing one, matching the sphinx-ux-badges precedent - Add a minimal reference page for sphinx-autodoc-pytest-fixtures (directives + entry point; its confvals stay on the how-to page to avoid duplicate targets)
why: The linked-facts sentence in the unreleased deliverable now also covers config value types and their registering entry points. what: - Extend the deliverable's linked-targets list
why: Small dict and list defaults rendered as inline literal blobs; structured values read better as highlighted Python, and the Pygments literal_block path already existed for long reprs. what: - _is_complex_default treats any non-empty container as complex, so dict/list/tuple/set defaults render through the existing Pygments-highlighted block regardless of repr length; empty containers and scalars stay inline
why: The package's public API had no rendered documentation, so its
how-to tables named build_resolved_annotation_paragraph and friends
as dead text with nothing to link to.
what:
- Add a reference page autodocumenting the public surface: the four
build_* helpers, render_annotation_nodes, the normalize_* text
helpers, classify_annotation_display, AnnotationDisplay, and the
setup entry point
- Link the how-to tables and prose with {func}/{class} roles;
typing.get_type_hints links to the python inventory, and
sphinx_stringify_annotation mentions use the canonical
sphinx.util.typing.stringify_annotation name (Sphinx publishes no
target for it)
why: Demo modules under docs/_ext were never autodocumented, so every demo Python-path fact (docutils_demo.DemoBadgeDirective, sphinx_config_demo.setup(), ...) degraded to dead text with nothing to link to. what: - Append a "Demo module reference" automodule section to the docutils, sphinx, and fastmcp examples pages, giving the demo objects py-domain anchors the entries' facts now resolve to - pytest-fixtures excluded: its fixture entries already create py-domain descriptions on the same page, so an automodule block emits duplicate-object warnings
why: pytest's default norecursedirs covers "build" but not Sphinx's "_build", so generated markdown under docs/_build collects as doctest files and breaks the run whenever a build output exists — including mid-session, since the objects-inv compatibility test's live docs build leaks output there. what: - Set norecursedirs explicitly, adding _build and node_modules to the conventional exclusions
5ec6048 to
5ee8fee
Compare
…lidators why: A full sweep of every package's autodoc output found two more name-valued facts rendered as dead text: the transform/parser "Registered via app.add_*()" calls and directive option "Validator" converters, all of which name real callables. what: - Registered via facts link to sphinx.application.Sphinx.add_* (the sphinx inventory resolves them where mapped; degrades to the literal call elsewhere) - Directive option validators link to their converter callable — builtins target the python inventory, docutils converters their qualified path — degrading when no inventory is mapped - Rebuild _option_field_list directly from the option_spec (the string round-trip through _option_rows could not carry converter identity)
why: This round's only new user-facing capability beyond the already-documented linked facts is highlighting structured defaults. what: - Note container config-value defaults rendering as Pygments blocks in the unreleased deliverable
why: The role-option validator already linked its converter, but the directive-option "Validator:" line — rendered through the generated rst:directive:option markup, a different path — stayed dead text, an inconsistency next to the now-linked role equivalent. what: - Emit the validator as a :py:obj: role with an explicit converter target in the generated markup: builtin converters link to the python inventory, docutils converters to their qualified path, and both render as plain text where no inventory is mapped (the build is not nitpicky) - Build the option list directly from option_spec and drop the now unused _option_rows string-table helper
why: The What's new entries carried docstring-grade detail — per-type fact enumerations, setup-replay/add_* discovery mechanics, and rendering internals — that buried the upgrade-time takeaway. what: - Lead with a one-paragraph summary of the broadened component autodoc - Trim the three deliverables to their user-visible surface: the new directives, what they document, and the cross-linking; mechanism detail moves to the autodoc output and the PR
why: The tightened release notes rendered the {docutils:transform}
example as a live cross-reference; SanitizeTransform is a
django-docutils class with no target on gp-sphinx's own site, so the
warn_dangling role failed the -W docs build in CI.
what:
- Wrap the role example in an inline code span so it shows the role
syntax without resolving it
Summary
sphinx-autodoc-docutils—autotransform(s),autoreader(s),autoparser(s),autowriter(s),autonode(s),autotranslator(s)— extending the single + bulk-by-module pattern thatautodirective/autoroleestablishedautobuilder(s)andautodomain(s)tosphinx-autodoc-sphinx, documentingsphinx.builders.Builderandsphinx.domains.Domainsubclasses beside the config values it already coversdocutils(transform/reader/parser/writer/node/translator) andsphinxext(builder/domain) — so every documented component is linkable from prose via roles like{docutils:transform}`SanitizeTransform`, each with a grouped component index and dangling-ref warningssetup()entry points, option validators,Registered viaapp methods) render as cross-references that resolve locally or via intersphinx and degrade to plain chips otherwise; list-valued facts render as individual chips; container config defaults render as Pygments-highlighted blocksautomoduletoday; registry-aware autodoc gives them the same polished, cross-linked output directives and roles already get (sphinx-autodoc-docutils: autodoc for Transforms, Readers, Parsers, Writers, Nodes, and Sphinx builders #52)Companion PR
[tool.uv.sources]Changes by area
sphinx-autodoc-docutilsdomain.py:DocutilsDomain(six objtypes,XRefRole(warn_dangling=True), grouped index) + a genericDocutilsComponentDescriptionthat owns anchors and domain notes_components.py: the shared markup → parse → badges → facts pipeline every type renders through;safe_transform_classes,transform_chip_nodes,linked_paragraph_transforms_doc.py…_translators_doc.py: one module per type — discovery (recorder-replay with module-scan fallback), an*Infodataclass where registration metadata exists, fact rows, and theAuto*/Auto*sdirective pair_directives.py: directive/role Python paths and directive-option validators now linksphinx-autodoc-sphinxdomain.py:SphinxExtDomainmirroring the lifecycle for builder/domain objtypes_components.py+_builders_doc.py/_domains_doc.py: discovery, facts, directive pairs_directives.py: confvalTyperoutes through the shared annotation pipeline (builtins link to the python inventory);Registered bylinks to the extensionsetup(); container defaults render as Pygments blocksShared UX packages
sphinx-ux-autodoc-layout:build_linked_literal/build_chip_paragraphinline helpers (Tier-1 shared);(docutils, *)and(sphinxext, *)layout profilessphinx-ux-badges: per-typeSAB.TYPE_*constants + outline modifiers, with light/dark palette tokens and a parity guard testDocs
sphinx-autodoc-docutils,sphinx-autodoc-typehints-gp, andsphinx-autodoc-pytest-fixtures; an "Extension entry point"autofunctionsection on every package reference page (soRegistered byfacts resolve); demo-moduleautomodulesections so demo Python-path facts linkDesign decisions
ObjectDescriptiondirectives, not py-domain retagging: generated markup targets the new domains, so desc nodes natively carry the right domain/objtype — anchors, TOC entries, xref targets, and layout profiles all come from Sphinx's own machinerypending_xrefwithrefwarn=False, so the same fact row is a live link where the inventory is mapped and a plain chip where it isn't — no per-site conditionals, no warning tax. docutils publishes no intersphinx inventory, so docutils-internal names degrade by designsphinxextdomain name: explicit and collision-free; avoids shadowing thesphinx.*namespacesphinx-autodoc-sphinxmirrors the small rendering pipeline rather than depending on its docutils siblingTest plan
rm -rf docs/_build; uv run ruff check . --fix --show-fixes; uv run ruff format .; uv run mypy .; uv run py.test --reruns 0 -vvv; just build-docstest_domain.py(both packages) — note/clear/merge/get_objects/lookup lifecycle and grouped indextest_domain_xref_integration.py(both packages) — resolvable refs build warning-free, dangling refs warn, resolvedhrefs present; one scenario automodules the demo module and asserts a fact's Python path resolves to the autodoc anchordocs/_buildRegistered by, confval types) all renderCloses #52