PyPI Publishing Setup¶
This monorepo publishes 8 separate packages to PyPI:
Individual packages:
1. canvod-readers - GNSS data readers
2. canvod-auxiliary - Auxiliary data handling
3. canvod-grids - Grid operations
4. canvod-store - Storage backends
5. canvod-utils - Utility functions
6. canvod-viz - Visualization tools
7. canvod-vod - VOD calculation
Umbrella package:
8. canvodpy - Depends on all 7 above
User experience:
pip install canvodpy # Gets all 8 packages automatically
-
Phase 1 — TestPyPI (Done)
Account created · First manual upload · OIDC trusted publishers configured. Beta/alpha releases publish here automatically.
-
Phase 2 — Real PyPI (Active)
Production account · Environment protection · Stable releases only.
pip install canvodpyserves users from here.
Quick Commands¶
# Build all 8 packages locally
just build-all
# Manual publish to TestPyPI (requires credentials)
just publish-testpypi
# Manual publish to PyPI (requires credentials)
just publish-pypi
# Automated publish (beta) - triggers workflow
git tag v0.1.0-beta.1 && git push --tags
# Automated publish (production) - triggers workflow
git tag v0.1.0 && git push --tags
Phase 1: TestPyPI Setup (Completed)¶
Step 1: Create TestPyPI Account (Done)¶
Already done! Account created at https://test.pypi.org
Step 2: Manual First Publish (Done)¶
Already completed with twine upload --repository testpypi dist/*
All 8 packages published: - https://test.pypi.org/project/canvod-readers/ - https://test.pypi.org/project/canvod-auxiliary/ - https://test.pypi.org/project/canvod-grids/ - https://test.pypi.org/project/canvod-store/ - https://test.pypi.org/project/canvod-utils/ - https://test.pypi.org/project/canvod-viz/ - https://test.pypi.org/project/canvod-vod/ - https://test.pypi.org/project/canvodpy/
Step 3: Set Up OIDC Automation (Done)¶
3.1: Create GitHub Environment
- Go to: https://github.com/nfb2021/canvodpy/settings/environments
- Click "New environment"
- Name:
testpypi - Save
3.2: Register Trusted Publishers (FOR EACH OF 8 PACKAGES)
For each package, go to its publishing settings: - https://test.pypi.org/manage/project/canvod-readers/settings/publishing/ - https://test.pypi.org/manage/project/canvod-auxiliary/settings/publishing/ - https://test.pypi.org/manage/project/canvod-grids/settings/publishing/ - https://test.pypi.org/manage/project/canvod-store/settings/publishing/ - https://test.pypi.org/manage/project/canvod-utils/settings/publishing/ - https://test.pypi.org/manage/project/canvod-viz/settings/publishing/ - https://test.pypi.org/manage/project/canvod-vod/settings/publishing/ - https://test.pypi.org/manage/project/canvodpy/settings/publishing/
On each page:
1. Click "Add a new publisher"
2. Select "GitHub"
3. Fill in:
- Owner: nfb2021
- Repository: canvodpy
- Workflow: publish_testpypi.yml
- Environment: testpypi
4. Click "Add"
3.3: Test OIDC Publishing
git tag v0.1.0-beta.1 -m "Test OIDC"
git push --tags
cd /path/to/canvodpy
uv build
Upload manually (first time only):
uvx twine upload --repository testpypi dist/*
When prompted:
username: your_testpypi_username
password: your_testpypi_password
Success looks like:
Uploading canvodpy-0.1.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Verify it's there: - Go to: https://test.pypi.org/project/canvodpy/
Step 3: Configure OIDC on TestPyPI¶
- Go to project settings:
-
https://test.pypi.org/manage/project/canvodpy/settings/publishing/
-
Scroll to "Publishing"
-
Click "Add a new publisher"
-
Fill in the form:
PyPI project name: canvodpy Owner: nfb2021 Repository name: canvodpy Workflow name: publish_testpypi.yml Environment name: test-release -
Click "Add"
-
You should see:
✓ Trusted publisher added Publisher: - Repository: nfb2021/canvodpy - Workflow: publish_testpypi.yml - Environment: test-release
Step 4: Create GitHub Environment¶
- Go to your repo:
-
https://github.com/nfb2021/canvodpy
-
Settings → Environments
-
New environment:
- Name:
test-release -
Click "Configure environment"
-
Add protection rules (optional):
- Required reviewers: Add yourself
-
Wait timer: 0 minutes (it's just testing)
-
Save
Step 5: Create TestPyPI Workflow¶
Create: .github/workflows/publish_testpypi.yml
name: Publish to TestPyPI
on:
release:
types: [published]
# Manual trigger for testing
workflow_dispatch:
permissions:
id-token: write # REQUIRED for OIDC
contents: read
jobs:
publish-testpypi:
name: Upload to TestPyPI
runs-on: ubuntu-latest
environment: test-release
# Only run if tag contains 'beta' or 'alpha' (for testing)
if: contains(github.ref, 'beta') || contains(github.ref, 'alpha')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.27"
- name: Set up Python
run: uv python install 3.14
- name: Build package
run: uv build
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
print-hash: true
Step 6: Test the Workflow¶
Create a beta release:
just release 0.1.0-beta.1
git push origin main
git push origin --tags
Publish the draft release:
1. Go to: https://github.com/nfb2021/canvodpy/releases
2. Find draft for v0.1.0-beta.1
3. Click "Publish release"
Watch the workflow: 1. Go to: https://github.com/nfb2021/canvodpy/actions 2. See "Publish to TestPyPI" running 3. Wait ~2-3 minutes 4. Should say "✓ Complete"
Verify upload: - https://test.pypi.org/project/canvodpy/ - Should see version 0.1.0b1
Test installation:
# Create test environment
uv venv test-env
source test-env/bin/activate # or test-env\Scripts\activate on Windows
# Install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ canvodpy
# Test it works
python -c "import canvodpy; print(canvodpy.__version__)"
# Should print: 0.1.0b1
# Clean up
deactivate
rm -rf test-env
If it works, you are ready for real PyPI.
Phase 2: Real PyPI Setup¶
Step 7: Create Real PyPI Account¶
- Go to: https://pypi.org/account/register/
- Fill in details (can use same email as TestPyPI)
- Verify email
- Enable 2FA (PyPI requires it for trusted publishers!)
Step 8: Reserve Package Name on PyPI¶
Package names are permanent
Once a package is uploaded to PyPI, the name is reserved forever. You cannot delete it or reuse its version numbers. Double-check everything before uploading.
# Build fresh
cd /path/to/canvodpy
rm -rf dist/
uv build
# Upload to REAL PyPI
uvx twine upload dist/*
When prompted:
username: your_pypi_username
password: your_pypi_password
Verify: - https://pypi.org/project/canvodpy/
Step 9: Configure OIDC on Real PyPI¶
Exactly same as TestPyPI:
- Go to:
-
https://pypi.org/manage/project/canvodpy/settings/publishing/
-
Add publisher:
PyPI project name: canvodpy Owner: nfb2021 Repository name: canvodpy Workflow name: publish_pypi.yml Environment name: release -
Click "Add"
Step 10: Create Production GitHub Environment¶
Enable 2FA first
PyPI requires 2FA to add trusted publishers. Enable it at pypi.org/manage/account/ before proceeding.
- Settings → Environments → New
- Name:
release - Protection rules (IMPORTANT!):
- Required reviewers: Add all maintainers
- Wait timer: 5 minutes (think before publishing)
- Save
Step 11: Create Production Workflow¶
Create: .github/workflows/publish_pypi.yml
name: Publish to PyPI
on:
release:
types: [published]
permissions:
id-token: write
contents: read
jobs:
publish-pypi:
name: Upload to PyPI
runs-on: ubuntu-latest
environment: release
# Skip beta/alpha releases for production PyPI
if: "!contains(github.ref, 'beta') && !contains(github.ref, 'alpha')"
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.27"
- name: Set up Python
run: uv python install 3.14
- name: Build package
run: uv build
- name: Check package
run: |
uvx twine check dist/*
ls -lh dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
print-hash: true
Step 12: First Production Release¶
When ready for v0.1.0:
# Create release
just release 0.1.0
git push origin main
git push origin --tags
Publish: 1. Go to releases 2. Find draft v0.1.0 3. Review carefully! 4. Click "Publish release" 5. Approve the workflow when prompted 6. Wait 5 minutes (your wait timer) 7. Workflow runs 8. Package published to PyPI!
Verify:
pip install canvodpy
python -c "import canvodpy; print(canvodpy.__version__)"
# Should print: 0.1.0
Your package is now on PyPI.
Summary¶
just release 0.2.0-beta.1
git push --tags
# → publishes to TestPyPI automatically
just release 0.2.0
git push --tags
# → publishes to real PyPI (after environment approval)
pip install canvodpy # latest stable
pip install canvodpy==0.2.0 # specific version
Best Practices¶
Test on TestPyPI first
Use -beta.X or -alpha.X version suffixes to publish to TestPyPI.
Verify pip install --index-url https://test.pypi.org/simple/ canvodpy works
before creating a stable release.
Version numbers are permanent
PyPI does not allow deleting packages or reusing version numbers. Bump the version and re-release if you need to fix a bad upload.
Environment protection
The release environment has required reviewers and a 5-minute wait timer —
this prevents accidental production releases.
Troubleshooting¶
Publisher not configured OIDC error
- Verify environment name in workflow matches PyPI config exactly
- Verify workflow filename matches exactly
- Verify repository owner/name are correct
- Confirm 2FA is enabled on your PyPI account
Package already exists
Cannot upload the same version twice. Bump and retry:
just bump patch # 0.1.0 → 0.1.1
Build failed
Test locally first:
uv build
ls dist/ # should contain .whl and .tar.gz
uvx twine check dist/*
Environment approval stuck
Check the release environment's protection rules at
Settings → Environments → release. Approve pending deployments manually.
Next Steps¶
- Test with beta release on TestPyPI
- First production release v0.1.0
- Set up Zenodo for DOI (Zenodo Setup)