From 8d8c7bf4360f4a5d469136b946ccb21a84085997 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Mon, 11 May 2026 17:38:57 +0200 Subject: [PATCH] Add auto rebuild trigger, PR preview, and diff-only execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I1 — trigger-docs-rebuild.yaml On push to main (when notebook content changes), POST to ReadTheDocs API to rebuild scverse/spatialdata-plot's `latest` version. Combined with the lib's `post_checkout: git submodule update --remote --recursive`, this propagates new gallery content end-to-end without manual submodule bumps. Skips silently if RTD_TOKEN secret is unset. Required setup: add RTD_TOKEN repo secret (token from https://readthedocs.org/accounts/tokens/). I4 — preview.yaml On every PR (touching notebook content), build the lib's docs against the PR's notebook content, deploy to gh-pages under pr-/, and post a preview link comment. Updates the comment in place on subsequent pushes. Required setup: enable GitHub Pages on this repo (Settings -> Pages, source: Deploy from a branch, branch: gh-pages /). I6 — execute.yaml: only diff-touched notebooks on PR Schedule + workflow_dispatch still re-execute everything; PRs now run only the notebooks they touched. Keeps PR feedback fast as the gallery grows. Mirrors sklearn's gallery CI pattern. --- .github/workflows/execute.yaml | 41 +++++++++-- .github/workflows/preview.yaml | 81 +++++++++++++++++++++ .github/workflows/trigger-docs-rebuild.yaml | 36 +++++++++ 3 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/preview.yaml create mode 100644 .github/workflows/trigger-docs-rebuild.yaml diff --git a/.github/workflows/execute.yaml b/.github/workflows/execute.yaml index ec0628a..b08cdb9 100644 --- a/.github/workflows/execute.yaml +++ b/.github/workflows/execute.yaml @@ -2,7 +2,7 @@ name: Execute notebooks on: schedule: - # Weekly: Mondays 04:00 UTC + # Weekly: Mondays 04:00 UTC — re-executes ALL notebooks against latest releases. - cron: "0 4 * * 1" pull_request: branches: [main] @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v5 with: - # Need full history so nbdime can diff against base. + # Need full history so we can diff PR HEAD against the merge base. fetch-depth: 0 - uses: actions/setup-python@v5 @@ -41,18 +41,43 @@ jobs: - name: Install execution environment run: | pip install --upgrade pip - pip install -e ".[exec]" nbdime jupyter + pip install -e ".[exec]" nbdime - - name: Re-execute notebooks + - name: Determine notebooks to execute + id: pick run: | - # Find every notebook under tutorials/ and examples/, execute in - # place, fail on any cell error. - find tutorials examples -name "*.ipynb" -not -path "*/.ipynb_checkpoints/*" | while read -r nb; do + # On the weekly schedule and manual dispatch, run all notebooks. + # On PR, run only notebooks the PR touched, to keep CI fast as the + # gallery grows. (sklearn does the same.) + if [ "${{ github.event_name }}" = "pull_request" ]; then + base="${{ github.event.pull_request.base.sha }}" + nbs=$(git diff --name-only --diff-filter=AMR "$base"...HEAD -- '*.ipynb' | grep -E '^(tutorials|examples)/' || true) + else + nbs=$(find tutorials examples -name "*.ipynb" -not -path "*/.ipynb_checkpoints/*" 2>/dev/null || true) + fi + if [ -z "$nbs" ]; then + echo "No notebooks to execute." + else + echo "Will execute:" + echo "$nbs" + fi + { + echo 'files<> "$GITHUB_OUTPUT" + + - name: Re-execute selected notebooks + if: steps.pick.outputs.files != '' + run: | + while IFS= read -r nb; do + [ -z "$nb" ] && continue echo "Executing $nb" jupyter nbconvert --to notebook --execute --inplace "$nb" - done + done <<< "${{ steps.pick.outputs.files }}" - name: Diff outputs against committed + if: steps.pick.outputs.files != '' run: | # If outputs drift from what's committed, fail the job. Authors are # expected to commit re-executed notebooks; CI catches drift between diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml new file mode 100644 index 0000000..d7409c4 --- /dev/null +++ b/.github/workflows/preview.yaml @@ -0,0 +1,81 @@ +name: PR preview + +on: + pull_request: + branches: [main] + paths: + - "**/*.ipynb" + - "tutorials/**" + - "examples/**" + +concurrency: + group: preview-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +# gh-pages publishing needs write access to the repo. +permissions: + contents: write + pull-requests: write + +jobs: + preview: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out notebooks PR (this repo) + uses: actions/checkout@v5 + with: + path: notebooks + ref: ${{ github.event.pull_request.head.sha }} + + - name: Check out spatialdata-plot main + uses: actions/checkout@v5 + with: + repository: scverse/spatialdata-plot + path: lib + fetch-depth: 0 + + - name: Mount PR notebooks into lib's submodule path + run: | + # Replace the lib's submodule mount with the PR's notebook content + # so the docs build renders the proposed changes. + rm -rf lib/docs/notebooks + cp -R notebooks lib/docs/notebooks + # The lib's .gitmodules still references the public URL — drop it + # for this build so sphinx doesn't get confused by an absent .git + # inside the submodule path. + rm -f lib/.gitmodules + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Build docs + working-directory: lib + run: uvx --with="virtualenv<21" hatch run docs:build + + - name: Deploy preview to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: lib/docs/_build/html + destination_dir: pr-${{ github.event.pull_request.number }} + keep_files: true + commit_message: "Preview for PR #${{ github.event.pull_request.number }}" + + - name: Comment preview link on PR + uses: actions/github-script@v7 + with: + script: | + const pr = context.issue.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + const url = `https://${owner}.github.io/${repo}/pr-${pr}/gallery.html`; + const marker = ''; + const body = `${marker}\n📖 **Docs preview**: ${url}\n\n_Built from ${context.sha.substring(0, 7)}; redeployed on every push._`; + const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: pr }); + const existing = comments.find(c => c.body && c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number: pr, body }); + } diff --git a/.github/workflows/trigger-docs-rebuild.yaml b/.github/workflows/trigger-docs-rebuild.yaml new file mode 100644 index 0000000..bccdfaa --- /dev/null +++ b/.github/workflows/trigger-docs-rebuild.yaml @@ -0,0 +1,36 @@ +name: Trigger spatialdata-plot docs rebuild + +on: + push: + branches: [main] + paths: + # Only the rendered content matters to the lib's docs build. + - "**/*.ipynb" + - "tutorials/**" + - "examples/**" + workflow_dispatch: + +jobs: + trigger: + runs-on: ubuntu-latest + steps: + - name: Trigger ReadTheDocs build of spatialdata-plot latest + env: + RTD_TOKEN: ${{ secrets.RTD_TOKEN }} + # The RTD project slug — verify in RTD admin (the URL segment of the + # project's dashboard page). For scverse this is typically + # `spatialdata-plot`; if the project is hosted under a different slug, + # update here. + RTD_PROJECT: spatialdata-plot + run: | + if [ -z "$RTD_TOKEN" ]; then + echo "RTD_TOKEN secret is not set; skipping rebuild trigger." + echo "To enable: generate a token at https://readthedocs.org/accounts/tokens/" + echo "and add it as the RTD_TOKEN repository secret." + exit 0 + fi + response=$(curl -fsS -X POST \ + -H "Authorization: Token $RTD_TOKEN" \ + "https://readthedocs.org/api/v3/projects/$RTD_PROJECT/versions/latest/builds/" \ + -w "\nHTTP %{http_code}") + echo "$response"