PyPI Release Runbook

This runbook defines the phase-18 release path for FEMIC:

  1. Package checks and deterministic wheel verification.

  2. TestPyPI publication and install smoke.

  3. Production PyPI publication using the exact same artifact set.

  4. Release traceability updates in roadmap/changelog.

Pre-release prerequisites

  • A clean git working tree.

  • Passing quality gates:

    • ruff format src tests

    • ruff check src tests

    • mypy src

    • pytest

    • pre-commit run --all-files

    • sphinx-build -b html docs _build/html -W

  • Packaging tools installed:

    python -m pip install --upgrade build twine
    

Packaging and deterministic wheel checks

Run the project helper:

scripts/release_package_checks.sh

The helper enforces:

  • deterministic build epoch (SOURCE_DATE_EPOCH),

  • python -m build,

  • twine check dist/*,

  • wheel install/init smoke,

  • wheel reproducibility across consecutive builds.

Current known limitation

The wheel is reproducible when SOURCE_DATE_EPOCH is fixed. The sdist archive may still vary byte-wise across repeated local builds due to upstream setuptools archive timestamp behavior. Release flow therefore treats the wheel as the deterministic artifact and uses the same built dist/* files for TestPyPI and PyPI publication.

TestPyPI publication and smoke

Preferred path: token-free trusted publishing (OIDC).

Before first TestPyPI publish:

  1. Ensure GitHub environment testpypi exists in UBC-FRESH/femic.

  2. In TestPyPI, open account-level publishing settings: https://test.pypi.org/manage/account/publishing/.

  3. Add a pending publisher: - project name: femic, - owner/repo: UBC-FRESH/femic, - workflow: publish-testpypi.yml, - environment: testpypi.

  4. Trigger GitHub workflow publish-testpypi on main.

Notes:

  • If there is no Add project button under /manage/projects, this account-level pending-publisher flow is the correct entry point.

  • First successful OIDC publish will create the TestPyPI project and attach the publisher.

Token fallback (not preferred)

If trusted publishing is temporarily unavailable, upload with token:

export TWINE_USERNAME=__token__
export TWINE_PASSWORD="$TEST_PYPI_API_TOKEN"
python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*

Install smoke in a clean environment (after publish):

python -m venv /tmp/femic-testpypi-smoke
/tmp/femic-testpypi-smoke/bin/pip install --upgrade pip
/tmp/femic-testpypi-smoke/bin/pip install \
  --index-url https://test.pypi.org/simple \
  --extra-index-url https://pypi.org/simple \
  femic==0.1.0
/tmp/femic-testpypi-smoke/bin/femic --help

Production PyPI publication

Preferred path: token-free trusted publishing (OIDC).

Before first production publish:

  1. PyPI project: femic.

  2. Publisher type: GitHub Actions.

  3. Repository owner/name: UBC-FRESH/femic.

  4. Workflow filename: publish-pypi.yml.

  5. Environment name: pypi.

Publish using workflow publish-pypi after TestPyPI validation passes.

Token fallback (not preferred):

export TWINE_USERNAME=__token__
export TWINE_PASSWORD="$PYPI_API_TOKEN"
python -m twine upload dist/*

Post-release traceability checklist

After successful PyPI publication:

  1. Create and push the matching git tag (for example v0.1.0).

  2. Record artifact hashes from sha256sum dist/* in CHANGE_LOG.md.

  3. Mark completed phase-18 checklist items in ROADMAP.md and update any linked planning note with validation outcomes.

  4. Keep install instructions in README.md aligned with the published version.

Trusted publishing troubleshooting

If GitHub Actions fails with invalid-publisher during publish:

  • confirm the workflow filename and environment name exactly match the trusted-publisher entry on TestPyPI/PyPI,

  • confirm repository owner/name is UBC-FRESH/femic,

  • confirm the publish job runs from refs/heads/main (or an allowed ref),

  • re-run the workflow after saving trusted-publisher settings.