Releasing#
For maintainers. Releases are published by .github/workflows/release.yml,
triggered by pushing a v* tag. That is the only path to PyPI; there is no
separate local publishing route. You drive it from your machine with git and
gh, as below.
The version lives in one place#
src/eegprep/__init__.py:
__version__ = "0.3.0"
pyproject.toml declares dynamic = ["version"] and reads that attribute, so
there is no version string to edit in pyproject.toml.
Release from your machine#
1. Dry run first. This runs the whole pipeline except publishing, so you find problems before a tag exists:
gh workflow run release.yml --ref master -f dry_run=true
gh run watch "$(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId')"
2. Bump the version in src/eegprep/__init__.py, then commit and land it on
master:
git commit -am "release: 0.3.0"
git checkout master && git merge --no-ff develop && git push origin master
Tip
If master is strictly behind develop, git push origin develop:master
fast-forwards it without checking out master. Useful when you have local
changes — for example a modified src/eegprep/eeglab submodule — that would
block the checkout.
3. Tag and push. Pushing the tag starts the release:
git tag -a v0.3.0 -m "Release version 0.3.0"
git push origin v0.3.0
4. Watch it. Nothing reaches PyPI unless every check passes:
gh run watch "$(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId')" --exit-status
If it fails, inspect and fix:
gh run view --log-failed
5. Add a section to Changelog.
What the workflow does#
verify —
ruff check,ruff format --check,ty check, and the full test suite withEEGPREP_SKIP_MATLAB=1.build —
uv build, then checks that the tag matches__version__, that the vendored EEGLAB checkout is absent from both artifacts, that packaged data (ICLabel weights, help resources) is present, thattwine check --strictpasses, and that the built wheel installs and imports.publish — uploads to PyPI using Trusted Publishing, so no API token is stored in the repository.
github-release — creates the GitHub release with the artifacts attached.
Note
Trusted Publishing needs one-time setup on PyPI under
Manage project → Publishing → Add a new publisher: owner sccn, repository
eegprep, workflow release.yml, environment pypi.
If a tag was pushed at a bad commit#
Fix the problem, then move the tag. This is safe only while the tag published nothing; once a version is on PyPI it can never be replaced.
git push origin :refs/tags/v0.3.0 # delete the remote tag
git tag -f v0.3.0 && git push origin v0.3.0
Docker image#
CI does not build Docker images. After the tag is published, build and push the
image, which also updates the pin in tools/hpc/main.pbs:
docker login
uv run python scripts/build_docker.py
Commit the updated pin. Use --no-push to build without publishing.
Warning
PyPI uploads are immutable. A version number can never be reused, even after the release is deleted. Always dry-run first.
Note
Build with uv build, never python -m build. The repository contains a
build/ output directory that shadows the build package on sys.path, so
python -m build fails with “‘build’ is a package and cannot be directly
executed” no matter what is installed.