docs: the Python API page renders every public name from its docstring - #1766
Merged
Merged
Conversation
The reference on docs/reference/api.md is now mkdocstrings, one entry per name in specsolve.__all__ plus the rows and frames a caller reads back (Diagnostics, Record, Metrics, SliceMetrics). It replaces the hand-written verb table and error list, which repeated the docstrings. - The 483 Sphinx roles in src/ are mkdocstrings cross-references, which the site resolves; a name from another package is plain code. - griffe-sphinx reads the `#:` attribute comments the tree already uses. - load_external_modules renders the language's errors that specsolve re-exports from mathspec. - tests/test_docs_site.py holds every exported name to an entry, and refuses a Sphinx role in src/. The task sections of api.md stay where they are; moving them into the how-to guides is the next change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw
Merging this PR will not alter performance
Comparing Footnotes
|
tests/test_docstring_links.py walks every docstring in src/, the `#:` comments included, resolves each link the way the site does, and refuses one that lands nowhere or on another package. The strict build only checks the docstrings it renders, and eight links in modules it never renders pointed at nothing; they are fixed. It runs in `pixi run docs-test`, where griffe is installed. AGENTS.md says how a docstring links a name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw
FBumann
added this pull request to stack #1770
September 25, 2026 13:13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw
FBumann
added a commit
that referenced
this pull request
Sep 25, 2026
… of the name it belongs to (#1769) > **Prompt:** Is there even more duplication in the docs now? Especially in Reference? As a stacked PR > [!NOTE] > The following content was generated by AI. The API page is now the rendered reference plus two rules that every verb shares. Each task-section fact that no docstring held moved into the docstring of its name. The `keep=` guidance moved to the debugging how-to. Four drifts are fixed. <details><summary>What moved where</summary> | api.md section (deleted) | Its unique facts now live in | |---|---| | The spec argument, Checking a spec | `check`: the spec shapes, "a `Spec` is not read again", "no verb takes a `Program`", the `expand('piecewise')` / `expand()` choice, and the CI-verb sentence | | Checking against a sink | `check`: "`solve` and `write` read the same table", and a refusal names the construct and the sinks that take it. The intro of "What each sink takes" | | The sources argument | `build` `sources`, which links the data contract | | Building a model | `Model` (already covered) | | Reading one row | `Model.row` (a label its dimension cannot hold, no reader for a column). `ConstraintRow`, now rendered (linopy's format, `display_terms` summary) | | Reading a result | `dual` (an expanded set, duals only where a solver ran, no reduced costs or slacks). `dual_ray` (`InfUnbdInfo` / `presolve`, live only). `Result.evaluate` (an undeclared expression is not a kind, the archive model check, a new parameter is a build). `to_pandas` (pandas is not installed) | | Writing a file | `write` `out` | | Re-solving with new numbers | `Model.update` (a raise releases the model, the reason behind `DataError`, `solve_over` is the loop written for you) | | How much of the session a solve keeps | The contract was already in `Model.solve` `keep` and `Result.kept`. The when-to-use guidance, the #815 numbers and #382 moved to howto/debug §6 | | Archiving a model, Loading or scanning | howto/archiving (already covered), `Model.solve` `archive` (sources through the build door, uncompressed), `solve_over` `archive` (a sliced source archived whole, a hand-built axis refused), `SolveArchive` (spec as written, digest caveats), `scan_result` (re-read at every collect) | | Diagnostics | the `Diagnostics` and `Metrics` attributes (already covered) | | Choosing a solver | `Model.solve` `solver_name` / `solver_options` (xpress, a time limit in three vocabularies, Gurobi environment options). `solve` now says "As `Model.solve` takes it" | Other duplicates: - sweeps.md: "The axes" table became links to `EachCoordinate` and `EachWindow` plus the hand-built row. The `SliceMetrics` and "asked before it is sliced" rows point at the entries. `EachWindow` now states the window checks rather than naming a private method. The remote-Gurobi sentence that #1771 added now links `Model.solve`. - glossary: "Row types" became one-liners that link the entries. **Drifts fixed:** - `solver_name` omitted xpress. - `sweep.record` columns were listed in three forms (6, 8 and 6). The real record has 9 columns. `Sweep.record`, sweeps.md and howto/parallel now say "one `Record` per slice", so the column list has one home. - `check`'s Returns said every verb takes the `Program` back. No verb does. - howto/data said index *levels* attach by name. A `MultiIndex` is refused. - The sink table said HiGHS rewrites SOS to binaries. It refuses and names `Spec.expand()`. - Two docstrings linked `../../docs/reference/sweeps.md`, which is broken when rendered. They now use the site URL. `ConstraintRow` linked a private helper; that is now plain text. **Gates** on the merge of #1766 (which now carries main through #1771), in a `uv` environment with the dev and docs groups and the `gurobi` and `xpress` extras: - `ruff check .` and `ruff format --check .`: clean. - `pyrefly check`: 0 errors. - `zensical build --strict`: no issues. Every `api.md#…` anchor used under `docs/` exists in the rendered page. - Docs tests (`test_docs_math`, `test_docstring_links`): 70 passed. - Full suite: 4115 passed, 250 skipped, 1 xfailed. It collects fewer tests than #1766 (4153) because `test_doc_examples` runs one test per fence, and the deleted sections held fences. **Not done:** the task sections were not rewritten as new how-tos. Their examples already exist in howto/archiving, howto/debug and interactive.md. #1765 edits the api.md archive table that this PR deletes, so whichever PR merges second resolves that conflict. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
After "Do it", on the plan to render the docstrings first and move api.md's tasks into how-tos next. Later: "How can we ensure future code changes use this format for links?", answered by a test for every docstring link and a rule in AGENTS.md.
Note
The following content was generated by AI.
docs/reference/api.mdnow renders all 27 exported names from their docstrings with mkdocstrings, plus the four row and frame types a caller reads back. The 483 Sphinx roles insrc/are now links, and a test holds every docstring link insrc/to landing on a specsolve object, rendered or not.What changed, what the build caught, guards, gates, not done
The page
## Referencesection, grouped as the architecture's surface is: run a spec (check,build,solve,write,evaluate); run it many times (solve_over,EachCoordinate,EachWindow); what comes back (Model,Result,Sweep, andDiagnostics,Record,Metrics,SliceMetrics, which readers return but__all__does not name); carry an answer (the two archives and six readers); errors and warnings.The tooling
docsgroup addsmkdocstrings==1.0.6,mkdocstrings-python==2.0.9andgriffe-sphinx==0.3.0, pinned exactly like the rest of the group.uv.lockis relocked.griffe-sphinxreads the#:attribute comments the tree already uses (AGENTS.md names them). Without it, the fields ofSweep,Diagnostics,Record,MetricsandSliceMetricsrender with no text.load_external_modules: truerendersSpecsolveError,LanguageError,SchemaErrorandDimensionError, which specsolve re-exports from mathspec, from mathspec's installed source.The roles. All 483 Sphinx roles in
src/are converted, as energy-models/mathspec#720 did:[`name`][], or[`name`][dotted.path]where it was written~dotted.path. It resolves in the docstring's own scope.Keeping future links in this form
tests/test_docs_site.py::test_no_docstring_links_with_a_sphinx_rolerefuses a Sphinx role anywhere insrc/. It runs in the main suite.tests/test_docstring_links.py(new) loadssrc/with griffe and the samegriffe_sphinxextension the site uses. It resolves each link the way the site does: in the docstring's scope, walking outward. It refuses one that lands nowhere, or on another package. It reads 440 links, docstrings and#:comments together, and asserts it read over 400, so a walk that reaches nothing fails too. It runs underpixi run docs-test, which CI's docs job already calls, and skips where griffe is absent._entriesno longer exists (now[_splice][]);placedisGrouping's, not_Order's;row_major,spanned,PolarsCompiler.expressionandAssembly._build_objectiveneeded their full path. A local function and mathspec'sProgrambecame plain code.AGENTS.md, Docstrings: a name is linked as[`name`][]where the module imports it and[`name`][dotted.path]where it does not; a name from another package is plain code.What the strict build caught, and the fixes
Diagnostics,Record,Metrics,SliceMetrics). A private method, theKEEPSconstant,concurrent.futures.Executor,LanguageErrorinsideerrors.py, and two internal digests are now plain code. Thekeep=docstrings now name the three values instead of pointing atKEEPS.Model's docstring rendered as a code block with the backticks showing; it is now one line of code spans.load_external_modulesshowed up. The strict build still fails, but the message points at the links, not at the cause.Guards. Mutation table, taken by hand because
tools/mutate.pymutates Python only. Each run started from a committed tree, restored throughgit checkout --, and dropped__pycache__on both sides; the tree was clean after each set:::: specsolve.checkentry deleted fromapi.md:func:role added tosolve's docstring[_entries][])[to_spec][mathspec.to_spec])Gates, in a
uvenvironment (dev + docs groups,gurobiandxpressextras), because pixi cannot be installed here:ruff check .andruff format --check .pass.pytest -n auto: 4143 passed, 250 skipped, 1 xfailed.pyrefly check: 0 errors, on the first commit. The second commit edits docstrings and tests only.zensical build --strict: builds, with no issues on the first commit. The rendered page holds 130 entries, no Sphinx role, and no stray doubled backticks.pytest tests/test_docs_math.py tests/test_docstring_links.py: 70 passed.Not done:
architecture.md's surface section now says the docstrings are the reference.🤖 Generated with Claude Code
https://claude.ai/code/session_016Pv7LzSgzt7Yn2K3ioyJXw