docs: add API documentation infrastructure - #4348
Merged
Merged
Conversation
5 tasks
LeRobot's documentation build passes `--not_python_module`, which tells doc-builder there is no importable Python package and disables `[[autodoc]]` entirely. The result is that all 90+ pages are hand-written guides and there is no generated API reference at all. This is the machinery to change that. It deliberately contains no docstring changes of its own — every docstring edit lives in the follow-up PR, so this one can be reviewed as tooling and configuration alone. **The standard.** `docs/source/writing_docstrings.mdx` is the contract: Google section headers with Hugging Face type formatting, the machine-checked argument line, `**Attributes**:`, doc-builder cross-references, fenced doctest examples. It also records three behaviours that are not discoverable from the source and were verified against a local build: `[[autodoc]]` silently skips members with no docstring; doc-builder does not inherit docstrings from base classes, so a registered config shim whose body is `pass` renders every field with no description; and module-level aliases resolve to the canonical class. **Autodoc turned on**, with two changes that are not obvious: - `--version main` on the main-docs job. Without `--not_python_module`, doc-builder resolves the version from `lerobot.__version__` and only maps it to the default branch when it contains "dev". transformers relies on that; our main carries 0.6.2. Verified by building both ways — dropping the flag alone would publish the main docs to /lerobot/v0.6.2/ instead of /lerobot/main/ and disable notebook building. - `pre_command` on both jobs. doc-builder ships a mock-deps registry entry for lerobot, so the reusable workflow takes its light-install path, which cannot import the package. The heavy dependencies cannot be mocked either: draccus runs `register_subclass` at import time and `processor/converters.py` calls `functools.singledispatch.register(torch.Tensor)`, which needs a real class. `[dataset]` is the only extra required. Workflow triggers gain `src/**`, since the reference is now generated from docstrings. `docs/source/api/` is excluded from the prettier hook, which reads `[[autodoc]]` member lists as lazy paragraph continuations and joins a ten-entry list onto one line. Nine API reference pages, scaffolded with each module's base class. **Doctests.** `LeRobotDocTestParser` is mandatory rather than optional here: ruff's `docstring-code-format = true` drops the blank line before a closing fence, after which stdlib's `_EXAMPLE_RE` reads the fence as expected output and every example with output fails. It is written against the installed pytest rather than copied from transformers, whose version predates pytest 9's `import_path` signature and its own fix for the `@property` line-number bug. `preprocess_string` also diverges: the upstream fenced-block split puts a single-line example's code in a chunk with no `>>>` in it, so neither the CUDA skip nor the `+IGNORE_RESULT` injection fires for it. **Checkers.** `utils/check_docstrings.py` is the ~300-line core of the 2203-line transformers original; the `@auto_docstring` system, modular propagation, GitPython and `checkers.py` are not ported. `utils/check_config_docstrings.py` checks that every registered robot config documents its port and calibration semantics. **Gates**, all set to values that pass today: ruff `D` with per-file-ignores per unconverted module, `interrogate` at `fail-under = 52` against a measured 52.1%, and Makefile targets wired into the quality workflow. The doctest allowlist ships empty and the `doctest` target handles that, because the files carrying runnable examples arrive with the docstring PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pkooij
force-pushed
the
docs/writing-standard
branch
from
August 6, 2026 19:01
7ccdebb to
2e8345a
Compare
5 tasks
The shared doc-builder workflows create their virtualenv with the runner's
system Python, which is 3.10.12 on ubuntu-22.04. lerobot requires >=3.12, so
the build died during "Setup environment":
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.10.12) does not satisfy
Python>=3.12 and lerobot==0.6.2 depends on Python>=3.12 ...
That step runs before `pre_command`, so the real install this workflow already
performs never got the chance to run. There was no fix available on the caller
side either: `env:` does not propagate into a reusable workflow, so `UV_PYTHON`
is unavailable, and `uv venv` runs in the runner workspace root rather than the
checkout, so a `.python-version` file cannot reach it. The non-light fallback
(`uv pip install "./pkg[dev]"`) fails identically, so this is not specific to
the mock-deps path — it blocks any package requiring 3.12+.
huggingface/doc-builder#808 adds a `python_version` input to both build
workflows, which this passes. Pins move to that merge commit, picking up three
unrelated fixes in the same range (#810, #811, #812); the upload workflow is
unchanged there and is bumped only to keep all three pins on one SHA.
pkooij
force-pushed
the
docs/writing-standard
branch
from
August 7, 2026 09:49
96e8f97 to
535094b
Compare
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
Enabling pydocstyle in the previous commit changes how ruff determines where a module's import block ends, which makes I001 fire on three vla_jepa tests that were clean before. The blank line between the `conftest` and `lerobot` imports is the trigger: both are first-party, so isort wants them in one contiguous block, and the docstring-aware analysis is what makes it notice. These files are unrelated to the API reference, so the fix is only to satisfy the new gate.
5 tasks
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.
Summary / Motivation
The docs build passes
--not_python_module, which disables[[autodoc]]entirely. All 90+ pages are hand-written guides, there is no generated API reference, and public docstring coverage sits at 48.8% (2152/4414).This is the infrastructure to change that — tooling and configuration only. The one file it adds under
src/lerobot/is the doctest parser.Related issues
What changed
The standard
docs/source/writing_docstrings.mdx— Google section headers with Hugging Face type formatting: section set and order, the machine-checked argument line, type-firstReturns:,**Attributes**:, cross-references, callouts, fenced doctest examples. Linked from_toctree.ymlandCONTRIBUTING.md.It also records three behaviours that aren't discoverable from the source, each verified against a local build:
[[autodoc]]silently skips members with no docstring, so coverage and reference completeness are the same problem.passrenders every field, inherited ones included, with no descriptions.SO101FollowerConfig = SOFollowerRobotConfig) resolve to the canonical class.Autodoc turned on
--not_python_moduledropped from both jobs, plus two changes that aren't obvious:--version mainon the main-docs job. doc-builder resolves the version fromlerobot.__version__and only maps it to the default branch when it contains"dev"; transformers relies on that, our main carries0.6.2. Verified by building both ways — without the flag the main docs publish to/lerobot/v0.6.2/, and notebook building is off.python_version: "3.12"on both jobs. The shared workflows build their venv with the runner'ssystem Python (3.10 on
ubuntu-22.04); lerobot requires >=3.12, so the install died during Setupenvironment, before
pre_commandcould run. There is no caller-side workaround —env:does notpropagate into a reusable workflow, and
uv venvruns in the workspace root so a.python-versionfile cannot reach it. Fixed upstream by Add python_version input to doc build workflows doc-builder#808; the pins move to that merge
commit.
pre_command: uv pip install "./lerobot[dataset]"on both jobs. doc-builder ships a mock-deps registry entry for lerobot, so the reusable workflow takes its light-install path, which can't import the package. The heavy dependencies can't be mocked either: draccus runsregister_subclassat import time, andprocessor/converters.pycallssingledispatch.register(torch.Tensor), which needs a real class.[dataset]is the only extra required.Workflow triggers gain
src/**, since the reference is now generated from docstrings — the docs job will run on most source PRs.docs/source/api/is excluded from the prettier hook, which reads[[autodoc]]member lists as lazy continuations and joins them onto one line. Nine API pages under a new toctree section.Doctests
LeRobotDocTestParseris mandatory rather than optional: ruff'sdocstring-code-format = truedrops the blank line before a closing fence, after which stdlib's_EXAMPLE_REreads the fence as expected output and every example with output fails.Written against the installed pytest rather than copied from transformers, whose version predates pytest 9's
import_pathsignature change and its own fix for the@propertyline-number bug.preprocess_stringdiverges deliberately — the upstream fenced-block split leaves a single-line example's code in a chunk containing no>>>, so neither the CUDA skip nor the+IGNORE_RESULTinjection fires for it.Plus
SKIP_CUDA_DOCTEST/SKIP_HARDWARE_DOCTEST,utils/check_doctest_list.py, and regression tests asserting the stdlib parser fails where ours succeeds.Checkers and gates
utils/check_docstrings.pyis the ~300-line core of the 2203-line transformers original — the@auto_docstringsystem, modular propagation, GitPython andcheckers.pyaren't ported. Two divergences found by running it:inspect.signaturealready resolves inherited dataclass fields (butdefault_factoryreports a<factory>sentinel that must not become a literal default), andfix_docstringmust not re-indent, because Python keeps docstring lines at their source indentation.utils/check_config_docstrings.pychecks that every registered robot config documents its port and calibration semantics.Gates, all set to values that pass today: Makefile targets wired into
quality.yml, ruffDwith per-file-ignores per unconverted module,interrogateatfail-under = 52against a measured 52.1%.How was this tested (or how to run locally)
The CI light-install environment was reproduced exactly to confirm the
pre_commandfix — build fails afterdoc-builder light-install lerobot, passes afteruv pip install ".[dataset]".Checklist (required before merge)
pre-commit run -a)pytest)Reviewer notes
Merge order matters. This turns autodoc on while the renderer fixes live in #4353. Merged alone, the new API pages publish with
:pymeth:as literal text andAttributes:sections rendered as constructor arguments — not broken, but visibly wrong until #4353 follows.Three gates ship deliberately loose here and are tightened in #4353, since the fixes they depend on live there:
check_docstrings.pyallowlists ten objects with bareAttributes:sections, the two package-root files areD-ignored, and the doctest list ships empty.--version mainis the thing to check hardest — it's invisible to "did the build succeed", and getting it wrong silently moves where the main docs publish.interrogateruns as a CI step rather than a pre-commit hook: 1.7.0 imports the deprecatedpypackage, which in pre-commit's isolated env resolves against whateverpyis importable and dies before reading any config.Worth reporting upstream:
doc_builder/mock_deps/lerobot.txtlists fourreal:dependencies and no packages to mock, which is what makes light-install produce an unimportable environment. It should probably be deleted — and if bare names are added later they'd shadow the really-installed packages and break the build again.Enabling
Dturns out to change how ruff decides where a module's import block ends, which makesI001 fire on three
vla_jepatests that were clean before — the blank line between theirconftestand
lerobotimports. Confirmed by adding only"D"toselecton an otherwise untouchedmain.They are fixed in the last commit here rather than left to #4354, so this PR is green on its own;
#4354 is now redundant.