Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ build:
# blocker #2). Fetch a pinned Chrome into choreographer's deps dir, where
# Kaleido discovers it automatically (no PATH wiring needed).
- plotly_get_chrome -y
pre_build:
# Reuse the gallery the release pipeline built and published
# (scripts/publish_prebuilt_gallery.py): executing all the examples here
# takes longer than the 15-minute build limit. Examples whose source
# changed since are still executed by sphinx-gallery.
- python docs/fetch_prebuilt_gallery.py
post_build:
# Fix animated GIF thumbnails after documentation build
- python docs/post_build.py
Expand Down
322 changes: 163 additions & 159 deletions CHANGELOG.md

Large diffs are not rendered by default.

50 changes: 42 additions & 8 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,41 @@ detached tag checkout — the notebook migrator detects the branch via
do NOT commit afterward** — any later commit changes HEAD, so you must
rebuild the gallery and re-publish (the gate fails until you do). The same
gate re-runs on the master push (step 4) and the tag (step 5).
- [ ] **Rehearse the gallery hand-off before publishing anything.**
`scripts/rehearse_prebuilt_gallery.sh "$PWD" /tmp/gallery-rehearsal`
runs the whole sequence below against a local bare remote, using the
gallery you just built: publish, record, commit, fetch into a fresh
clone, build with the Read the Docs recipe (no example may execute),
then overwrite the branch and confirm the fetch refuses it. It must end
with `REHEARSAL PASSED`. Add `mini` to rehearse with three examples in a
few minutes, before the long gallery build. Build the docs with the
sphinx-gallery version Read the Docs installs (the latest; the floor in
`docs/doc_requirements.txt`).
- [ ] **Publish the built gallery for Read the Docs to reuse, and record
it.** Read the Docs ends a build after 15 minutes and executing the
gallery takes about 25, so its build fetches the gallery you just built
(`docs/fetch_prebuilt_gallery.py`, run from `.readthedocs.yaml`) and
sphinx-gallery skips every example whose source is unchanged. Do this
BEFORE publishing the notebooks above, from the repo root, with the
freshly built `docs/auto_examples` and a clean tracked tree:
`python scripts/publish_prebuilt_gallery.py --push`
It replaces the single commit on the `docs-gallery-v1.1.0` branch (a
forced push, by design: the tree is about 100 MB), refuses a gallery
that is incomplete or names this machine's path, and writes the new
commit's id to `docs/prebuilt_gallery.json`. Commit that file:
`git add docs/prebuilt_gallery.json && git commit -m "release: record the pre-built gallery"`
The docs build fetches the gallery by that commit id, never by branch
name, so overwriting the branch later cannot change what a build of the
release reads. This is the ONE commit allowed after building the
gallery; the gate fails if anything else changed. Publish the notebooks
(previous item) after it, so their manifest names the final HEAD.
- [ ] **Verify it:**
`HYPERTOOLS_REQUIRE_RELEASE=1 pytest tests/test_release_readiness_gate.py::test_release_gate_prebuilt_gallery_is_published_for_this_commit`
→ green, and `python docs/fetch_prebuilt_gallery.py --require` in a
fresh clone of the release commit prints `51 of 51 examples are
current`. After the `master` push in step 4, open the Read the Docs
build for `latest` and confirm it finished and its `pre_build` step
printed the same line.

## 3. Build + verify artifacts locally (do NOT upload yet)

Expand Down Expand Up @@ -216,14 +251,12 @@ same artifacts you verify are the ones you publish.
(Publication is a MANUAL step today — there is no CI job for it; a
`contents: write` `publish-gallery-notebooks` job on master/tags could
automate it once token/environment handling is decided.)
- [ ] **Read the Docs: build BOTH `latest` and the `v1.1.0` tag by hand.**
Pushes do not reach RTD at the moment: the GitHub → RTD webhook
(`https://readthedocs.org/api/v2/webhook/github/hypertools/`) last
answered HTTP 400 in GitHub's delivery log, and RTD has built nothing
since the 1.0.0 release (`latest`/`stable` at 647ce929, 2026-07-24),
although `master` moved on 2026-09-05. Re-sync the GitHub
integration in the RTD admin (Admin → Integrations,
https://app.readthedocs.org/dashboard/hypertools/integrations/), then
- [ ] **Read the Docs: confirm `latest` and the `v1.1.0` tag both built.**
Pushes reach RTD again: both `master` pushes on 2026-10-09 started
builds (before that the 2017 webhook had no secret and RTD answered
HTTP 400). A build that fails with "Build terminated due to time out"
means the pre-built gallery was not fetched or was stale (step 2): the
build's `pre_build` output says which. If a build is missing,
trigger builds of `latest` (master) and of the `v1.1.0` tag from
https://app.readthedocs.org/projects/hypertools/builds/, and point the
"stable"/default version at `v1.1.0`, replacing `v1.0.0`. Verify:
Expand Down Expand Up @@ -294,6 +327,7 @@ run with `HYPERTOOLS_REQUIRE_RELEASE=1` by the `release-gate` CI job on
| CHANGELOG heading | `## <version> (YYYY-MM-DD)` — version == pyproject, a REAL calendar date (not `(unreleased)`, not `2026-99-99`), and not earlier than the release commit's date (a stale draft date fails) |
| generated gallery (`docs-clean` job) | every built `docs/auto_examples/*.ipynb` carries the PyPI spec (covers every published notebook, at the build layer) |
| gallery Colab notebooks published | `docs-notebooks/v<version>/manifest.json` present, its `source_commit` == the release HEAD, its inventory == the built gallery, and the branch's actual `.ipynb` set (one GitHub tree request) == the manifest — so stale (old-RC), partial, or mismatched publishes all fail. Requires step 2's publish to have run FROM the release commit, BEFORE the master/tag push — see the deadlock note there. |
| pre-built gallery recorded | `docs/prebuilt_gallery.json` is for this version; its commit is what `docs-gallery-v<version>` points at; nothing but that file changed since the gallery was built; the gallery's example md5s == the examples in the checkout |

Always-on (every branch): no notebook installs the defunct `dev-1.0-refactor`;
all tutorial branch-installs share one branch; every README image is a single
Expand Down
31 changes: 31 additions & 0 deletions docs/_gallery_log_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,34 @@ def install(logger_name=GALLERY_LOGGER):
"""Attach the filter to sphinx-gallery's logger (idempotent enough for a
single docs build). Called from conf.py's ``setup(app)``."""
logging.getLogger(logger_name).addFilter(TransientDocLinkFetchFilter())


def relative_warning_paths(root):
"""Make Python warnings name files under ``root`` by their path relative
to it, e.g. ``examples/plot_impute.py:43: UserWarning: ...``.

sphinx-gallery prints a warning an example raises into that example's
output block, and Python's default format starts with the absolute path
of the file that warned. On a gallery page that is the build machine's
path to the checkout: noise to a reader, and different on every machine
that builds the docs. Returns the installed formatter; installing twice
is harmless (conf.py calls this before every example).
"""
import os
import warnings

current = warnings.formatwarning
if getattr(current, '_hypertools_root', None) == root:
return current
prefix = os.path.join(os.path.abspath(root), '')

def formatwarning(message, category, filename, lineno, line=None):
text = current(message, category, filename, lineno, line)
if filename.startswith(prefix) and text.startswith(filename):
text = (filename[len(prefix):].replace(os.sep, '/')
+ text[len(filename):])
return text

formatwarning._hypertools_root = root
warnings.formatwarning = formatwarning
return formatwarning
Loading
Loading