Skip to content

Fix the configuration-reference generator - #766

Open
wbarnha wants to merge 1 commit into
masterfrom
claude/faust-configref-drift
Open

Fix the configuration-reference generator#766
wbarnha wants to merge 1 commit into
masterfrom
claude/faust-configref-drift

Conversation

@wbarnha

@wbarnha wbarnha commented Aug 9, 2026

Copy link
Copy Markdown
Member

Description

extra/tools/render_configuration_reference.py generates docs/includes/settingref.txt, which docs/userguide/settings.rst includes. Nothing validates its output, so a bug there silently corrupts the published configuration reference instead of failing something. Three did — and together they made the output depend on which interpreter ran make configref, which is why regenerating produced a tree-wide diff rather than a clean one.

Docstring dedent, broken by Python 3.13

normalize_indent dedented by scanning for the first indented line after the summary and stripping that much from every line. On 3.12 and older that was the body indent, so it worked.

Python 3.13 strips the common leading whitespace from docstrings at compile time. From then on the body arrives flush left, and the first indented line the scan finds is the body of a .. warning:: or .. note:: — whose indent it then removed, so the content escaped the directive and rendered as loose paragraphs instead of an admonition:

 .. warning::
 
-   The autodiscovery functionality uses the :pypi:`Venusian` library
+The autodiscovery functionality uses the :pypi:`Venusian` library

Replaced with inspect.cleandoc, which dedents by the minimum indent: a no-op on an already-dedented docstring, and byte-identical output on every supported interpreter. That also fixes an off-by-one in strip_space (i > n where i >= n was meant), which is why directive bodies in the committed reference sit at three spaces rather than four.

pathlib._local on 3.13+

Types were referenced through __module__. 3.13 moved pathlib.Path into pathlib._local, so the reference became :class:~pathlib._local.Path`` — which resolves nowhere, and changed depending on the interpreter. public_module() now walks up private components and takes the shallowest package that still exposes the same object.

CLI options rendered one character at a time

Three settings declared related_cli_options={"faust": "--datadir"} — a bare string where Mapping[str, List[str]] is declared. The renderer iterates the value, so --datadir became nine separate options:

:option:`faust -`, :option:`faust -`, :option:`faust d`, :option:`faust a`, ...

Section.setting() takes **kwargs: Any, so the declared type is erased and mypy cannot catch it — hence a test rather than a type fix alone.

Tests

tests/unit/test_configref_renderer.py covers the normalization against both docstring forms, the directive-body indent, the module reference, a smoke render of every real setting, and asserts no setting declares its CLI options as a bare string.

Verified in both directions: 6 of the 9 fail against the original generator on 3.12 and 3.13; all 9 pass after. The generator now produces byte-identical output on 3.12, 3.13 and free-threaded 3.14.

Full suite: 2216 passed. mypy, verify_doc_defaults, flake8 / black / isort all clean.

Not done here

docs/includes/settingref.txt is deliberately left untouched, because regenerating it needs two unrelated decisions first:

  • It contains a hand-written "OAuth2 Authentication" block that exists nowhere in the source docstrings, so regenerating would delete real documentation. It belongs in broker_credentials.__doc__.
  • broker_client_id defaults to f"faust-{faust_version}", so the rendered default embeds whichever version generated it — the committed file says faust-0.8.9, a dev tree renders faust-0.1.dev193+.... Until that is symbolic the file cannot be regenerated reproducibly, and no byte-exact CI drift check can work.

Worth knowing meanwhile: python extra/tools/... puts the script's own directory on sys.path, not the repository root, so the generator renders whichever faust is installed. With a non-editable install it silently renders a stale copy.

Note

Touches docs/includes/settingref.txt's generator but not the file, so it should not conflict with #762 — which adds a setting entry to that file by hand for the same reason described above.


Generated by Claude Code

`extra/tools/render_configuration_reference.py` generates
docs/includes/settingref.txt, which docs/userguide/settings.rst
includes.  Nothing validates its output, so a bug there silently
corrupts the published configuration reference instead of failing
something.  Three did, and together they made the output depend on which
interpreter ran `make configref` -- which is why regenerating produced a
tree-wide diff rather than a clean one.

## Docstring dedent, broken by Python 3.13

`normalize_indent` dedented by scanning for the first *indented* line
after the summary and stripping that much from every line.  On 3.12 and
older that was the body indent, so it worked.

Python 3.13 strips the common leading whitespace from docstrings at
compile time.  From then on the body arrives flush left, and the first
indented line the scan finds is the body of a `.. warning::` or
`.. note::` -- whose indent it then removed, so the content escaped the
directive and rendered as loose paragraphs instead of an admonition.

Replaced with `inspect.cleandoc`, which dedents by the *minimum* indent:
a no-op on an already-dedented docstring, and byte-identical output on
every supported interpreter.  That also fixes an off-by-one in
`strip_space` (`i > n` where `i >= n` was meant), which is why directive
bodies in the committed reference sit at three spaces rather than four.

## `pathlib._local` on 3.13+

Types were referenced through `__module__`.  3.13 moved `pathlib.Path`
into `pathlib._local`, so the reference became
`:class:`~pathlib._local.Path``, which resolves nowhere -- and changed
depending on the interpreter.  `public_module()` now walks up private
components and takes the shallowest package that still exposes the same
object.

## CLI options rendered one character at a time

Three settings declared `related_cli_options={"faust": "--datadir"}`,
a bare string where `Mapping[str, List[str]]` is declared.  The renderer
iterates the value, so `--datadir` became nine separate options:

    :option:`faust -`, :option:`faust -`, :option:`faust d`, ...

`Section.setting()` takes `**kwargs: Any`, so the declared type is
erased and mypy cannot catch it -- hence the test below rather than a
type fix alone.

## Tests

tests/unit/test_configref_renderer.py covers the normalization against
both docstring forms, the directive-body indent, the module reference,
and a smoke render of every real setting.  It also asserts no setting
declares its CLI options as a bare string.

Verified in both directions: 6 of the 9 fail against the original
generator on 3.12 and on 3.13; all 9 pass after.  The generator now
produces byte-identical output on 3.12, 3.13 and free-threaded 3.14.

## Not done here

The committed settingref.txt is deliberately left untouched, because
regenerating it needs two unrelated decisions first:

  * it contains a hand-written "OAuth2 Authentication" block that exists
    nowhere in the source docstrings, so regenerating would delete real
    documentation.  It belongs in `broker_credentials.__doc__`.
  * `broker_client_id` defaults to `f"faust-{faust_version}"`, so the
    rendered default embeds whichever version generated it -- the
    committed file says `faust-0.8.9`, a dev tree renders
    `faust-0.1.dev193+...`.  Until that is symbolic the file cannot be
    regenerated reproducibly, and no byte-exact CI drift check can work.

Worth knowing meanwhile: `python extra/tools/...` puts the script's own
directory on sys.path, not the repository root, so the generator renders
whichever faust is *installed*.  With a non-editable install it silently
renders a stale copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8qT5E3rnSXvw7ibNLXrVr
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.05%. Comparing base (4af976b) to head (70b6e44).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #766      +/-   ##
==========================================
- Coverage   96.06%   96.05%   -0.01%     
==========================================
  Files         103      103              
  Lines       11072    11081       +9     
  Branches     1191     1189       -2     
==========================================
+ Hits        10636    10644       +8     
  Misses        345      345              
- Partials       91       92       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants