Skip to content

Commit 227d3f6

Browse files
ci: update github workflows
1 parent 80e4f07 commit 227d3f6

12 files changed

Lines changed: 6372 additions & 2258 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 59 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,28 @@ on:
1010
# Run on all PRs regardless of target branch
1111
branches:
1212
- '**'
13+
# Let release.yml run CI before publishing
14+
workflow_call:
15+
16+
# CI only reads the code
17+
permissions:
18+
contents: read
1319

1420
jobs:
1521
# Job 1: Lint and Format Check
16-
# Runs ruff to check code quality and formatting
22+
# Runs the same ruff hooks (version, args) as prek does locally
1723
lint:
1824
runs-on: ubuntu-latest
1925
steps:
2026
- name: Checkout code
2127
uses: actions/checkout@v7
2228

23-
- name: Set up Python
24-
uses: actions/setup-python@v7
25-
with:
26-
python-version: '3.11'
27-
2829
- name: Install uv
29-
uses: astral-sh/setup-uv@v7
30-
with:
31-
version: "latest"
32-
33-
- name: Install ruff
34-
run: uv pip install --system ruff
30+
uses: astral-sh/setup-uv@v10.2.0
3531

36-
- name: Run ruff check
37-
run: ruff check --config ./afterpython/ruff.toml .
38-
39-
- name: Run ruff format check
40-
# --check flag ensures it only validates, doesn't reformat
41-
run: ruff format --check --config ./afterpython/ruff.toml .
32+
- name: Run ruff
33+
# fails if ruff-check finds issues or ruff-format would change a file
34+
run: uvx prek run --config afterpython/.pre-commit-config.yaml --all-files --show-diff-on-failure ruff-check ruff-format
4235

4336
# Job 2: Detect Test Strategy
4437
# Determines whether to use pixi or uv based on pixi.toml presence
@@ -67,44 +60,36 @@ jobs:
6760
runs-on: ubuntu-latest
6861
strategy:
6962
matrix:
70-
# TODO: support python 3.14
71-
python-version: ['3.11', '3.12', '3.13']
63+
python-version: ['3.12', '3.13', '3.14']
7264
fail-fast: false
7365

66+
env:
67+
# every uv command in this job uses this Python (uv downloads it if needed)
68+
UV_PYTHON: ${{ matrix.python-version }}
69+
7470
steps:
7571
- name: Checkout code
7672
uses: actions/checkout@v7
7773

78-
- name: Set up Python ${{ matrix.python-version }}
79-
uses: actions/setup-python@v7
80-
with:
81-
python-version: ${{ matrix.python-version }}
82-
8374
- name: Install uv
84-
uses: astral-sh/setup-uv@v7
85-
with:
86-
version: "latest"
75+
uses: astral-sh/setup-uv@v10.2.0
8776

8877
- name: Install dependencies
89-
run: |
90-
# Install package, main dependencies, and test dependencies
91-
# If no [dependency-groups] test exists, only the package and main dependencies are installed
92-
uv pip install --system . --group test || uv pip install --system .
78+
# the package + all [dependency-groups] (e.g. test, dev), at the versions in uv.lock
79+
# --locked fails if uv.lock is missing or out of date with pyproject.toml
80+
run: uv sync --locked --all-groups
9381

9482
- name: Run tests with pytest
95-
# Tolerant by design: if pytest isn't installed, the project has no
96-
# [dependency-groups] test (the install step fell back to `uv pip
97-
# install --system .`). Treat that the same as "no tests yet" and
98-
# pass green — matches the install step's tolerance.
99-
# When pytest does run, exit code 5 (no tests collected) is success.
100-
# We capture pytest's exit code before any other command clobbers $?.
83+
# No pytest in any dependency group means "no tests yet", pass green.
84+
# Exit code 5 (no tests collected) is success too.
85+
# `|| code=$?` because GitHub runs bash with -e, which would stop at pytest's failure.
10186
run: |
102-
if ! command -v pytest >/dev/null 2>&1; then
103-
echo "pytest not installed (no [dependency-groups] test); skipping."
87+
if [ ! -x .venv/bin/pytest ]; then
88+
echo "pytest not installed (not in any [dependency-groups]); skipping."
10489
exit 0
10590
fi
106-
pytest -v
107-
code=$?
91+
code=0
92+
uv run --no-sync pytest -v || code=$?
10893
if [ $code -eq 5 ]; then exit 0; else exit $code; fi
10994
11095
# Job 3.2: Test Suite - Pixi Workflow
@@ -115,8 +100,7 @@ jobs:
115100
runs-on: ubuntu-latest
116101
strategy:
117102
matrix:
118-
# TODO: support python 3.14
119-
environment: ['py311', 'py312', 'py313']
103+
environment: ['py312', 'py313', 'py314']
120104
fail-fast: false
121105

122106
steps:
@@ -128,21 +112,20 @@ jobs:
128112
with:
129113
pixi-version: v0.81.0
130114
cache: true
115+
# install (and cache) only the environment this matrix run tests
116+
environments: ${{ matrix.environment }}
131117
# Optional: Uncomment if you need prefix.dev authentication
132118
# auth-host: prefix.dev
133119
# auth-token: ${{ secrets.PREFIX_DEV_TOKEN }}
134120

135121
- name: Run tests with pixi
136-
# Runs test in specific Python environment (py311, py312, py313)
137-
# Assumes you have environments defined in pixi.toml with a "test" task
138-
# Example pixi.toml:
139-
# [environments]
140-
# py311 = ["py311", "test"]
141-
# [feature.test.tasks]
142-
# test = "pytest -v"
143-
# Note: Exit code 5 (no tests collected) is treated as success
122+
# Runs the "test" task of pixi.toml in this matrix run's environment (e.g. py313)
123+
# Exit code 5 (no tests collected) is success.
124+
# `|| code=$?` because GitHub runs bash with -e, which would stop at the failure.
144125
run: |
145-
pixi run -e ${{ matrix.environment }} test || if [ $? -eq 5 ]; then exit 0; else exit $?; fi
126+
code=0
127+
pixi run -e ${{ matrix.environment }} test || code=$?
128+
if [ $code -eq 5 ]; then exit 0; else exit $code; fi
146129
147130
# Job 3.3: Test Suite - Complete
148131
# Unified test status for branch protection
@@ -179,42 +162,44 @@ jobs:
179162
- name: Checkout code
180163
uses: actions/checkout@v7
181164

182-
- name: Set up Python
183-
uses: actions/setup-python@v7
184-
with:
185-
python-version: '3.11'
186-
187165
- name: Install uv
188-
uses: astral-sh/setup-uv@v7
189-
with:
190-
version: "latest"
166+
uses: astral-sh/setup-uv@v10.2.0
191167

192168
- name: Build package
193-
run: uv build
169+
# --no-sources: build like it will be published, ignoring [tool.uv.sources] (local paths, git)
170+
run: uv build --no-sources
194171

195172
- name: List build artifacts
196173
# Show what was built (wheel + sdist)
197174
# Useful for debugging build issues
198175
run: ls -lh dist/
199176

200-
- name: Install built package
201-
# Test that the built wheel can be installed
202-
# This catches packaging issues like missing files
203-
run: uv pip install --system dist/*.whl
204-
205177
- name: Verify installation
206-
# Generic verification: assume project.name maps to import name
207-
# by replacing '-' with '_'. If a project breaks this rule, CI fails.
178+
# Install the wheel into a throwaway env and import it, catches packaging issues like missing files.
179+
# Import name: [tool.uv.build-backend] module-name if set, else project.name with '-' -> '_'.
180+
# python -P: don't put the current folder on sys.path, so the import can't pick up
181+
# the source folder (or the afterpython/ folder) instead of the installed wheel.
208182
run: |
209-
PROJECT_NAME="$(python - <<'PY'
183+
uv run --isolated --no-project --with dist/*.whl python -P - <<'PY'
184+
import importlib
210185
import tomllib
186+
211187
with open("pyproject.toml", "rb") as f:
212188
data = tomllib.load(f)
213-
print(data["project"]["name"])
189+
backend = data.get("tool", {}).get("uv", {}).get("build-backend", {})
190+
names = backend.get("module-name") or data["project"]["name"].replace("-", "_")
191+
for name in [names] if isinstance(names, str) else names:
192+
importlib.import_module(name)
193+
print(f"import {name} OK")
214194
PY
215-
)"
216-
IMPORT_NAME="${PROJECT_NAME//-/_}"
217-
python -c "import ${IMPORT_NAME}"
195+
196+
- name: Upload build artifacts
197+
# release.yml publishes exactly these files instead of building again
198+
uses: actions/upload-artifact@v7
199+
with:
200+
name: dist
201+
path: dist/
202+
if-no-files-found: error
218203

219204
# Job 5: Type Check
220205
# typecheck:

‎.github/workflows/deploy.yml‎

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ on:
1616
- 'afterpython/authors.yml'
1717
- 'afterpython/faq.yml'
1818
- 'afterpython/README.py'
19+
- 'afterpython/afterpython.toml'
20+
# metadata.json (name, version, urls) and the homepage description
21+
- 'pyproject.toml'
22+
- 'README.md'
1923
- '.github/workflows/deploy.yml'
2024
workflow_dispatch: # Allow manual deployment from Actions tab
2125

@@ -50,31 +54,19 @@ jobs:
5054
cache: 'pnpm'
5155
cache-dependency-path: './afterpython/_website/pnpm-lock.yaml'
5256

53-
- name: Set up Python
54-
uses: actions/setup-python@v7
55-
with:
56-
python-version: '3.11'
57-
5857
- name: Install uv
59-
uses: astral-sh/setup-uv@v7
60-
with:
61-
version: "latest"
62-
63-
- name: Install afterpython
64-
run: uv pip install --system afterpython
58+
uses: astral-sh/setup-uv@v10.2.0
6559

66-
- name: Install marimo
67-
# Needed by `ap build` when afterpython/README.py is a marimo notebook;
68-
# afterpython shells out to the `marimo` CLI for the WASM export.
69-
run: uv pip install --system marimo
60+
- name: Install the project
61+
# afterpython itself (the project) + its marimo extra, for the marimo README.py, at uv.lock's versions
62+
run: uv sync --locked --extra marimo
7063

7164
- name: Install website dependencies
7265
run: pnpm install
7366
working-directory: ./afterpython/_website
7467

7568
- name: Build website
76-
run: |
77-
ap build
69+
run: uv run --no-sync ap build
7870

7971
- name: Upload artifact
8072
uses: actions/upload-pages-artifact@v5

‎.github/workflows/release.yml‎

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ on:
66
- 'v*' # Trigger on all version tags (v0.1.0, v0.1.0rc1, v0.1.0.dev4, etc.)
77

88
jobs:
9+
# Run the whole CI workflow (lint, tests, build) on the tagged commit first
10+
ci:
11+
uses: ./.github/workflows/ci.yml
12+
permissions:
13+
contents: read
14+
915
release:
16+
# Only publish if CI passes, a PyPI upload can't be undone
17+
needs: ci
1018
runs-on: ubuntu-latest
1119
permissions:
1220
contents: write # Required for creating GitHub releases
@@ -16,21 +24,28 @@ jobs:
1624
- name: Checkout code
1725
uses: actions/checkout@v7
1826

19-
- name: Set up Python
20-
uses: actions/setup-python@v7
21-
with:
22-
python-version: '3.11'
23-
2427
- name: Install uv
25-
uses: astral-sh/setup-uv@v7
26-
with:
27-
version: "latest"
28+
uses: astral-sh/setup-uv@v10.2.0
2829

29-
- name: Build package
30-
run: uv build
30+
- name: Check tag matches version
31+
# e.g. a hand-made tag v0.4.0 on a commit whose pyproject.toml says 0.3.20
32+
run: |
33+
VERSION="$(uv version --short)"
34+
if [ "v$VERSION" != "$GITHUB_REF_NAME" ]; then
35+
echo "::error::Tag $GITHUB_REF_NAME doesn't match version $VERSION in pyproject.toml"
36+
exit 1
37+
fi
38+
39+
- name: Download build artifacts
40+
# the wheel + sdist that CI built and verified, published as is
41+
uses: actions/download-artifact@v8
42+
with:
43+
name: dist
44+
path: dist/
3145

3246
- name: Publish to PyPI
33-
run: uv publish
47+
# Skip files already on PyPI so a re-run (e.g. after the GitHub release step failed) doesn't fail here
48+
run: uv publish --check-url https://pypi.org/simple/
3449

3550
- name: Determine if pre-release
3651
id: check_prerelease

0 commit comments

Comments
 (0)