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
41 changes: 33 additions & 8 deletions .github/workflows/execute.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand All @@ -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<<EOF'
echo "$nbs"
echo 'EOF'
} >> "$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
Expand Down
81 changes: 81 additions & 0 deletions .github/workflows/preview.yaml
Original file line number Diff line number Diff line change
@@ -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 = '<!-- preview-link -->';
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 });
}
36 changes: 36 additions & 0 deletions .github/workflows/trigger-docs-rebuild.yaml
Original file line number Diff line number Diff line change
@@ -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"
Loading