Skip to content

docs: add API documentation infrastructure - #4348

Merged
pkooij merged 6 commits into
mainfrom
docs/writing-standard
Aug 7, 2026
Merged

docs: add API documentation infrastructure#4348
pkooij merged 6 commits into
mainfrom
docs/writing-standard

Conversation

@pkooij

@pkooij pkooij commented Aug 6, 2026

Copy link
Copy Markdown
Member

Pairs with #4353. This PR is the machinery and contains no docstring changes; #4353 has every docstring edit. See merge order below.

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-first Returns:, **Attributes**:, cross-references, callouts, fenced doctest examples. Linked from _toctree.yml and CONTRIBUTING.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.
  • doc-builder does not inherit docstrings from base classes. A registered config shim whose body is pass renders every field, inherited ones included, with no descriptions.
  • Module-level aliases (SO101FollowerConfig = SOFollowerRobotConfig) resolve to the canonical class.

Autodoc turned on

--not_python_module dropped from both jobs, plus two changes that aren't obvious:

  • --version main on the main-docs job. 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 — 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's
    system Python (3.10 on ubuntu-22.04); lerobot requires >=3.12, so the install died during Setup
    environment
    , before pre_command could run. There is no caller-side workaround — env: does not
    propagate into a reusable workflow, and uv venv runs in the workspace root so a .python-version
    file 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 runs register_subclass at import time, and processor/converters.py calls 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 — 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

LeRobotDocTestParser is mandatory rather than optional: 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.

Written against the installed pytest rather than copied from transformers, whose version predates pytest 9's import_path signature change and its own fix for the @property line-number bug. preprocess_string diverges 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_RESULT injection 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.py is the ~300-line core of the 2203-line transformers original — the @auto_docstring system, modular propagation, GitPython and checkers.py aren't ported. Two divergences found by running it: inspect.signature already resolves inherited dataclass fields (but default_factory reports a <factory> sentinel that must not become a literal default), and fix_docstring must not re-indent, because Python keeps docstring lines at their source indentation.

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: Makefile targets wired into quality.yml, ruff D with per-file-ignores per unconverted module, interrogate at fail-under = 52 against a measured 52.1%.

How was this tested (or how to run locally)

uv pip install -e ".[dataset]" -r docs-requirements.txt
doc-builder build lerobot docs/source/ --build_dir /tmp/doc-build   # 111 pages, no flag
make check-docstrings && make check-doctest-list && make doctest
uv run pytest tests/utils/test_doctest_utils.py -q
pre-commit run --all-files

The CI light-install environment was reproduced exactly to confirm the pre_command fix — build fails after doc-builder light-install lerobot, passes after uv pip install ".[dataset]".

Checklist (required before merge)

  • Linting/formatting run (pre-commit run -a)
  • All tests pass locally (pytest)
  • Documentation updated
  • CI is green
  • Community Review: I have reviewed another contributor's open PR and linked it here: #

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 and Attributes: 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.py allowlists ten objects with bare Attributes: sections, the two package-root files are D-ignored, and the doctest list ships empty.

--version main is the thing to check hardest — it's invisible to "did the build succeed", and getting it wrong silently moves where the main docs publish.

interrogate runs as a CI step rather than a pre-commit hook: 1.7.0 imports the deprecated py package, which in pre-commit's isolated env resolves against whatever py is importable and dies before reading any config.

Worth reporting upstream: doc_builder/mock_deps/lerobot.txt lists four real: 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 D turns out to change how ruff decides where a module's import block ends, which makes
I001 fire on three vla_jepa tests that were clean before — the blank line between their conftest
and lerobot imports. Confirmed by adding only "D" to select on an otherwise untouched main.
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.

@pkooij pkooij changed the title docs: add docstring writing standard docs: API documentation infrastructure (standard, autodoc, doctests, checkers) Aug 6, 2026
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
pkooij force-pushed the docs/writing-standard branch from 7ccdebb to 2e8345a Compare August 6, 2026 19:01
@pkooij pkooij changed the title docs: API documentation infrastructure (standard, autodoc, doctests, checkers) docs: add API documentation infrastructure Aug 6, 2026
@github-actions github-actions Bot added documentation Improvements or fixes to the project’s docs tests Problems with test coverage, failures, or improvements to testing CI Issues related to the continuous integration pipeline github_actions labels Aug 7, 2026
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
pkooij force-pushed the docs/writing-standard branch from 96e8f97 to 535094b Compare August 7, 2026 09:49
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

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.

pkooij and others added 2 commits August 7, 2026 11:57
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.
@pkooij
pkooij requested a review from CarolinePascal August 7, 2026 10:02

@CarolinePascal CarolinePascal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

@pkooij
pkooij merged commit 6c73c41 into main Aug 7, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI Issues related to the continuous integration pipeline documentation Improvements or fixes to the project’s docs github_actions tests Problems with test coverage, failures, or improvements to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants