Plan 09 — Automated Doc Generation
Automate the generation of reference documentation from Python source code using
Google-style docstrings, type hints/annotations, and module-level comments via
mkdocstrings with
mkdocstrings-python.
Decision¶
Use mkdocstrings (not annotated-doc). Reason: mkdocstrings is
compatible with Zensical (the static site generator by the authors of MkDocs
Material, which we use instead of MkDocs), and supports Google-style docstrings
out of the box via the Python handler. Zensical provides preliminary
mkdocstrings support as of version 0.0.11 (docs).
Goal¶
Eliminate manual authoring of API/CLI reference docs by extracting them directly from the source. Generated docs should be REUSE-compliant and compatible with the existing SPDX changelog setup.
Prerequisite: enrich docstrings¶
The existing docstrings in the source code are minimal (e.g. one-line module docstrings, short class descriptions). Before mkdocstrings can render useful API reference pages, every public symbol must have a proper Google-style docstring:
| Symbol | File | What to add |
|---|---|---|
SPDXMarkdown class |
formatters/spdx_markdown.py |
Full class docstring (parameters, attributes) |
_strip_frontmatter |
formatters/spdx_markdown.py |
Args/returns for the function |
_SPDXMarkdownImpl class |
formatters/_impl.py |
Method docstrings for overrides |
| Module docstrings | All .py files |
Expand to Google-style (summary + description) |
mkdocstrings integration with Zensical¶
mkdocstrings renders API reference documentation from source
code docstrings. mkdocstrings-python is the Python
handler that supports Google-style docstrings.
Installation¶
Added to [dependency-groups] dev in pyproject.toml:
Configuration for Zensical¶
Add to zensical.toml:
[project.plugins.mkdocstrings.handlers.python]
inventories = ["https://docs.python.org/3/objects.inv"]
paths = ["src"]
[project.plugins.mkdocstrings.handlers.python.options]
docstring_style = "google"
heading_level = 3
inherited_members = true
show_root_full_path = false
show_root_heading = true
show_source = false
Key options:
docstring_style—"google"(we use Google style).heading_level— heading level for root API entries (3 =<h3>).inherited_members— include inherited methods in the docs.show_root_full_path— omit the full dotted path prefix on root headings.show_root_heading— render a visible heading for the documented symbol (defaults tofalse, which was the root cause of invisible headings on first attempt).show_source— whether to display the source code link.paths— directories to search for Python modules (relative to project root).inventories— intersphinx inventories for cross-referencing (e.g. Python stdlib).
Note: Source files outside the
docs/parent directory are not watched for live-reload by Zensical's file agent. Thesrc/directory works because it is at the project root alongsidedocs/. See the Zensical mkdocstrings page for details.
Example usage in a Markdown page¶
Create docs/api.md with a bare ::: block (no inline YAML options):
This renders the full API reference for SPDXMarkdown inline in the page. All
handler and option configuration is inherited from zensical.toml.
mdformat caveat: ::: blocks with inline YAML (e.g. handler: python,
options:) get mangled by mdformat because it treats them as regular paragraphs
and collapses multi-line indented options. Use bare identifiers only — any
per-block customisation must go through the global zensical.toml config.
Underscore escape: mdformat escapes lone underscores in ::: paths,
breaking references to private symbols (e.g. ::_impl becomes :\_impl). The
workaround is to not document private API entry points via ::: — instead,
document the public wrapper class whose docstring covers the implementation.
Implementation steps¶
- Enrich docstrings. Write Google-style docstrings for every public symbol (see prerequisite table above).
- Install
mkdocstrings-python. Add to dev dependency group. - Configure in
zensical.toml. Add the[project.plugins.mkdocstrings]section with Python handler settings (Google style,paths = ["src"],inherited_members = true,show_source = false). - Create
docs/api.md. Insert a bare:::block referencingcommitizen_spdx_changelog.formatters.spdx_markdown.SPDXMarkdown. - Add nav entry. Add
{ "API Reference" = "api.md" }to thenavlist inzensical.toml. - Discover
show_root_heading. Initially documentation appeared to produce no output — the root cause wasshow_root_headingdefaulting tofalse. Without it, mkdocstrings renders the docstring content without any visible heading, making the page appear empty. Enableshow_root_heading = trueandshow_root_full_path = false. - Fix mdformat compatibility. mdformat collapses multi-line options inside
:::blocks and escapes underscores. Switch to bare identifiers and remove inline YAML options — all configuration moves tozensical.toml. - Expand
SPDXMarkdowndocstring. Since_SPDXMarkdownImplis private and its:::path is broken by mdformat's underscore escaping, document the implementation architecture (lazy loading, overridden methods) in the publicSPDXMarkdowndocstring instead. - Integrate with CI. Add a
docsjob in.github/workflows/ci.yamlthat builds the Zensical site and fails if docs are out of date. - Verify end-to-end. Run
make docs-buildand confirm API reference pages render correctly and REUSE compliance is maintained.
Tasks¶
- Enrich docstrings. Write Google-style docstrings for
SPDXMarkdown,_SPDXMarkdownImpl,_strip_frontmatter, and all module docstrings (hello()was removed from__init__.pyinstead). - Install
mkdocstrings-python. Add to[dependency-groups] devinpyproject.toml. - Configure in
zensical.toml. Add Python handler with Google docstring style,paths = ["src"],inherited_members = true,show_source = false. - Create
docs/api.md. Insert bare:::block referencingcommitizen_spdx_changelog.formatters.spdx_markdown.SPDXMarkdown. - Add nav entry. Include
{ "API Reference" = "api.md" }in thenavlist. - Fix
show_root_heading. Enableshow_root_heading = trueandshow_root_full_path = falseto make root symbol headings visible. - Fix mdformat compatibility. Remove inline YAML from
:::blocks; move all configuration to globalzensical.toml. Note: mdformat escapes underscores in paths, so private symbols (_impl) cannot use:::. - Expand
SPDXMarkdowndocstring. Document lazy-loading architecture,_SPDXMarkdownImpl, and both overridden methods — covers private implementation details that cannot have their own:::block. - Integrate with CI. Add a
docsjob that builds with Zensical and runs markdown linting. - Verify end-to-end. Run
make docs-buildand confirm API reference pages render correctly.
Dependencies¶
- Docstring enrichment — standalone, no external dependencies.
mkdocstrings-pythonavailable on PyPI.zensicalinstalled (already in dev dependencies).- Python 3.13+.
Acceptance¶
- Google-style docstrings written for all public symbols.
-
mkdocstrings-pythoninstalled and configured inzensical.toml. - API reference pages render Google-style docstrings correctly.
- docs build succeeds with
make docs-build— no mkdocstrings errors. - Generated pages maintain SPDX frontmatter (host pages already have headers; mkdocstrings output inherits them).
- CI docs job passes (build succeeds).