16 — Notebooks and scripts¶
Rules: LAY-8, DOC-12, DOC-13, TST-14, TST-15.
Users meet a project at three levels, and each is published on the documentation site:
| Level | Folder | Purpose | Kept honest by |
|---|---|---|---|
| 1. Understand | notebooks/ |
One concept per notebook: what happens under the hood, with real outputs | standards check-notebooks, then nbmake re-runs every notebook |
| 2. Apply | scripts/ |
Complete applications for specific or advanced uses | standards check-scripts, and a smoke test per script in the 100 % coverage run |
| 3. Integrate | docs/reference.md |
The Python API, generated from docstrings | docstrings at 100 %, strict docs build |
Notebooks¶
standards check-notebooks [--dir notebooks] checks, without running anything:
- names are
NN-concept.ipynb, numbered from01without gaps or duplicates; - the first cell is markdown
# NN — <concept>with Concept: and You will learn:, and the last cell is markdown with Next:; - every code cell ran exactly once, top to bottom (execution counts 1, 2, 3, …), with no error output and no empty cell. This proves the committed outputs come from a clean run;
- no import of a private name (
pkg._x,from pkg.mod import _y) of the package undersrc/; notebooks/README.mdlinks every notebook.
Then the notebooks pre-push hook (and CI) executes every notebook with nbmake, so a
notebook that breaks because the code changed fails the push. To refresh one after a change:
uv run jupyter nbconvert --to notebook --execute --inplace notebooks/01-time-grid.ipynb
Notebooks are committed with their outputs, so GitHub and the documentation site (mkdocs-jupyter,
execute: false) show results without running anything. Ruff lints them, with a few notebook
exemptions declared in pyproject.toml.
Scripts¶
standards check-scripts checks every scripts/<name>.py:
- a module docstring (what it does, for whom);
def main(argv: Sequence[str] | None = None) -> int;if __name__ == "__main__": raise SystemExit(main());- a smoke test
tests/scripts/test_<name>.py; - a mention of
`scripts/<name>.py`inscripts/README.md.
The smoke test calls main([...]) on small inputs, and runs the module as __main__ (for
example with runpy.run_module), so the scripts are part of the 100 % line and branch coverage
like any other code.
Repository tooling moved to tools/¶
Code that only hooks and CI call lives in tools/ (LAY-5). scripts/ now belongs to users.