Repository navigation
feat(docs): examples run when the docs build - #17
Merged
Merged
Conversation
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.
This was referenced Oct 10, 2026
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.
Examples in a package's docs now run when the docs build, so they show real output, figures included, and
just build-docsfails when one breaks... plot::(matplotlib).rstpage.. ipython:: python(IPython)@savefig name.pngdocs_notebooks{code-cell}blocks (myst-nb), under a four-line header.ipynb), withdocs_notebooksChanging a
.. code-block:: pythonto.. ipython:: pythonis enough to make an existing.rstexample 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
devgroup, which every CI job installs. Without it thedevgroup grows from 83 to 108 packages (matplotlib and IPython); with it, to 132.With
docs_notebooks,just build-docsstarts clean and runs every notebook, as CI does, andjust docs-servereruns 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: thedocs_notebooksquestion.docs/conf.py: the plot and IPython directives. The plot directive shows the source and one PNG; IPython's figures go underdocs/_build. Withdocs_notebooks: myst-nb, run with a cache (docs/_build/.jupyter_cache), printing a failing cell's traceback, and**.ipynb_checkpointsexcluded.pyproject.toml:matplotlibandipythonin thedevgroup;myst-nbwithdocs_notebooks.just docs-serveignoresdocs/_buildand__pycache__. It ignored onlydocs/_build/html, and a build with notebooks writes elsewhere underdocs/_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.pycundersrc/, 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 lintalready 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 thedevgroup).CONTRIBUTING.mdand thebuild-docscomment say it runs the examples.fixtures/holds one example of each kind.just testnow 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
devgroup (pkg[extra]), souv syncinstalls them locally and in CI.How a broken example fails
.. plot::.. ipython:::okexcept:allows it):okwarning:allows it)After
copier updatein a packagecopier update --defaultsanswers no todocs_notebooks; a package with notebooks passes--data docs_notebooks=true..rstpages share one IPython session, so each page imports and defines what it uses.docs_notebooks: myst-nb reads.mdfiles as pages, so a stray.mdunderdocs/warns "document isn't included in any toctree" (add it to a toctree or toexclude_patterns); and a package that loadsmyst_parserremoves it (myst_nbloads it), and nbsphinx goes too.Tested
just testpasses for both bakes... plot::, and a raising or warning.. ipython::block each failjust build-docs; a notebook withexecution_mode: offis 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.