Automate Semantic Versioning with Conventional Commits in Python
Automatically bump a semantic version based on conventional commit messages (feat, fix, BREAKING CHANGE) and write the new version to a file.
Python code
42 linesimport re
from pathlib import Path
def get_next_version(current: str, commit_messages: list[str]) -> str:
"""Return the next semantic version based on conventional commit messages."""
major, minor, patch = map(int, current.split("."))
if any(msg.startswith("BREAKING CHANGE") for msg in commit_messages):
return f"{major + 1}.0.0"
if any(msg.startswith("feat") for msg in commit_messages):
return f"{major}.{minor + 1}.0"
if any(msg.startswith("fix") for msg in commit_messages):
return f"{major}.{minor}.{patch + 1}"
return current
def update_version_file(path: Path, new_version: str) -> None:
"""Update the version string in a simple VERSION file."""
path.write_text(new_version + "\n")
print(f"Updated {path} to version {new_version}")
if __name__ == "__main__":
# Mock commit history as a list of conventional commit messages
commits = [
"feat: add user authentication",
"fix: correct login redirect bug",
"docs: update README",
"feat: add profile page",
"chore: bump dependencies",
]
version_file = Path("VERSION")
version_file.write_text("1.2.3\n")
current = version_file.read_text().strip()
new_version = get_next_version(current, commits)
update_version_file(version_file, new_version)
print(f"Current: {current}")
print(f"Next: {new_version}")
print(f"Final file content: {version_file.read_text().strip()!r}")
Output
Updated VERSION to version 1.3.0
Current: 1.2.3
Next: 1.3.0
Final file content: '1.3.0'
How it works
The get_next_version function parses the current version into major, minor, and patch components using map(int, ...), then checks commit messages for prefixes indicating a bump type. The logic prioritizes breaking changes (major bump) over features (minor bump) and fixes (patch bump), falling back to the current version if no relevant commits are found. This mimics how tools like semantic-release determine the next release number from conventional commit history. The script writes the updated version to a VERSION file, simulating what a CI pipeline would do before tagging a release.
Common mistakes
- Checking commit prefixes without handling case sensitivity or extra whitespace (e.g., 'Feat:' vs 'feat:').
- Writing the version file before computing the new version, leaving it in an inconsistent state.
- Not stripping the version string, causing parsing errors when reading from a file with a trailing newline.
Variations
- Use a changelog generator like `python-semantic-release` instead of a custom script for full CI integration.
- Parse the current version from a `pyproject.toml` file using `tomllib` instead of a separate VERSION file.
Real-world use cases
- Bump the version number in a project's version file during a CI/CD pipeline before building and publishing a package.
- Automatically tag releases in a Git repository based on merged pull requests that follow conventional commit conventions.
- Generate release notes from the bump messages and attach them to a new GitHub release for changelog automation.
Sponsored
More from Production deployment patterns
- Auto Rollback on Error Rate Exceeded in Python medium
- Design a Data Helper for Beginners in Python easy
- Docker healthcheck CMD mock in Python easy
- Generate a Mock Artifact Version Tag in Python easy
- Generate a docker-compose.yml with mock services in Python easy
- How to Attach an SBOM to a Release in Python (Mock) easy
Keep learning
Related tutorials and quizzes for this topic.