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.whl

  • dist/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.