Skip to content

feat(docs): examples run when the docs build - #17

Merged
jmineau merged 4 commits into
mainfrom
feat/executed-docs
Oct 10, 2026
Merged

jmineau merged 4 commits into
mainfrom
feat/executed-docs

Conversation

@jmineau

@jmineau jmineau commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Examples in a package's docs now run when the docs build, so they show real output, figures included, and just build-docs fails when one breaks.

Where How Shows
A docstring's Examples section .. plot:: (matplotlib) the code and the figure it draws, on that member's own API page
An .rst page .. ipython:: python (IPython) each line with its output; a figure with @savefig name.png
A Markdown page, with docs_notebooks {code-cell} blocks (myst-nb), under a four-line header what each cell prints and plots
A notebook (.ipynb), with docs_notebooks myst-nb the same, for every cell

Changing a .. code-block:: python to .. ipython:: python is enough to make an existing .rst example run.

The new question: docs_notebooks (default no)

The first two rows are on in every project. The last two need myst-nb, which runs cells in a Jupyter kernel, so they are a copier question: answering yes adds myst-nb, and 24 packages with it (ipykernel, jupyter-client, pyzmq, tornado, jupyter-cache, ...), to the dev group, which every CI job installs. Without it the dev group grows from 83 to 108 packages (matplotlib and IPython); with it, to 132.

With docs_notebooks, just build-docs starts clean and runs every notebook, as CI does, and just docs-serve reruns a Markdown page or notebook only when its code changes. A notebook that needs data CI doesn't have keeps its committed outputs with "mystnb": {"execution_mode": "off"} in its metadata.

Changes

  • copier.yml: the docs_notebooks question.
  • docs/conf.py: the plot and IPython directives. The plot directive shows the source and one PNG; IPython's figures go under docs/_build. With docs_notebooks: myst-nb, run with a cache (docs/_build/.jupyter_cache), printing a failing cell's traceback, and **.ipynb_checkpoints excluded.
  • pyproject.toml: matplotlib and ipython in the dev group; myst-nb with docs_notebooks.
  • just docs-serve ignores docs/_build and __pycache__. It ignored only docs/_build/html, and a build with notebooks writes elsewhere under docs/_build, so after the first edit each rebuild triggered the next, without end. Separately, and before this change, every source edit built the docs twice: importing the edited module rewrites its .pyc under src/, which is watched.
  • .pre-commit-config.yaml: the ruff hooks check notebooks too (types_or: [python, pyi, jupyter], as ruff's own hook does). just lint already did, so a commit could pass the hooks and fail CI.
  • AGENTS.md: a section on writing examples (the kinds this project has, data CI can reach, fixed seeds, extras in the dev group). CONTRIBUTING.md and the build-docs comment say it runs the examples.
  • Template repo: fixtures/ holds one example of each kind. just test now bakes with both yes/no questions answered yes, then no (still two bakes), copies the fixtures in, checks that their output reaches the HTML, and checks that the "no" bake locks no ipykernel.

The Documentation workflow is unchanged. A package lists the extras its examples need in its dev group (pkg[extra]), so uv sync installs them locally and in CI.

How a broken example fails

Example Raises Emits a Python warning
.. plot:: build fails not tried
.. ipython:: build fails (:okexcept: allows it) build fails (:okwarning: allows it)
code cell build fails shown on the page under the cell (myst-nb's default; not tried here)

After copier update in a package

  • copier update --defaults answers no to docs_notebooks; a package with notebooks passes --data docs_notebooks=true.
  • All .rst pages share one IPython session, so each page imports and defines what it uses.
  • With docs_notebooks: myst-nb reads .md files as pages, so a stray .md under docs/ warns "document isn't included in any toctree" (add it to a toctree or to exclude_patterns); and a package that loads myst_parser removes it (myst_nb loads it), and nbsphinx goes too.

Tested

  • just test passes for both bakes.
  • By hand, in a baked project: a raising notebook cell, a raising .. plot::, and a raising or warning .. ipython:: block each fail just build-docs; a notebook with execution_mode: off is not run.
  • just docs-serve, with notebooks: one rebuild per edit of a page or a source file; a Markdown page reran only when its code changed, and the notebook was not rerun.

Closes #16.

A package's docs can now show real output, figures included, and
`just build-docs` fails when an example breaks.

- matplotlib's plot directive: `.. plot::` in a docstring's Examples
  section, or on any page, shows the code and the figure it draws.
- myst-nb: code cells run at build time, in Markdown pages (prose with a
  few cells) and in notebooks. `just build-docs` starts clean and runs
  them all; `just docs-serve` reruns a page only when its code changes.
  A notebook CI can't run keeps its committed outputs with
  "mystnb": {"execution_mode": "off"} in its metadata.
- matplotlib and myst-nb join the dev group.
- The ruff pre-commit hooks check notebooks, as `just lint` already did.
- AGENTS.md says how to write examples.
- `just test` copies fixtures/ into the baked project (a docstring
  figure, a Markdown page with a code cell, a notebook) and checks that
  their output reaches the HTML.

Closes #16.
Code cells need a Markdown page, and most guides are .rst. IPython's
directive runs a block in an .rst page and shows each line with its
output, so changing `.. code-block:: python` to `.. ipython:: python`
is enough to make an existing example run.

- conf.py loads the directive and saves its figures (`@savefig`) under
  docs/_build instead of docs/savefig.
- A block that raises or warns fails the build; `:okexcept:` and
  `:okwarning:` allow one that is meant to.
- ipython joins the dev group.
- AGENTS.md describes it; `just test` runs an .rst page with two blocks
  and checks its output and figure in the HTML.

Part of #16.
The live preview watches docs/ and src/, and only docs/_build/html was
ignored. A build writes the notebook cache, the executed notebooks and
the IPython figures elsewhere under docs/_build, so after the first
edit each rebuild triggered the next, without end. Importing an edited
module also rewrites its .pyc under src/, which built the docs a second
time after every source edit.

Part of #16.
myst-nb runs cells in a Jupyter kernel, so it adds 24 packages
(ipykernel, jupyter-client, pyzmq, tornado, jupyter-cache, ...) to the
dev group, which every CI job installs. Only a project with notebooks
needs that, so it moves behind `docs_notebooks` (default no).
`.. plot::` and `.. ipython::` stay on everywhere.

- conf.py, the dev group and AGENTS.md include the myst-nb parts only
  with docs_notebooks.
- `just test` answers both yes/no questions yes in one bake and no in
  the other, copies the notebook fixtures only into the first, and
  checks that the second locks no ipykernel.

Part of #16.
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.

Examples that run when the docs build (figures in docstrings, notebooks)

1 participant