Releasing to PyPI

This project uses automated, tag-driven releases via GitHub Actions with a TestPyPI gate and smoke test before PyPI publish.

Workflow file:

  • .github/workflows/release.yml

Release Checklist (Quick Reference)

Stage

Check

Command / Action

Pre-flight

On main, clean working tree

git checkout main && git pull origin main && git status

Pre-flight

Version/changelog updated

Verify pyproject.toml and CHANGELOG.md

Pre-flight

Tests pass locally

pytest -q

Trigger (tag path)

Create and push tag

git tag -a vX.Y.Z -m "Release X.Y.Z" then git push origin vX.Y.Z

Trigger (manual fallback)

Run workflow manually

GitHub Actions → Release → Run workflow (target=pypi, version=X.Y.Z)

Verification

Confirm tag exists remotely

`git ls-remote –tags origin

Verification

Confirm workflow completed

Check Release workflow run in GitHub Actions

Verification

Confirm install/version

python -m pip install --upgrade grad-visitor-scheduler and python -c "import grad_visit_scheduler as gvs; print(gvs.__version__)"

Release Pipeline (Current Behavior)

Trigger options:

  • Push a version tag that matches v* (for example v0.2.0)

  • Run manually from GitHub Actions (workflow_dispatch)

Pipeline stages:

  1. Run tests (pytest -q)

  2. Build package artifacts (sdist + wheel)

  3. Validate metadata (twine check)

  4. Publish artifacts to TestPyPI

  5. Smoke install package from TestPyPI

  6. Publish the same artifacts to PyPI

One-Time Setup: Trusted Publishing

No PyPI API token is required. Publishing uses GitHub OIDC.

Configure Trusted Publisher in both:

For each index, set:

  • Owner: dowlinglab

  • Repository: grad-visit-scheduler

  • Workflow: release.yml

  • Environment: leave empty (unless you later add a GitHub Environment gate)

Manual Release Runs (workflow_dispatch)

GitHub Actions → ReleaseRun workflow

Inputs:

  • target=testpypi

    • Runs build/test/check + TestPyPI publish + smoke install

    • Stops before PyPI publish

  • target=pypi

    • Runs full pipeline including PyPI publish

  • version (optional)

    • Used to pin smoke install (e.g. 0.2.1 or v0.2.1)

    • If omitted, smoke install uses latest package visible on TestPyPI

Verifying a Published Release

PyPI install check:

python -m pip install --upgrade grad-visitor-scheduler
python -c "import grad_visit_scheduler as gvs; print(gvs.__version__)"

TestPyPI install check:

python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple grad-visitor-scheduler
python -c "import grad_visit_scheduler as gvs; print(gvs.__version__)"

Common Failure Modes

  • Trusted Publisher mismatch (invalid-publisher/auth errors):

    • Verify owner/repo/workflow filename exactly match configuration.

  • Smoke install cannot find version on TestPyPI:

    • Wait a minute and rerun; index propagation can lag.

    • Confirm version in pyproject.toml matches tag.

  • File already exists on publish:

    • That version is already uploaded; bump version and retag.

  • Tag exists but no on: push release workflow appears:

    • Confirm tag exists on remote: git ls-remote --tags origin | grep vX.Y.Z

    • Use manual workflow dispatch on main as deterministic fallback: target=pypi, version=X.Y.Z.

  • Build/test failures:

    • Release pipeline is intentionally blocked until tests/build succeed.

Notes for Future You

  • Package name on index: grad-visitor-scheduler

  • Import name in Python: grad_visit_scheduler

  • Current release trigger is tag-first; no GitHub Release object is required.