Repository navigation
Publish to PyPI on a GitHub release, after checking the tagged commit - #63
Merged
Merged
Conversation
parashardhapola
force-pushed
the
54-pca-dot-plots-and-incomplete-beta
branch
from
October 26, 2026 23:05
9202c3b to
558102d
Compare
parashardhapola
changed the base branch from
54-pca-dot-plots-and-incomplete-beta
to
master
October 26, 2026 23:10
A published release runs .github/workflows/publish.yml: it requires the commit's Tests run to have passed, builds the documentation, builds and checks both files with pinned tools, smoke-tests the wheel on Linux, Windows and macOS, and uploads by trusted publishing from the pypi environment.
parashardhapola
marked this pull request as ready for review
October 26, 2026 23:10
parashardhapola
force-pushed
the
53-release-workflow
branch
from
October 26, 2026 23:10
9948d00 to
cb0d3f3
Compare
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.
Summary
Publishing a release on GitHub now uploads CytePool to PyPI, built from the commit that the release's tag names, in the way CyteArc's release workflow does. Nothing is uploaded unless that commit's Tests run passed, the documentation builds, the wheel and the source distribution match the tag and
pyproject.tomland hold only the package, and the wheel installs and reproduces limma's tables in clean environments on Linux (Python 3.13, 3.14 and 3.15), Windows and macOS (Python 3.13). The workflow runs only when a release is published, so it can be merged before the first tag, which stays with the owner.What the owner sets up once, before the first release
A trusted publisher on PyPI. The workflow then uploads with a token that GitHub issues for each run, and no PyPI API token is stored anywhere. At https://pypi.org/manage/account/publishing/, add a pending publisher for GitHub with:
cytepoolNygenAnalyticsCytePoolpublish.ymlpypiA pending publisher does not reserve the name, so check that
cytepoolis still free on PyPI. The first upload creates the project, owned by the account that added the publisher.An environment named
pypion GitHub (Settings, Environments, New environment). Add the people who approve uploads as required reviewers. Under Deployment branches and tags, choose Selected branches and tags and add a tag rule*, so that only a run for a tag can upload. If this environment does not exist, the first run creates it without any protection.How a release is made
0.1.0, or0.2.0rc1for a release candidate, which PyPI lists as a pre-release.pypienvironment approves, it uploads the wheel and the source distribution.When a check fails, nothing is uploaded. Fix the cause on master, delete the release and its tag, and publish the release again with the fixed commit as the target. Re-running the failed workflow tests the same commit again, which helps only when the failure came from outside the commit, such as a network error. The installation page of the documentation now has these steps in a "Make a release" section.
Changes
.github/workflows/publish.yml: the release workflow, triggered by a published release, with five jobs:tests: the latest run oftests.ymlfor the commit, started by a push or by hand, must have passed;docs: runsdocs.ymlon the commit, aspages.ymldoes;build: requires the tag to still name the commit, builds both files from a clean checkout with pinned tools and uv 0.13.5, checks them withtests/validate_release_artifacts.py, and storesdist/;smoke: runstests/smoke_test_wheel.pyon Linux with Python 3.13, 3.14 and 3.15, and on Windows (windows-2025) and macOS (macos-26) with Python 3.13;publish: after the four others, uploads by trusted publishing from thepypienvironment..github/build-constraints.txt: pins the build backend and everything it installs: setuptools 84.2.1, setuptools-scm 10.3.4, vcs-versioning 2.6.0, wheel 0.48.0 and packaging 26.3.MANIFEST.in: keeps the tests, the documentation, the validation scripts and the repository's tooling out of the source distribution.tests/validate_release_artifacts.py: checks the wheel and the source distribution against the tag and the source tree, prints every problem it finds and exits with status 1.tests/smoke_test_wheel.py: installs the wheel in a clean environment and runs a small analysis twice, the second time from the numba cache.tests/test_release_artifacts.py: 20 tests on files built from this tree.docs/source/installation.md: the "Make a release" section for maintainers.pyproject.toml: a Documentation link (docs.nygen.io/CytePool) for the PyPI page.No public function or class of the package changes.
Design notes
testsjob lists the runs oftests.ymlwhose head SHA is the release commit and takes the latest one started by a push or by hand. A pull request's run is left out, because it tests the merge of its branch into master while GitHub files it under the branch's head SHA. Running Tests by hand on the tag gives a commit that was never pushed to master a run of its own. The latest run must have completed successfully, so a later failed or unfinished run blocks the release even after an earlier pass.no-local-version-strictof vcs-versioning, which drops the local part and stops the build when the checkout has changes. The build step also checksgit statusbefore and after the build, and an earlier step checks that the tag still names the commit the release was published for, as in CyteArc's workflow. One case can still differ: when two version tags name the same commit (a release candidate promoted without a new commit),git describemay report the other tag, depending on which of them are annotated. The artifact check then refuses the release, so a wrong version is never uploaded; a new commit for the final release avoids the case.uv buildbuilds the source distribution first and the wheel from it, so the published wheel is what a build from the published source distribution gives.pyproject.toml; every module ofcytepool/, byte for byte. The wheel may hold nothing else but its.dist-infofiles, and the source distribution nothing else butpyproject.toml,MANIFEST.in, the README, the licence files and what setuptools writes (PKG-INFO,setup.cfg,cytepool.egg-info/);cytepool/_version.pymust be in both and name the version. Any other file is reported by name. The check also requires the requirements without an extra to be numpy and numba alone, the package's rule, and the wheel to bepy3-none-anywithRoot-Is-Purelib: true.MANIFEST.in, setuptools-scm puts every tracked file in it: 944 files oftests/(mostly fixtures),docs/,validation/,.github/,uv.lockand the repository's tooling files, 7.4 MB in all. It now holds the 77 modules,pyproject.toml,MANIFEST.in, the README and the two licence files: 0.7 MB, and enough to build the wheel.uv venvanduv pip installof the wheel alone, so numpy and numba resolve as they do for a user. The analysis runs with that interpreter in isolated mode from a scratch folder. It checks that cytepool is imported from the environment and reports the wheel's version, poolstests/data/differential_expression/cells.npzwithfrom_cells, builds the paired design oftests/test_differential_expression.py, runslimma_voom, and compares the three contrasts of the T cells with limma's tables inlimma.npzto 1e-6. The tests hold these values to 1e-10 on Linux; other platforms' math libraries may round differently, and a broken build differs by far more. The analysis runs twice with aNUMBA_CACHE_DIRof its own: the first run must compile the routines (85 of them) and load none, the second must load them from the cache and compile none. This shows on each platform that the cache ofcytepool._jitworks for an installed wheel; a second process then starts in under a second instead of compiling for about 30 seconds.ubuntu-26.04as in the other workflows,windows-2025, andmacos-26(Apple silicon). The upload usespypa/gh-action-pypi-publish@release/v1, as CyteArc does; it also runstwine checkand attaches attestations to the files.validation/,docs/,LICENSEand others) will not resolve; absolute links to GitHub would.Validation
No statistical method changes, so there is no comparison with a reference tool; the release path was checked on this machine instead. Windows and macOS run only on GitHub.
uv build --build-constraints .github/build-constraints.txtin a clean clone of master, with the strict local schemeDirtyWorkingTreeErroruv run --no-project --with packaging==26.3 python tests/validate_release_artifacts.py --release-tag 0.1.dev54 dist/*--release-tag v0.1.0cytepool/_version.pyand the Version field of each fileMANIFEST.in.github/tovalidation/uv run --no-project --python 3.13 python tests/smoke_test_wheel.py dist/cytepool-*.whlghubuntu-26.04, the label every workflow here usesThe clean environments held numpy 2.5.4, numba 0.68.0 and llvmlite 0.50.0, with uv 0.13.5.
Tests
tests/test_release_artifacts.pyhas 20 tests. A module fixture builds the wheel and the source distribution once, with the workflow's command and pinned tools, into a temporary folder; with a warm uv cache this takes 2 to 4 seconds, the slowest setup of the suite. The tests compare against expectations written by hand, not taken from the validator:tests/,docs/orvalidation/), withcytepool/_version.pyin both;The fixture skips, saying why, when uv or git is missing or the tree is not a git checkout. One more test runs the smoke test's analysis on the source tree, so that a change that breaks it fails on its pull request rather than at a release.
Runtime of the module: 3 seconds warm; 37 seconds with a cold numba cache, nearly all of it compiling the routines of
limma_voom, which the suite compiles for its own tests anyway. The whole suite: 4736 passed and 18 skipped (zstd needs Python 3.14) in 34 seconds. The new tests also pass withOPENBLAS_CORETYPE=Sandybridge NUMBA_CPU_NAME=genericand a fresh numba cache. ruff, ruff format, mypy,make -C docs apiandmake -C docs htmlpass.