Python Bumpversion Release — Deep Dive
The Parse/Serialize System
bump-my-version’s power comes from its regex-based version parsing and multi-format serialization. Understanding this system unlocks custom versioning schemes.
How It Works
- Parse: A regex extracts named groups from the current version string
- Bump: The tool increments the specified group and resets downstream groups
- Serialize: The new version is formatted using one of the serialize templates
[tool.bumpversion]
current_version = "2.1.0-beta.3"
parse = '''(?x)
(?P<major>\d+)\.
(?P<minor>\d+)\.
(?P<patch>\d+)
(-(?P<stage>alpha|beta|rc)\.(?P<stage_n>\d+))?
'''
serialize = [
"{major}.{minor}.{patch}-{stage}.{stage_n}",
"{major}.{minor}.{patch}"
]
The (?x) flag enables verbose regex mode. Multiple serialize formats are tried in order — the first one that produces a complete string (no empty groups) wins.
Reset Rules
When you bump minor, all groups to its right reset:
2.1.0-beta.3→2.2.0(patch resets to 0, pre-release disappears)
When you bump stage_n:
2.1.0-beta.3→2.1.0-beta.4(only the stage number increments)
When you bump stage:
2.1.0-alpha.3→2.1.0-beta.1(stage advances, stage_n resets)
Calendar Versioning (CalVer)
Some projects use date-based versions:
[tool.bumpversion]
current_version = "2026.03.1"
parse = '(?P<year>\d{4})\.(?P<month>\d{2})\.(?P<release>\d+)'
serialize = ["{year}.{month}.{release}"]
[tool.bumpversion.parts.year]
calver_format = "{YYYY}"
[tool.bumpversion.parts.month]
calver_format = "{0M}"
Bumping release increments the build number within the current month. Bumping month advances to the current month and resets the release number.
Advanced File Patterns
Regex Search Patterns
For files where the version string isn’t in a simple format:
[[tool.bumpversion.files]]
filename = "Chart.yaml"
search = "appVersion: {current_version}"
replace = "appVersion: {new_version}"
[[tool.bumpversion.files]]
filename = "README.md"
regex = true
search = 'version-v[\d.]+(-\w+\.\d+)?-blue'
replace = "version-v{new_version}-blue"
The regex mode is essential for version badges in README files, where the version is embedded in a URL pattern.
Glob Patterns
Update version strings across multiple files:
[[tool.bumpversion.files]]
glob = "packages/*/pyproject.toml"
search = 'version = "{current_version}"'
replace = 'version = "{new_version}"'
This is invaluable for monorepos where multiple packages share a synchronized version.
CI/CD Integration
GitHub Actions Release Pipeline
# .github/workflows/release.yml
name: Release
on:
workflow_dispatch:
inputs:
bump:
description: "Version bump type"
required: true
type: choice
options:
- patch
- minor
- major
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_PAT }}
- uses: actions/setup-python@v5
- run: pip install bump-my-version build twine
- name: Configure Git
run: |
git config user.name "release-bot"
git config user.email "release@example.com"
- name: Bump version
run: |
bump-my-version bump ${{ inputs.bump }}
echo "VERSION=$(bump-my-version show current_version)" >> "$GITHUB_ENV"
- name: Push changes and tag
run: git push origin main --tags
- name: Build and publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
python -m build
twine upload dist/*
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
gh release create "v$VERSION" \
--title "v$VERSION" \
--generate-notes \
dist/*
Using workflow_dispatch gives the release manager a button in the GitHub UI to trigger releases with a chosen bump type.
Automated Bump in Merge Pipelines
For teams that want automated bumps on every merge to main:
on:
push:
branches: [main]
jobs:
auto-bump:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'bump version')"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_PAT }}
- run: pip install bump-my-version
- run: |
git config user.name "release-bot"
git config user.email "release@example.com"
bump-my-version bump patch
git push origin main --tags
The if condition prevents infinite loops — the bump commit itself won’t trigger another bump.
Hooks: Pre and Post Bump
Run scripts before or after the version bump:
[tool.bumpversion]
pre_commit_hooks = ["python scripts/validate_changelog.py"]
post_commit_hooks = ["python scripts/notify_slack.py {new_version}"]
Use pre-commit hooks to validate that the changelog has an entry for the new version, or that all tests pass. Post-commit hooks can trigger notifications, start deployment pipelines, or update external trackers.
Migration from Legacy Tools
From bump2version
bump-my-version reads .bumpversion.cfg and setup.cfg configurations. The migration path:
- Install
bump-my-version - Run
bump-my-version show --configto display the interpreted configuration - Move the config to
pyproject.toml - Remove the old
.bumpversion.cfg
From Manual Versioning
For projects that have been versioning by hand, start by auditing every file that contains a version string:
grep -rn "1\.3\.2" --include="*.py" --include="*.toml" --include="*.yaml" .
Add each match to the [[tool.bumpversion.files]] configuration. Then run a dry-run bump to verify:
bump-my-version bump patch --dry-run --verbose
Integration with Other Tools
With Commitizen
Some teams use both: Commitizen for commit message enforcement and changelog generation, and bump-my-version for the actual version bump (because of its superior file pattern handling):
# In CI:
cz changelog --unreleased-version "$(bump-my-version show new_version --increment patch)"
bump-my-version bump patch
With towncrier
Generate the changelog with towncrier, then bump with bump-my-version:
towncrier build --version "$(bump-my-version show new_version --increment minor)" --yes
bump-my-version bump minor
Troubleshooting
“Version not found in file”: The search string must match exactly, including quotes and whitespace. Use --verbose --dry-run to see what bump-my-version is looking for versus what’s in the file.
Tag already exists: If a previous bump was partially applied (files changed but tag not created), you may need to git tag v1.4.0 manually or reset and re-run.
Dirty working tree: bump-my-version refuses to bump if there are uncommitted changes. Either commit or stash them first. Use --allow-dirty in development (never in CI).
One thing to remember: bump-my-version is the Swiss Army knife of version management — flexible enough for any versioning scheme, precise enough for any file format, and automatable enough for any CI pipeline.
See Also
- Python Black Formatter Understand Black Formatter through a practical analogy so your Python decisions become faster and clearer.
- Python Changelog Automation Let your git commits write the changelog so you never forget what changed in a release.
- Python Ci Cd Python Understand CI CD Python through a practical analogy so your Python decisions become faster and clearer.
- Python Cicd Pipelines Use Python CI/CD pipelines to remove setup chaos so Python projects stay predictable for every teammate.
- Python Commitizen Conventional Commits Write git commit messages that follow a pattern so tools can automatically version your software and write your changelog.