Skip to content

Commit ea476a1

Browse files
committed
Improve GitHub Actions Python release workflow
Split the workflow into validation and release paths and add clearer step names and safeguards. PRs against main/master now run a validation matrix (3.10, 3.11, 3.12) that builds the package, runs twine check, and smoke-tests installing the wheel; fail-fast is disabled to surface version-specific issues. Tag pushes matching v*.*.* trigger a canonical release build (single Python 3.12) that produces dist artifacts, uploads them as workflow artifacts, and a separate publish job downloads those artifacts and uploads them to PyPI. Other improvements: minimal permissions (contents: read), explicit checkout/setup steps, pip caching enabled, comments for clarity, and use of --skip-existing when uploading to PyPI.
1 parent 3cc01db commit ea476a1

File tree

1 file changed

+48
-5
lines changed

1 file changed

+48
-5
lines changed

.github/workflows/python-package.yml

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,62 @@
11
name: Build, validate & Release
22

3+
# Usage:
4+
# - For PRs: this workflow runs automatically to validate the package builds and installs correctly on multiple Python versions. No artifacts are published for PRs.
5+
# - For releases: when you push a tag like v1.2.3, this workflow runs the full matrix validation, then builds the release artifacts, and finally publishes to PyPI if all checks pass.
6+
37
on:
8+
# Release pipeline: run only when pushing a version-like tag (e.g. v1.2.3)
49
push:
510
tags:
611
- "v*.*.*"
12+
13+
# Validation pipeline: run on PRs targeting main/master (no publishing)
714
pull_request:
815
branches: [main, master]
916
types: [opened, edited, synchronize, reopened]
1017

18+
# This workflow only needs to read repo contents
1119
permissions:
1220
contents: read
1321

1422
jobs:
1523
test_matrix:
24+
# PR + tag validation: ensure the project builds and installs on multiple Pythons
1625
name: Test install & smoke (Py ${{ matrix.python-version }})
1726
runs-on: ubuntu-latest
1827
strategy:
28+
# Run all versions even if one fails (helps spot version-specific issues)
1929
fail-fast: false
2030
matrix:
2131
python-version: ["3.10", "3.11", "3.12"]
32+
2233
steps:
23-
- uses: actions/checkout@v6
24-
- uses: actions/setup-python@v6
34+
# Fetch repository sources so we can build/test
35+
- name: Checkout sources
36+
uses: actions/checkout@v6
37+
38+
- name: Set up Python
39+
uses: actions/setup-python@v6
2540
with:
2641
python-version: ${{ matrix.python-version }}
42+
# Optional: cache pip downloads to speed up repeated runs
2743
cache: pip
2844

45+
# Install packaging toolchain:
46+
# - build: creates wheel + sdist
47+
# - twine: validates metadata and can upload (upload only happens in publish job)
2948
- name: Install build tools
3049
run: python -m pip install -U pip build twine
3150

51+
# Build distributions just to verify packaging config works on this Python
3252
- name: Build (for validation only)
3353
run: python -m build
3454

55+
# Validate dist metadata (README rendering, required fields, etc.)
3556
- name: Twine check
3657
run: python -m twine check dist/*
3758

59+
# Smoke test: install the built wheel and verify the package imports
3860
- name: Install from wheel & smoke test
3961
run: |
4062
python -m pip install dist/*.whl
@@ -45,50 +67,71 @@ jobs:
4567
PY
4668
4769
build_release:
70+
# Tag-only build: produce the "official" release artifacts once matrix passed
4871
name: Build release artifacts
4972
runs-on: ubuntu-latest
5073
needs: test_matrix
74+
# Safety gate: only run for version tags, never for PRs/branches
5175
if: startsWith(github.ref, 'refs/tags/v')
76+
5277
steps:
53-
- uses: actions/checkout@v6
54-
- uses: actions/setup-python@v6
78+
# Fetch sources for the tagged revision
79+
- name: Checkout sources
80+
uses: actions/checkout@v6
81+
82+
# Use a single, modern Python for the canonical release build
83+
- name: Set up Python (release build)
84+
uses: actions/setup-python@v6
5585
with:
5686
python-version: "3.12"
5787

88+
# Install build + validation tooling
5889
- name: Install build tools
5990
run: python -m pip install -U pip build twine
6091

92+
# Produce both sdist and wheel in dist/
6193
- name: Build distributions
6294
run: python -m build
6395

96+
# Re-check metadata on the final artifacts we intend to publish
6497
- name: Twine check
6598
run: python -m twine check dist/*
6699

100+
# Store dist/ outputs so the publish job uploads exactly what we built here
67101
- name: Upload dist artifacts
68102
uses: actions/upload-artifact@v4
69103
with:
70104
name: dist
71105
path: dist/*
72106

73107
publish:
108+
# Tag-only publish: download built artifacts and upload them to PyPI
74109
name: Publish to PyPI (API token)
75110
runs-on: ubuntu-latest
76111
needs: build_release
112+
# Safety gate: only run for version tags
77113
if: startsWith(github.ref, 'refs/tags/v')
114+
78115
steps:
116+
# Retrieve the exact distributions produced in build_release
79117
- name: Download dist artifacts
80118
uses: actions/download-artifact@v4
81119
with:
82120
name: dist
83121
path: dist
84122

85-
- uses: actions/setup-python@v6
123+
# Set up Python (only needed to run Twine)
124+
- name: Set up Python (publish)
125+
uses: actions/setup-python@v6
86126
with:
87127
python-version: "3.x"
88128

129+
# Install twine for uploading
89130
- name: Install Twine
90131
run: python -m pip install -U twine
91132

133+
# Upload to PyPI using an API token stored in repo secrets.
134+
# --skip-existing avoids failing if you re-run a workflow for the same version.
92135
- name: Publish to PyPI
93136
env:
94137
TWINE_USERNAME: __token__

0 commit comments

Comments
 (0)