Skip to content

✨ sphinx-codelinks: read ubproject.toml through ub-project, and let -D override the TOML - #2005

Merged
chrisjsewell merged 9 commits into
masterfrom
feat/sncl-ub-project
Sep 30, 2026
Merged

chrisjsewell merged 9 commits into
masterfrom
feat/sncl-ub-project

Conversation

@chrisjsewell

Copy link
Copy Markdown
Member

What

sphinx-codelinks now reads ubproject.toml through ub-project, the shared reader of the sphinx-needs family, and gains it as a runtime
dependency (ub-project>=1.1.0,<2). One loader, config.load_codelinks_table(path) — ub-project's load_toml + select_table(…, "codelinks") — serves both readers, the Sphinx extension's config-inited hook and codelinks analyse; src/ no longer imports
tomllib. The loader returns raw values: relative paths are still anchored where they are used, now through ub-project's anchor
(identical to the / join it replaces, by construction), with every .resolve() kept.

Two behaviour fixes ride on it: a -D value is no longer overwritten by the TOML, and a default ubproject.toml that exists but is broken
warns again, as any configured file that could not be read did at 1.4.0.

Behaviour (enumerated)

  1. -D src_trace_<key> beats the TOML. For set_local_url, set_remote_url, local_url_field, remote_url_field,
    debug_measurement, debug_filters and config_from_toml, "the TOML sets it and -D is given" now yields the -D value. The order is
    -D > TOML > conf.py > default, as in sphinx-needs. Only the full confval name counts — a bare -D set_local_url=0, which Sphinx rejects
    as unknown, leaves the TOML value alone. projects and outdir are never skipped (NOT_OVERRIDABLE_FROM_D): Sphinx refuses a -D for
    both (a dict; a Path-typed default) yet keeps the key in config.overrides, so skipping would honour an override that was never
    applied — a TOML projects or outdir stands, as before. A -D value Sphinx cannot convert (-D src_trace_set_local_url=yes, Sphinx ≥
    8.2) is now reported by Sphinx (rc 2) instead of being silently replaced by the TOML — as it already was without a TOML.
  2. A broken ubproject.toml warns (invalid TOML, not UTF-8, a directory, codelinks not a table, a pathologically nested file):
    under -W these builds fail, where master's (unreleased) behaviour built silently without the [codelinks] table.
  3. The reader's warnings are typed codelinks.config — a [codelinks.config] suffix on Sphinx ≥ 8 — so
    suppress_warnings = ["codelinks.config"] silences them.
  4. Messages: a broken file's warning names the file once and says what is wrong (<p>: invalid TOML: …, not valid UTF-8 TOML,
    cannot be read, [codelinks] must be a table, got str; a nested file: … from <p>: maximum recursion depth exceeded); an explicit file
    without the table says … has no [codelinks] table. Using configuration from conf.py. instead of 'codelinks'; codelinks analyse
    shows why a file could not be loaded instead of only that it could not, and reports codelinks = 0/[]/false as "must be a table" rather
    than "No 'codelinks' section". Exit codes are unchanged in every cell except the ones in items 1 and 2: every CLI exit code is unchanged,
    including rc 2 for a pathologically nested file.
  5. ub-project>=1.1.0,<2 is a runtime dependency — declared directly, because PyPI's sphinx-needs 8.5.0 does not bring it.

No existing test or snapshot expectation moved.

The default file (what 1.4.0 did, what master does, what this ships)

  • 1.4.0: no default; every failure of a configured file warned — including a file configured as
    src_trace_config_from_toml = "ubproject.toml", missing, without [codelinks], or broken.
  • master (unreleased, Need layout system #102): the default became ubproject.toml, recognised by NAME (a string comparison), so any failure of a file
    of that name was silent — whether the name was left at its default or written in conf.py.
  • this PR: the default is the value ubproject.toml exactly — left unset, or written in conf.py as that string (the reader compares
    strings, not files). That file may be absent or have no [codelinks] table — both silent (the intent of Need layout system #102, kept: other tools share
    the file). Any other value, ./ubproject.toml or an absolute path to the same file included, is an explicit file: missing or without the
    table, it warns. A file that exists but cannot be read or parsed warns (codelinks.config) either way, as 1.4.0 warned for a corrupt
    configured file. The trade, stated: a 1.4.0 project that wrote "ubproject.toml" out keeps the corrupt-file warning but no
    longer gets one for a missing file or a missing table. The Need layout system #102 changelog bullet is amended in place to say so.

Not in this PR

  • A conf.py-only src_trace_projects (no TOML at all) crashes every build that uses src-trace with KeyError: 'source_discover_config'
    — pre-existing on master and unrelated (the conversion only runs on the TOML path); to be filed separately.
  • Unknown keys inside [codelinks]: the extension skips them silently, the CLI refuses them — the two readers disagree today, and a
    follow-up decides both together.
  • config_from_toml remains a key the TOML itself may set (it moves the anchor without reading the named file); pinned by a test so the
    follow-up that retires it changes a fenced thing.
  • check_sphinx_configuration's bare raise Exception for a schema error (and the "filed" typo in its message) — validation, not reading.

Docs

components/configuration.rst: the -D precedence rule (with src_trace_projects and src_trace_outdir as the two keys -D cannot
set), the default-name rule, and the codelinks.config type. changelog.rst Unreleased: the default-file bullet amended, plus a bullet
each for the -D fix and the ub-project reader. The package AGENTS.md: the loader's home, the -D exception, the stale click/typer caps
section, 17 test modules. The root AGENTS.md, root CLAUDE.md and compat-requirements.txt describe the libclang-gated tests without
counts that go stale.

Tests

39 new cases (31 Sphinx-level through make_app, 8 CLI through CliRunner); 28 of them fail against master. The other 11 pass there
and are pins: a bare-name -D; a refused -D src_trace_projects; a refused -D src_trace_outdir; the conf.py-vs-TOML order; the CLI's
empty table; the CLI's missing table; the CLI's nested file; a symlinked TOML anchoring at the link's directory; a TOML-set
config_from_toml; and the default name written out, missing and without the table (2). Each pin is proven by a mutation that turns it
red. One test asserts that the keys exempt from the -D skip
are exactly the codelinks confvals Sphinx refuses from -D, detected per confval from Sphinx's own warning, on 7.4 / 8.2 / 9.1.
402 passed with libclang, 0 skipped (the sphinx-7 and sphinx-8 cells were run at 400, one commit earlier; the last commit adds two test
cases and changes no reader logic).

Toolchain

The lock is master's plus the two lines of the sphinx-codelinks block (not a relock); uv lock --check passes on uv 0.12.15 and 0.12.9.
poe check-workspace, poe import-check-codelinks (24 modules, ub-project==1.1.0 and sphinx-needs==8.5.0 from PyPI) and
poe docs-codelinks (-nW) pass. The version (1.4.0) is unchanged; no release.

Red against d0edd0e (22): a `-D src_trace_<key>` value beats the TOML for the
seven scalar keys and `config_from_toml`, and in the -D / TOML / conf.py
three-way; a corrupt default ubproject.toml (syntax, non-UTF-8, a directory,
`codelinks` not a table) warns `codelinks.config`; an explicit file without
`[codelinks]` says so; the reader's warnings are suppressible with
`codelinks.config`; the CLI shows the parser's reason and ub-project's
"[codelinks] must be a table" words.

Pins, green at d0edd0e and proven by mutation later (8): a bare-name `-D`
and a refused `-D src_trace_projects=x` leave the TOML alone; conf.py vs TOML
is unchanged; an empty or missing `[codelinks]` keeps the CLI's words; a
5000-deep TOML is still rc 2; a symlinked TOML anchors at the link's
directory; a TOML-set `config_from_toml` moves the anchor.
A runtime dependency, declared directly: PyPI's sphinx-needs 8.5.0 does not
bring ub-project, so the workspace's own sphinx-needs making it importable
would hide a missing declaration from every local test.

The lock is master's plus the two lines of the sphinx-codelinks block -- not a
relock, which would rewrite 224 unrelated marker lines. `uv lock --check`
passes on uv 0.12.15 and on the hook's 0.12.9.
…D override the TOML

One loader, `config.load_codelinks_table(path)` = ub-project's `load_toml` +
`select_table(..., "codelinks", source=path)`, returning the table's raw
values; both readers call it and `src/` no longer imports tomllib.

The Sphinx reader keeps its discovery as it was. A file that exists but cannot
be read or parsed now warns for the default ubproject.toml too, as every
configured file did at 1.4.0 (the silence was unreleased); a missing file or a
missing `[codelinks]` table stays silent for the default file. Its three
warnings are typed `codelinks.config`, the load failure no longer repeats the
path ub-project's message already names, and an explicit file without the
table says so instead of printing a KeyError repr.

`set_config_to_sphinx` skips a TOML key given as `-D src_trace_<key>`: only the
full confval name (a bare `-D set_local_url=0` is refused by Sphinx and must
not suppress the TOML), and never `projects` (Sphinx refuses a whole-dict
override but keeps it in `config.overrides`). `-D src_trace_outdir` is refused
by Sphinx in the same way, and the TOML's `outdir` is skipped with it: inert,
the extension never reads that value -- pinned by its own test, which replaces
the `outdir` row of the "-D wins" parametrization.

The CLI re-raises ub-project's error in its own words (a BadParameter, now
showing the reason), keeps a catch-all for the RecursionError tomllib can
raise, and keeps its falsy "No 'codelinks' section" test.

The path joins go through ub-project's `anchor`, at the use sites as before
and with every `.resolve()` kept: identical to `/` by construction.
…ng and the ub-project reader

configuration.rst: `-D` overrides both conf.py and the TOML, `src_trace_projects`
only comes from conf.py or the TOML (no dotted `-D`), Sphinx refuses
`-D src_trace_outdir`; a file that exists but cannot be read or parsed warns,
the default one included, as `codelinks.config`.

changelog `Unreleased`: the default-ubproject.toml bullet is amended in place
(it is unreleased text), plus a bullet for the `-D` rule and one for the
ub-project reader and its messages.

AGENTS.md: config.py holds the loader, `load_codelinks_table`; integration
point 2 names the `-D` exception; the stale click/typer caps section and the
test counts (393; 337 + 26 skipped without libclang) are corrected.
…the outdir exemption

Red at b1ac8d4 (4): a 5000-deep TOML, default or explicit, warns naming the
file (the Sphinx side's non-ProjectConfigError warning dropped the path);
`-D src_trace_outdir` leaves the TOML's `outdir` standing (the pin, inverted);
the keys exempt from the -D skip are exactly the codelinks confvals Sphinx
refuses from -D, detected per confval by Sphinx's own "cannot override"
warning.

Pins, green at b1ac8d4: `-D` on one key leaves the other TOML keys and the
projects; an explicit syntax error names the file once; the default name
written in conf.py is still the default (missing file, missing table:
silent); the CLI's ProjectConfigError messages are not the catch-all's.
…er crash names the file

`outdir` joins `projects` in one named tuple, `NOT_OVERRIDABLE_FROM_D`: Sphinx
refuses a `-D` for both (a dict; a Path-typed default) yet keeps the key in
`config.overrides`, so skipping the TOML value would honour an override that
was never applied. A TOML `outdir` now stands under `-D src_trace_outdir=...`,
as it did before this branch.

The Sphinx reader's catch-all keeps ub-project's message for a
ProjectConfigError (which names the file) and says `... from <file>: <error>`
for anything else -- a RecursionError's text names no file.
…counts that go stale

A file named ubproject.toml is the shared default whether or not conf.py
spells the name: missing, or without `[codelinks]`, it is silent; a file that
exists but cannot be read or parsed warns, as any configured file did at 1.4.0;
any other name is explicit and warns in all three cases. The earlier text
said an "explicitly configured" missing file warns, which is false for the
default name written out -- corrected in configuration.rst, both docstrings
and the amended changelog bullet, which now also says what a 1.4.0 project
that wrote the default name loses.

The -D changelog bullet and AGENTS.md name `outdir` beside `projects`, and the
bullet says an invalid -D value is now Sphinx's own error rather than being
replaced by the TOML. `load_codelinks_table` names the RecursionError it lets
through.

Counts: the package AGENTS.md says 17 test modules (it was already 17), and it,
the root AGENTS.md, the root CLAUDE.md and compat-requirements.txt describe
the libclang-gated tests without numbers that go stale with every test added.
…entences

The default is the value `ubproject.toml` exactly -- left unset, or written
in conf.py as that string -- because the reader compares strings, not files:
`./ubproject.toml` (or an absolute path to the same file) is an explicit file
and warns when missing or without `[codelinks]`. The sentences in
configuration.rst, both docstrings and the changelog bullet said the file's
name decided; they now say what the code does, and a test pins it (red if the
default were decided by `Path(value).name`).

AGENTS.md: "the two numbers" / "check the number" referred to counts the
block no longer shows.
@read-the-docs-community

read-the-docs-community Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 sphinx-codelinks | 🛠️ Build #34863833 | 📁 Comparing ee59bc4 against latest (d0edd0e)

  🔍 Preview build  

3 files changed
± changelog.html
± components/configuration.html
± development/traceability.html

@github-actions github-actions Bot added pkg: workspace The repository as a whole: workflows, CI, release, docker, tooling, the workspace root pkg: sphinx-codelinks Concerns the sphinx-codelinks package (packages/sphinx-codelinks) labels Sep 30, 2026
@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.87500% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.79%. Comparing base (d68d10d) to head (ee59bc4).
⚠️ Report is 30 commits behind head on master.

Files with missing lines Patch % Lines
...codelinks/sphinx_extension/directives/src_trace.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2005      +/-   ##
==========================================
+ Coverage   91.69%   91.79%   +0.09%     
==========================================
  Files         129      129              
  Lines       18155    18125      -30     
==========================================
- Hits        16648    16638      -10     
+ Misses       1507     1487      -20     
Flag Coverage Δ
codelinks 93.69% <96.87%> (+0.26%) ⬆️
mounts 94.26% <ø> (+0.24%) ⬆️
pytests 91.48% <ø> (+0.07%) ⬆️
reports 88.55% <ø> (-0.21%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…el output

typer forces a colour terminal when GITHUB_ACTIONS is set, so on the runners
the error panel carries ANSI escape codes, including between the halves of a
wrapped line, and `_normalize_output` only collapsed box characters and
whitespace: a phrase split by the wrap stayed split. Which test broke depends
on the panel width (on CI: test_analyse_toml_syntax_error_shows_the_reason).
The escapes are now removed first. Reproduced locally with GITHUB_ACTIONS=1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: sphinx-codelinks Concerns the sphinx-codelinks package (packages/sphinx-codelinks) pkg: workspace The repository as a whole: workflows, CI, release, docker, tooling, the workspace root

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant