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
1420jobs :
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:
0 commit comments