CI/CD and Releases¶
This guide explains the continuous integration and deployment setup in this Python package template.
Overview¶
The template includes:
- Automated testing on every push and pull request (3.12, 3.13, 3.14)
- Code quality checks (linting, formatting, CodeQL security scanning)
- Automated weekly releases with semantic versioning and changelog generation
- Draft releases to preview upcoming changes before publishing
- Documentation deployment to GitHub Pages
- Dependency updates via Dependabot
- Package publishing (optional, requires setup)
Quick Start¶
- No setup required - CI runs automatically on pushes and pull requests
- Releases happen weekly on the Tuesday of each week (automated)
- Optional: Enable PyPI publishing - See Publishing section
Workflows¶
CI (.github/workflows/CI.yml)¶
Triggers: Every push, pull request, or manual trigger
- Tests across Python 3.12, 3.13, 3.14
- Runs pytest with coverage reporting
- Uploads coverage to Codecov
- Security scanning with CodeQL
Status checks to require: test (3.12), test (3.13), test (3.14)
Tag (.github/workflows/create_tag.yml)¶
Triggers: Weekly (Tuesday at UTC midnight) or manual trigger
- Runs CI workflow to ensure tests pass
- Automatically bumps version using semantic versioning
- Creates and pushes git tag (e.g.,
v1.2.3) - Calls Release workflow with the new tag
Release (.github/workflows/release.yml)¶
Triggers: When called by Tag workflow, or manually with a tag
- Checks out the specified tag
- Generates changelog from conventional commits
- Deletes old draft release
- Creates GitHub release with changelog
- Optionally calls Publish workflow (requires setup)
Draft Release (.github/workflows/draft_release.yml)¶
Triggers: Every push to main
- Generates changelog for unreleased changes
- Creates/updates
next-releasedraft on GitHub Releases - Auto-deleted when a real release is created
- Helps preview what's in the next release
Documentation (.github/workflows/documentation.yml)¶
Triggers: Every push to main or manual trigger
- Builds documentation with Zensical
- Deploys to GitHub Pages
- Uses pip caching for faster builds
Publish (.github/workflows/publish.yml & publish_testpypi.yml)¶
Triggers: Manual via workflow_dispatch or called by Release workflow
- Builds Python distribution packages
- Publishes to PyPI or TestPyPI
- Requires setup (see Publishing)
- Enabled when environments are configured (requires
pypiortestpypienvironments)
Release Process¶
Setup: Configure Your Repository URL¶
Before the first release, update cliff.toml with your repository information:
- Open
cliff.toml - Replace
https://github.com/isaac-cf-wong/python-package-template(line 61) with your actual GitHub repository (e.g.,octocat/hello-world) - Commit and push this change
This is used by git-cliff to generate links in the changelog (commits,
comparisons, etc.).
Conventional Commits¶
Format commits to trigger automatic changelog generation:
feat: add new feature # Triggers minor version bump
fix: fix a bug # Triggers patch version bump
feat!: breaking change # Triggers major version bump
docs: update documentation # No version bump
chore: update dependencies # No version bump
Learn more about Conventional Commits
Automatic Release (Weekly)¶
The Tag workflow runs automatically on Tuesday of every week at midnight UTC:
- CI passes ✅
- Version is bumped (e.g., 1.0.0 → 1.0.1)
- Git tag is created and pushed
- Release workflow creates a GitHub Release
- Changelog is auto-generated from commits
No action required - just use conventional commits.
Manual Release¶
Trigger a release anytime:
- Go to your repository → Actions tab
- Click the Tag workflow
- Click Run workflow → Confirm
Or manually specify a tag for Release workflow:
- Go to Actions → Release workflow
- Click Run workflow
- Enter tag name (e.g.,
v1.2.3) - Click Run workflow
Verify Release¶
After the workflow completes:
- Actions tab - Workflow should show ✅
- Releases page - New release should appear with auto-generated changelog
- GitHub Pages - Docs should be updated (if configured)
Publishing¶
Setup PyPI Publishing (Optional)¶
Publishing is disabled by default — it only runs when you create the required environments.
To Enable Publishing¶
- Go to your repository → Settings → Environments
- Create an environment named
pypi(for PyPI) and/ortestpypi(for TestPyPI) - For each environment, optionally add deployment rules:
- Deployment branches: Select
mainorSelected branches - Environment secrets (optional): Only needed if not using trusted publishing
- Deployment branches: Select
Manual Publish¶
Publish manually when ready:
- Go to Actions → Publish workflow
- Click Run workflow → Enter tag name
- Select environment from the dropdown
- Click Run workflow
Auto-Publish on Release¶
The Release workflow is already configured to call the Publish workflow. Once
you create the pypi environment, releases will automatically publish to PyPI.
For Production Packages
This template includes continue-on-error: true in the publish workflows
to handle the case where environments are not created.
When you fork this template for production use, remove these lines from:
.github/workflows/publish.yml(line 78).github/workflows/publish_testpypi.yml(line 78)
This ensures your package will fail loudly if publishing encounters errors, rather than silently skipping the publish step.
Configure Trusted Publishing (Recommended)¶
Use PyPI trusted publishing instead of API tokens:
- Go to PyPI or TestPyPI
- Go to your project → Settings → Publishing
- Add GitHub as trusted publisher:
- Owner:
your-username - Repository:
your-repo-name - Workflow:
create_tag.yml(for automatic releases) andpublish.yml(for manual publishes) - Environment:
pypi(ortestpypifor TestPyPI)
- Owner:
No secrets or tokens needed!
Customization¶
Change Release Schedule¶
Edit .github/workflows/create_tag.yml:
on:
schedule:
- cron: '0 0 1 * *' # Change to your preferred time
Cron format: minute hour day month weekday
"0 0 1 * *"= 1st of month, midnight UTC"0 0 * * 0"= Every Sunday, midnight UTC"0 12 * * *"= Every day at noon UTC
Require Additional Status Checks¶
Edit .github/workflows/CI.yml to add custom checks, then add them to branch
protection:
- Go to Settings → Branches → main
- Under "Require status checks to pass before merging"
- Add your new check names
Change Python Versions¶
Edit .github/workflows/CI.yml:
matrix:
python-version: ['3.10', '3.11', '3.12'] # Modify as needed
Disable CodeQL (Optional)¶
If security scanning isn't needed, remove the codeql job from
.github/workflows/CI.yml.
Troubleshooting¶
CI workflow fails¶
- Check test logs in Actions tab
- Verify dependencies in
pyproject.toml - Ensure tests pass locally:
pytest
Release workflow fails¶
- Check commits use conventional commit format
- Verify
cliff.tomlchangelog configuration - See Actions logs for detailed error
Version not bumped correctly¶
- Ensure commits start with
feat:,fix:,feat!:, etc. - Check Conventional Commits format
Draft release not updating¶
- Check workflow concurrency settings in
.github/workflows/draft_release.yml
PyPI publishing fails¶
- Verify trusted publishing is configured on PyPI/TestPyPI
- Check that the environment name matches your workflow
- See Actions logs for authentication errors
CodeQL configuration error¶
- Remove
security-events: writepermission from workflows without CodeQL scanning - Only
.github/workflows/CI.ymlshould have this permission
Best Practices¶
- Use conventional commits - Ensures correct version bumping and meaningful changelogs
- Review draft releases - Check "next-release" to preview what will be published
- Keep commits atomic - One logical change per commit
- Require status checks - Prevent merging broken code to main
- Use branch protection - Require pull requests and passing checks before merging