Repository navigation
refactor(template): extract the docs build steps out of hooks.py - #185
Merged
Merged
Conversation
`docs/hooks.py` was 2678 lines behind five MkDocs event hooks. Two of them did work that has nothing to do with rendering a page: `on_pre_build` read `src/` and wrote `docs/pages/api/*.md`, then exported the marimo notebooks; `on_post_build` read `site/` and wrote markdown back into it. Neither imported a MkDocs type -- `on_pre_build` ignored its `config` argument entirely -- yet the only way to run either was a full `mkdocs build`. That put file-in/file-out logic in the slowest test tier the template has, made the generated `.md` an intermediate nobody could produce on demand, and left a discovery layer that four generated repos each reinvented before the template consolidated one. Move ~1000 lines into three modules that import nothing from mkdocs: docs/_api_pages.py discovery + API page generation docs/_notebooks.py marimo export and its content-hash cache docs/_markdown_export.py HTML->markdown conversion and llms.txt The hooks stay as adapters. Deleting them would break the documented preview workflow: `mkdocs.yml` watches `src` so that adding a class appears without restarting `mkdocs serve`, and that only works because `on_pre_build` regenerates on every rebuild. `on_config` keeps resetting the per-build caches, but two of them moved with the functions that own them, so it now calls `_api_pages.reset_caches()` -- a `global` statement cannot rebind another module's globals. The cache registration test was scanning only the hooks module, so it had silently narrowed to "every cache that stayed behind" while still passing; it now scans every module the hooks load. Verified as a move, not a rewrite: the generated API pages are byte-identical under both `include_examples` values. The only site difference is deliberate -- mkdocs treats every non-page file under `docs_dir` as a static asset, so it was publishing the build tooling's source and bytecode to the documentation site. That was already true of `hooks.py`; splitting it made it four modules, so `exclude_docs` now covers `*.py` and `__pycache__/`. Also checked by hand against a live server: adding a class to `src/` and retitling a notebook's `__gallery__` both appear without a restart. BREAKING CHANGE: `docs/` gains `_api_pages.py`, `_markdown_export.py` and, with examples enabled, `_notebooks.py`. Projects that forked `docs/hooks.py` will need to reconcile a one-file-into-four split rather than a line diff. The site no longer publishes `docs/*.py` or `docs/__pycache__/`.
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.
Why
docs/hooks.pywas 2678 lines behind five MkDocs event hooks. Two of them did work that has nothing to do with rendering a page:on_pre_buildreadssrc/, writesdocs/pages/api/*.md, exports the marimo notebooks — and ignores itsconfigargument entirelyon_post_buildreadssite/, writes markdown back into it — usingconfigfor two directory stringsNeither imports a MkDocs type. They are file-in/file-out programs wearing a hook as a costume, and the costume cost real things: the only way to run either was a full
mkdocs build, so they lived in the slowest test tier for logic needing no theme, no server and no markdown; the generated.mdwas an intermediate nobody could produce on demand; and the discovery layer was reinvented independently by four generated repos before the template consolidated one.What changed
~1000 lines moved into three modules that import nothing from mkdocs:
docs/_api_pages.pydocs/_notebooks.pydocs/_markdown_export.pyllms.txtEach has a
main()and runs standalone. Newnox -s build_stepsruns them without a docs build.The hooks stay as adapters. Deleting them would break the documented preview workflow —
mkdocs.ymlwatchessrcso adding a class appears without restartingmkdocs serve, and that only works becauseon_pre_buildregenerates on every rebuild.Verification
Byte-identity. The generated API pages are identical before and after, under both
include_examplesvalues. The one site file that differs is proven nondeterministic by a control (two builds of the same code differ — marimo embeds a per-export UUID).A real leak fixed. mkdocs treats every non-page file under
docs_diras a static asset, so it was publishing the build tooling's source and bytecode to the docs site. Already true ofhooks.py; splitting it made it four modules.exclude_docsnow covers*.pyand__pycache__/.Cache guarantee kept. Two caches moved with their functions, so
on_configcalls_api_pages.reset_caches()— aglobalstatement cannot rebind another module's globals. The registration test was scanning only the hooks module, so it had silently narrowed to "every cache that stayed behind" while still passing; it now scans every module the hooks load, and is mutation-tested (removing the reset fails it, naming both caches).Checked against a live server. Adding a class to
src/and retitling a notebook's__gallery__both appear without a restart.prekcleanmkdocs build --strictclean on both variantsLint config narrowed
docs/**/*split into two entries. The hooks keep their unused-argument exemptions (they implement mkdocs' signatures); the extracted steps get onlyT201/PLW0603— no blanketARGexemption, because they implement no imposed signature. That immediately caught a genuinely unused parameter with examples disabled.Not done here (deliberately)
print("[hooks] …")prefix still says[hooks]from_api_pages— a rename, excluded from a movedocs_examplescould usedocs_dir / "examples"instead ofproject_root / "docs" / "examples"— more correct, but a behaviour changemkdocs servetriggers a rebuild loop (_generate_api_pageswrites into a watched path). Symmetric control: baseline +18 rebuilds, this branch +19 — unchanged by this PRDownstream
Fan-out to the seven generated repos follows release.
hooks.pyis Tier 1, but Dynamic Reclassification promotes a locally-edited Tier 1 file to Tier 2, socopier updatemerges rather than overwrites — a one-file-into-four split against a fork is not a case the tiering was designed for. Establish each repo's actual fork status first (render the template with that repo's.copier-answers.ymland diff; cross-repo md5 comparison proves nothing, since the template interpolates{{ package_name }}20 times).