PyPI Release Workflow
This workflow describes how to publish a Buffalo Wings release to PyPI. It is normally used as part of the main Release Workflow, but it can also be followed separately when package publication needs to be handled manually.
Goal
The goal is to build the release artifacts, validate them, upload them to TestPyPI or PyPI, and confirm that the published package installs correctly.
Prerequisites
Before starting, make sure:
the repository is in a releasable state,
the version has already been set to the intended release version,
the full project checks pass,
you have the credentials needed to upload to TestPyPI or PyPI,
and you understand whether this publication is a dry run on TestPyPI or the real PyPI release.
1. Confirm The Repository State
Make sure the repository is clean before building artifacts:
git status --porcelain
If this prints anything, resolve it before continuing.
Read the current version from pyproject.toml:
VERSION="$(uv run python - <<'PY'
import tomllib
with open("pyproject.toml", "rb") as f:
print(tomllib.load(f)["project"]["version"])
PY
)"
VERSION_TAG="v${VERSION}"
echo "Preparing to publish ${VERSION_TAG}"
2. Remove Old Build Artifacts
Remove any previous distribution artifacts so the release is built from a clean state:
rm -fr dist/ build/ *.egg-info
3. Build The Distribution Artifacts
Build the wheel and source distribution:
uv run python -m build
This should produce files like:
dist/buffalowings-${VERSION}-py3-none-any.whldist/buffalowings-${VERSION}.tar.gz
4. Validate The Built Artifacts
Check the built artifacts with twine:
uv run twine check dist/*
If this fails, inspect the generated wheel metadata before uploading anything:
python - <<'PY'
import glob
import zipfile
whl = glob.glob("dist/*.whl")[0]
with zipfile.ZipFile(whl) as z:
meta = [n for n in z.namelist() if n.endswith(".dist-info/METADATA")][0]
print(z.read(meta).decode("utf-8", errors="replace")[:400])
PY
5. Upload The Release
Do not put PyPI credentials directly into shell history or commit them to the repository.
Use secure shell variables, password manager integration, or a configured ~/.pypirc file.
5.1. Upload To TestPyPI First
Use TestPyPI when you want to confirm that the package metadata and installation flow are correct before publishing the real release:
uv run twine upload --repository testpypi dist/*
5.2. Upload To PyPI
Once the package has been validated and you are ready for the real release, upload it to PyPI:
uv run twine upload dist/*
6. Verify Installation
After publishing, create a clean virtual environment and verify that the package installs and imports correctly.
6.1. Verify A PyPI Release
python -m venv /tmp/pypi-venv
source /tmp/pypi-venv/bin/activate
pip install "BuffaloWings==${VERSION}"
python -c "import buffalo_wings; print('import OK')"
6.2. Verify A TestPyPI Release
python -m venv /tmp/testpypi-venv
source /tmp/testpypi-venv/bin/activate
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple "BuffaloWings==${VERSION}"
python -c "import buffalo_wings; print('import OK')"
If the project later adds a real command-line interface, this step should also verify the installed CLI entry point.
7. Handle Publication Errors
If a package is uploaded to PyPI with a problem, you cannot overwrite the same version. Fix the issue, bump the version, rebuild the artifacts, and publish a new release.