diff --git a/.github/workflows/github-pages-mkdocs.yml b/.github/workflows/github-pages-mkdocs.yml index 43c722d9..7da7cb29 100644 --- a/.github/workflows/github-pages-mkdocs.yml +++ b/.github/workflows/github-pages-mkdocs.yml @@ -18,46 +18,16 @@ on: workflows: ['github-release'] types: [completed] -# Each job restores the .jupyter_cache/ content-addressed store from -# actions/cache and runs scripts/prep_notebooks.py to populate working-tree -# notebooks with outputs (executing only those missing from the cache), then -# hands off to the shared mkdocs-deploy composite action which builds with -# mkdocs-jupyter execute: false — reading the populated outputs directly. -# -# The cache key pins on all notebook inputs + library source + pyproject.toml. -# Any change to library code a notebook imports invalidates the cache and -# forces re-execution, keeping rendered outputs correct. -# -# The prep step uses the runner's default Python with a light pip install -# (statista + jupyter + nbconvert) — it does NOT install uv; the composite -# action does that itself for the mkdocs build. jobs: deploy-pr: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Restore jupyter notebook cache - uses: actions/cache@v4 - with: - path: .jupyter_cache - key: jupyter-cache-${{ hashFiles('docs/notebook/**/*.ipynb', 'src/statista/**/*.py', 'pyproject.toml') }} - restore-keys: | - jupyter-cache- - - - name: Prepare notebook outputs (execute uncached, merge cached) - run: | - pip install -e . jupyter nbconvert ipykernel - python scripts/prep_notebooks.py - - uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1 with: trigger: 'pull_request' @@ -65,78 +35,40 @@ jobs: python-version: '3.12' install-groups: 'groups: docs' deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} + notebooks-path: 'docs/notebook' deploy-main: if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Restore jupyter notebook cache - uses: actions/cache@v4 - with: - path: .jupyter_cache - key: jupyter-cache-${{ hashFiles('docs/notebook/**/*.ipynb', 'src/statista/**/*.py', 'pyproject.toml') }} - restore-keys: | - jupyter-cache- - - - name: Prepare notebook outputs (execute uncached, merge cached) - run: | - pip install -e . jupyter nbconvert ipykernel - python scripts/prep_notebooks.py + - uses: actions/checkout@v5 + with: + fetch-depth: 0 - - uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1 - with: - trigger: 'main' - package-manager: 'uv' - python-version: '3.12' - install-groups: 'groups: docs' - deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} + - uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1 + with: + trigger: 'main' + package-manager: 'uv' + python-version: '3.12' + install-groups: 'groups: docs' + deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} + notebooks-path: 'docs/notebook' deploy-release: if: github.event_name == 'release' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') runs-on: ubuntu-latest steps: - # When triggered by workflow_run, GitHub loads this workflow file from the - # default branch but the checkout would default to that branch's HEAD too. - # We instead pin checkout to the branch where `github-release` actually ran - # (e.g. release/0.8.0) so the docs build sees the version-bump + lockfile - # commits and tag that github-release just pushed onto that branch. - # (Don't use head_sha here — that's the pre-bump SHA that triggered - # github-release, not the post-bump HEAD we want to publish.) - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || '' }} - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Restore jupyter notebook cache - uses: actions/cache@v4 - with: - path: .jupyter_cache - key: jupyter-cache-${{ hashFiles('docs/notebook/**/*.ipynb', 'src/statista/**/*.py', 'pyproject.toml') }} - restore-keys: | - jupyter-cache- - - - name: Prepare notebook outputs (execute uncached, merge cached) - run: | - pip install -e . jupyter nbconvert ipykernel - python scripts/prep_notebooks.py + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || '' }} - - uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1 - with: - trigger: 'release' - package-manager: 'uv' - python-version: '3.12' - install-groups: 'groups: docs' - deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} - mike-alias: 'latest' + - uses: serapeum-org/github-actions/actions/mkdocs-deploy@mkdocs/v1 + with: + trigger: 'release' + package-manager: 'uv' + python-version: '3.12' + install-groups: 'groups: docs' + deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} + notebooks-path: 'docs/notebook' + mike-alias: 'latest' diff --git a/mkdocs.yml b/mkdocs.yml index 284ec811..60e0ebfd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -52,10 +52,6 @@ theme: plugins: - search - mkdocs-jupyter: - # Committed notebooks have outputs stripped (nbstripout pre-commit hook); - # CI populates outputs via scripts/prep_notebooks.py before the build, - # using a GitHub Actions cache keyed on notebook + source + pyproject hash. - # See .github/workflows/github-pages-mkdocs.yml. execute: false allow_errors: false - autorefs diff --git a/pyproject.toml b/pyproject.toml index 265b7551..048335b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,8 @@ docs = [ "notebook<7.0", "jupyter", "commitizen (>=4.8.3,<5.0.0)", + "nbconvert", + "ipykernel" ] diff --git a/scripts/prep_notebooks.py b/scripts/prep_notebooks.py deleted file mode 100644 index 013958ea..00000000 --- a/scripts/prep_notebooks.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Populate notebook outputs before ``mkdocs build`` (CI-side helper). - -Committed notebooks have outputs stripped (see ``strip_notebook_outputs.py``). -For the rendered docs site to show outputs, the notebooks must be executed -before ``mkdocs-jupyter`` reads them — but doing that on every CI run is -wasteful. This script uses ``.jupyter_cache/`` as a content-addressed store: - -* If a cached executed copy already exists for a given notebook, it is copied - back over the working-tree notebook. -* Otherwise the notebook is executed in place (via ``jupyter nbconvert - --execute --inplace``) and then saved into the cache for next time. - -The ``.jupyter_cache/`` directory is itself persisted across CI runs by -``actions/cache`` keyed on the hash of the notebooks, library source, and -``pyproject.toml`` — so the per-notebook cache lookups here will mostly hit on -unchanged runs. - -Usage: - python scripts/prep_notebooks.py -""" - -from __future__ import annotations - -import shutil -import subprocess -import sys -from pathlib import Path - -NOTEBOOK_ROOT = Path("docs/notebook") -CACHE_ROOT = Path(".jupyter_cache/executed") - - -def cached_path_for(nb_path: Path) -> Path: - """Return the cache location that mirrors a notebook's path.""" - rel = nb_path.relative_to(NOTEBOOK_ROOT) - return CACHE_ROOT / rel - - -def execute_in_place(nb_path: Path) -> None: - """Execute a notebook in place using ``jupyter nbconvert``.""" - subprocess.run( - [ - sys.executable, - "-m", - "jupyter", - "nbconvert", - "--to", - "notebook", - "--execute", - "--inplace", - "--ExecutePreprocessor.timeout=600", - str(nb_path), - ], - check=True, - ) - - -def main() -> int: - if not NOTEBOOK_ROOT.exists(): - print(f"Notebook root {NOTEBOOK_ROOT} does not exist.", file=sys.stderr) - return 1 - - CACHE_ROOT.mkdir(parents=True, exist_ok=True) - - notebooks = [ - nb - for nb in sorted(NOTEBOOK_ROOT.rglob("*.ipynb")) - if ".ipynb_checkpoints" not in nb.parts - ] - - hits, misses = 0, 0 - for nb in notebooks: - cached = cached_path_for(nb) - if cached.exists(): - print(f"[cache hit] {nb}") - shutil.copy2(cached, nb) - hits += 1 - else: - print(f"[cache miss] executing {nb}") - execute_in_place(nb) - cached.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(nb, cached) - misses += 1 - - print(f"Done: {hits} hits, {misses} misses, {len(notebooks)} notebooks.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/uv.lock b/uv.lock index 6a2ffc72..82900e0d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.11, <4" resolution-markers = [ "python_full_version >= '3.14'", @@ -3330,6 +3330,7 @@ dev = [ ] docs = [ { name = "commitizen" }, + { name = "ipykernel" }, { name = "jupyter" }, { name = "jupyter-contrib-nbextensions" }, { name = "mike" }, @@ -3343,6 +3344,7 @@ docs = [ { name = "mkdocs-table-reader-plugin" }, { name = "mkdocstrings" }, { name = "mkdocstrings-python" }, + { name = "nbconvert" }, { name = "notebook" }, ] @@ -3373,6 +3375,7 @@ dev = [ ] docs = [ { name = "commitizen", specifier = ">=4.8.3,<5.0.0" }, + { name = "ipykernel" }, { name = "jupyter" }, { name = "jupyter-contrib-nbextensions", specifier = ">=0.7.0" }, { name = "mike", specifier = ">=2.1.3" }, @@ -3386,6 +3389,7 @@ docs = [ { name = "mkdocs-table-reader-plugin", specifier = ">=3.1.0" }, { name = "mkdocstrings", specifier = ">=0.24.0" }, { name = "mkdocstrings-python", specifier = ">=1.7.5" }, + { name = "nbconvert" }, { name = "notebook", specifier = "<7.0" }, ]