python-release.yml¶
Reusable workflow for automated Python package releases with testing, building, and publishing to PyPI.
๐ค AI-Generated Content
This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.
Overview¶
Complete release pipeline:
- Test - Run full CI test suite
- Build - Build wheel and source distributions
- Publish - Upload to PyPI
- Release - Create GitHub release with artifacts
Usage¶
Basic Release on Tags¶
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
permissions:
contents: write
id-token: write
uses: provide-io/ci-tooling/.github/workflows/[email protected]
with:
python-version: '3.11'
secrets:
pypi-token: ${{ secrets.PYPI_TOKEN }}
Test PyPI Release¶
jobs:
release:
uses: provide-io/ci-tooling/.github/workflows/[email protected]
with:
python-version: '3.11'
repository-url: 'https://test.pypi.org/legacy/'
secrets:
pypi-token: ${{ secrets.TEST_PYPI_TOKEN }}
Dry Run¶
jobs:
release:
uses: provide-io/ci-tooling/.github/workflows/[email protected]
with:
dry-run: true
secrets:
pypi-token: ${{ secrets.PYPI_TOKEN }}
Inputs¶
| Input | Type | Description | Default |
|---|---|---|---|
python-version |
string | Python version to use | '3.11' |
uv-version |
string | UV version to use | '0.7.8' |
test-directory |
string | Test directory | 'tests/' |
coverage-threshold |
number | Coverage threshold | 80 |
skip-tests |
boolean | Skip test job | false |
repository-url |
string | PyPI repository URL | 'https://upload.pypi.org/legacy/' |
skip-existing |
boolean | Skip if package exists | true |
create-github-release |
boolean | Create GitHub release | true |
prerelease |
boolean | Mark as prerelease | false |
dry-run |
boolean | Validate without publishing | false |
release-notes-file |
string | Path to release notes | '' |
Secrets¶
| Secret | Description | Required |
|---|---|---|
pypi-token |
PyPI API token | Yes (unless trusted publishing) |
github-token |
GitHub token | No (auto-provided) |
Outputs¶
| Output | Description |
|---|---|
release-version |
Released version |
pypi-url |
PyPI package URL |
github-release-url |
GitHub release URL |
Jobs¶
test¶
Runs complete test suite before release:
- Code quality checks
- Unit tests with coverage
- Security scanning
- Ensures package is ready for release
build¶
Builds package distributions:
- Creates wheel (
.whl) - Creates source distribution (
.tar.gz) - Validates package metadata
- Uploads artifacts
publish¶
Publishes to PyPI:
- Verifies metadata with twine
- Uploads to PyPI (or Test PyPI)
- Skips if package version exists (optional)
- Uses official PyPA publish action
release¶
Creates GitHub release:
- Creates Git tag (if not exists)
- Generates release notes
- Attaches build artifacts
- Marks as prerelease (optional)
Job Dependencies¶
test
โโโ build (depends on test)
โโโ publish (depends on build)
โโโ release (depends on publish)
All jobs run sequentially. If any job fails, subsequent jobs are skipped.
Examples¶
Complete Release Workflow¶
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
id-token: write
jobs:
release:
uses: provide-io/ci-tooling/.github/workflows/[email protected]
with:
python-version: '3.11'
coverage-threshold: 85
secrets:
pypi-token: ${{ secrets.PYPI_TOKEN }}
Manual Release Trigger¶
name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release'
required: true
type: string
jobs:
release:
uses: provide-io/ci-tooling/.github/workflows/[email protected]
with:
python-version: '3.11'
secrets:
pypi-token: ${{ secrets.PYPI_TOKEN }}
Test Then Release¶
name: CI/CD
on:
push:
branches: [main]
tags: ['v*']
jobs:
ci:
if: "!startsWith(github.ref, 'refs/tags/v')"
uses: provide-io/ci-tooling/.github/workflows/[email protected]
release:
if: startsWith(github.ref, 'refs/tags/v')
uses: provide-io/ci-tooling/.github/workflows/[email protected]
secrets:
pypi-token: ${{ secrets.PYPI_TOKEN }}
Prerelease¶
on:
push:
tags:
- 'v*-alpha*'
- 'v*-beta*'
- 'v*-rc*'
jobs:
release:
uses: provide-io/ci-tooling/.github/workflows/[email protected]
with:
prerelease: true
secrets:
pypi-token: ${{ secrets.PYPI_TOKEN }}
Skip Tests (Not Recommended)¶
jobs:
release:
uses: provide-io/ci-tooling/.github/workflows/[email protected]
with:
skip-tests: true # Use only if tests ran in previous job
secrets:
pypi-token: ${{ secrets.PYPI_TOKEN }}
PyPI Token Setup¶
Create PyPI Token¶
- Log in to PyPI
- Account Settings โ API tokens
- "Add API token"
- Name: "GitHub Actions - {repo-name}"
- Scope: Project or account-wide
- Copy token (starts with
pypi-)
Add to GitHub¶
- Repository Settings โ Secrets and variables โ Actions
- "New repository secret"
- Name:
PYPI_TOKEN - Value: Paste PyPI token
- "Add secret"
Test PyPI (Optional)¶
Same steps on Test PyPI:
- Create token
- Add as TEST_PYPI_TOKEN
- Use with repository-url: 'https://test.pypi.org/legacy/'
Trusted Publishing (Alternative)¶
PyPI supports trusted publishing without tokens:
- Configure on PyPI:
- Project Settings โ Publishing
- Add trusted publisher
- Owner: your-org
- Repository: your-repo
- Workflow: release.yml
-
Environment: release (optional)
-
Update workflow:
permissions: id-token: write # For trusted publishing contents: write jobs: release: uses: provide-io/ci-tooling/.github/workflows/[email protected] # No pypi-token needed!
Permissions¶
Required¶
With Trusted Publishing¶
Environment Protection¶
jobs:
release:
environment: production # Requires approval
uses: provide-io/ci-tooling/.github/workflows/[email protected]
Release Notes¶
Auto-Generated¶
Default behavior - generates notes with: - Version number - PyPI package link - Artifact list
Custom File¶
Workflow will read and use file content.
Inline Notes¶
Artifacts¶
python-packages¶
Contains:
- *.whl - Wheel distribution
- *.tar.gz - Source distribution
Retention: 90 days
Available for download from: - Workflow run page - GitHub release page - PyPI package page
Troubleshooting¶
Package Already Exists¶
If version already published:
Or bump version in VERSION file.
Tests Failing¶
Fix tests or skip (not recommended):
Metadata Validation Failed¶
Check pyproject.toml:
- Valid readme field
- Complete description
- Valid license identifier
Test locally:
GitHub Release Failed¶
Ensure:
- Tag exists: git push --tags
- Permissions: contents: write
- Valid token
PyPI Upload Failed¶
Check: - Valid token - Token has correct scope - Version not already published - Package name available (first release)
Version Bumping¶
Semantic Versioning¶
Follow semver.org:
# Patch (1.0.0 โ 1.0.1)
echo "1.0.1" > VERSION
# Minor (1.0.1 โ 1.1.0)
echo "1.1.0" > VERSION
# Major (1.1.0 โ 2.0.0)
echo "2.0.0" > VERSION
Create Tag¶
Automated¶
Use bump2version:
bump2version patch # Patch version
bump2version minor # Minor version
bump2version major # Major version
Dry Run Workflow¶
Test releases before publishing:
-
Create dry-run workflow (
.github/workflows/test-release.yml):name: Test Release on: pull_request: branches: [main] jobs: test-release: uses: provide-io/ci-tooling/.github/workflows/[email protected] with: dry-run: true secrets: pypi-token: ${{ secrets.PYPI_TOKEN }} -
Verify in PR: Dry run executes on PRs
- Review: Check step summary for validation results
- Merge: Actual release on tag push
Release Checklist¶
Before creating release:
- Update
VERSIONfile - Update
CHANGELOG.md(if used) - Update documentation
- Run tests locally:
pytest - Build locally:
uv build - Validate:
twine check dist/* - Commit changes
- Create and push tag
Best Practices¶
Always Test Before Release¶
Don't skip tests. They catch issues before users do.
Use Semantic Versioning¶
Follow semver conventions: - Major: Breaking changes - Minor: New features - Patch: Bug fixes
Write Release Notes¶
Document what changed: - New features - Bug fixes - Breaking changes - Upgrade notes
Test on Test PyPI First¶
For major releases:
1. Release to Test PyPI
2. Test installation: pip install --index-url https://test.pypi.org/simple/ package-name
3. Verify functionality
4. Release to production PyPI
Security¶
- Never commit tokens: Always use GitHub Secrets
- Use project-scoped tokens: Limit token access
- Enable 2FA: Require 2FA for PyPI account
- Rotate tokens regularly: Every 90 days
- Consider trusted publishing: More secure than tokens
Performance¶
Typical workflow execution:
| Job | Time | Notes |
|---|---|---|
| test | 2-4min | Full CI test suite |
| build | 30-60s | Package building |
| publish | 30-60s | PyPI upload |
| release | 30-60s | GitHub release |
| Total | 4-7min | Complete pipeline |
Next Steps¶
- python-ci.yml - CI workflow reference
- Actions - Individual actions
- Quick Start - Getting started