Add the PyPI release workflow (M-PYPI PYPI.6) - #35
Merged
Conversation
Three jobs, gh-action-pypi-publish + Trusted Publishing throughout (OIDC,
no stored PyPI token):
- build: sdist + wheel, twine check, then installs ONLY the built wheel
into a fresh venv and imports it — catches a packaging mistake that an
editable install would hide. Built once and shared via upload-artifact
so TestPyPI and PyPI publish the exact same bytes.
- publish-testpypi: runs on every trigger, right before any production
publish, so a bad build surfaces on the sandbox index first.
- publish-pypi: gated with `if: github.event_name == 'release'`. A
workflow_dispatch rehearsal run has no code path to this job at all —
that's a structural safeguard, not just the `pypi` environment's
required-reviewer gate.
workflow_dispatch replaces the originally-sketched `-rc` tag convention for
rehearsing against TestPyPI: trigger it manually from the Actions tab to
exercise build + TestPyPI publish without touching production PyPI or
needing a throwaway tag.
Trusted Publishing already registered on both indexes (PYPI.5): pending
publisher on pypi.org (project doesn't exist there yet), normal publisher
on test.pypi.org (it does, from the manual PYPI.4 upload). Both point at
this workflow file and their respective `pypi` / `testpypi` GitHub
Environments.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M-PYPI PYPI.6 — the workflow that will actually publish to PyPI.
Design
Three jobs,
pypa/gh-action-pypi-publish+ Trusted Publishing throughout (OIDC— no PyPI/TestPyPI token stored anywhere in this repo):
build— builds the sdist + wheel once and shares them viaupload-artifact, so TestPyPI and PyPI always publish the exact samebytes rather than two separate builds that could theoretically differ.
twine check, then — the part worth reading closely — installs only thebuilt wheel into a throwaway venv and imports it. That catches a packaging
mistake (missing bundled data file, wrong package discovery, etc.) that an
editable install (
pip install -e .) would never surface, because editableinstalls bypass most of what actually goes into a wheel.
publish-testpypi— runs on every trigger, immediately before anyproduction publish, so a broken build is caught on the sandbox index first.
skip-existing: truebecause TestPyPI rejects re-uploading a version and arehearsal run will often repeat one.
publish-pypi— gatedif: github.event_name == 'release'. A manualrehearsal run has no code path to this job at all — that's a structural
safeguard, not just the
pypienvironment's required-reviewer gate. Noskip-existinghere: PyPI's own rejection of a duplicate version should failthe job loudly, not be silently skipped.
Triggers
release: [published]— the real path (PYPI.7).workflow_dispatch— run manually from the Actions tab to rehearsebuild + TestPyPI publish without touching PyPI or needing a throwaway tag.
Replaces the roadmap's originally-sketched
-rctag convention; this issimpler and can't accidentally leak toward production because of the
ifabove.
Already done (not in this diff — GitHub/PyPI web UI only)
pypi.org (project doesn't exist there yet) and a normal publisher on
test.pypi.org (it does — from the manual PYPI.4 TestPyPI upload). Both point
at
release.ymland their respectivepypi/testpypienvironment names.pypihas a required reviewer, so a tagpush builds the release but a human approves the actual production publish.
outside PyPI/GitHub OIDC now.
Before the first real release
Rehearse via Actions → Release → Run workflow and confirm the TestPyPI job
goes green end to end. Only after that should PYPI.7 (cutting the actual
v0.5.0GitHub Release) happen.Testing
YAML validated (
yaml.safe_load);pre-commit runclean. The workflow itselfcan only be meaningfully tested by running it — see the rehearsal step above,
which should happen after merge, before tagging a release.
🤖 Generated with Claude Code