From 28b6b37ed3eea8bb7c1d23bcec1f7ea8620bf6cf Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Thu, 26 Feb 2026 15:00:13 +1000 Subject: [PATCH] Cache jupytext conversion for docs builds --- docs/Makefile | 2 +- docs/conf.py | 8 +++-- docs/sphinxext/jupytext_cache.py | 51 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 docs/sphinxext/jupytext_cache.py diff --git a/docs/Makefile b/docs/Makefile index abf9cc069..db698ae42 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -12,7 +12,7 @@ BUILDDIR = _build .PHONY: help clean html html-exec Makefile html: - @UPLT_DOCS_EXECUTE=$${UPLT_DOCS_EXECUTE:-always} $(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" -E -a $(SPHINXOPTS) + @UPLT_DOCS_EXECUTE=$${UPLT_DOCS_EXECUTE:-auto} $(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) html-exec: @UPLT_DOCS_EXECUTE=always $(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" -E -a $(SPHINXOPTS) diff --git a/docs/conf.py b/docs/conf.py index 52f8f46fc..4dd0323f1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -403,6 +403,8 @@ def _reset_ultraplot(gallery_conf, fname): "color-spec": ":py:func:`color-spec `", "artist": ":py:func:`artist `", } +# Keep autodoc aliases stable across builds so incremental Sphinx caching works. +autodoc_type_aliases = dict(napoleon_type_aliases) # Fail on error. Note nbsphinx compiles all notebooks in docs unless excluded nbsphinx_allow_errors = False @@ -410,8 +412,10 @@ def _reset_ultraplot(gallery_conf, fname): # Give *lots* of time for cell execution nbsphinx_timeout = 300 -# Add jupytext support to nbsphinx -nbsphinx_custom_formats = {".py": ["jupytext.reads", {"fmt": "py:percent"}]} +# Add jupytext support to nbsphinx with conversion cache. +nbsphinx_custom_formats = { + ".py": ["sphinxext.jupytext_cache.reads_cached", {"fmt": "py:percent"}] +} # Keep notebook output backgrounds theme-adaptive. nbsphinx_execute_arguments = [ diff --git a/docs/sphinxext/jupytext_cache.py b/docs/sphinxext/jupytext_cache.py new file mode 100644 index 000000000..fc48d18b8 --- /dev/null +++ b/docs/sphinxext/jupytext_cache.py @@ -0,0 +1,51 @@ +""" +Jupytext converter with a small on-disk cache for docs builds. +""" + +import hashlib +import os +from pathlib import Path + +import jupytext +import nbformat + + +def _get_cache_dir(): + """ + Return cache directory for converted jupytext notebooks. + """ + override = os.environ.get("UPLT_DOCS_JUPYTEXT_CACHE_DIR", "").strip() + if override: + return Path(override).expanduser() + if os.environ.get("READTHEDOCS", "") == "True": + return Path.home() / ".cache" / "ultraplot" / "jupytext" + return Path(__file__).resolve().parent.parent / "_build" / ".jupytext-cache" + + +def reads_cached(inputstring, *, fmt="py:percent"): + """ + Convert Jupytext source to a notebook and cache by content hash. + """ + disabled = os.environ.get("UPLT_DOCS_DISABLE_JUPYTEXT_CACHE", "").strip().lower() + if disabled in {"1", "true", "yes", "on"}: + return jupytext.reads(inputstring, fmt=fmt) + + key = hashlib.sha256( + (fmt + "\0" + getattr(jupytext, "__version__", "") + "\0" + inputstring).encode( + "utf-8" + ) + ).hexdigest() + cache_file = _get_cache_dir() / f"{key}.ipynb" + if cache_file.is_file(): + try: + return nbformat.read(cache_file, as_version=4) + except Exception: + cache_file.unlink(missing_ok=True) + + notebook = jupytext.reads(inputstring, fmt=fmt) + try: + cache_file.parent.mkdir(parents=True, exist_ok=True) + nbformat.write(notebook, cache_file) + except Exception: + pass + return notebook