Keeping versions aligned across setup.py, pyproject.toml, and GitHub tags is critical for maintaining a healthy Python project. It prevents mismatches, enables CI/CD automation, and ensures seamless releases.

In this guide, you'll learn best practices for versioning Python packages and syncing metadata with GitHub release tags using bump-my-version, GitHub Actions, and automation scripts.


📌 Table of Contents

  1. Why Versioning is Crucial
  2. The Right Way to Define Your Version
  3. Aligning Versions with GitHub Tags
  4. Keeping Dependencies in Sync
  5. Using bump-my-version for Automated Versioning
  6. Validating Versions in CI/CD
  7. Mistakes to Avoid
  8. Final Checklist
  9. FAQs

Why Versioning is Crucial

Imagine deploying a package and realizing later that the version in setup.py differs from pyproject.toml. 🤦‍♂️ This breaks automation, confuses users, and complicates debugging.

When you keep versions in sync, you:

  • Ensure smooth CI/CD deployments
  • Reduce version conflicts in dependencies
  • Automate and streamline release workflows

Following best practices prevents the dreaded "version mismatch" error and keeps your project organized.


The Right Way to Define Your Version

A common pitfall is defining the version in multiple places. Instead, define it in a single source of truth.

1️⃣ Store Version in __version__.py

Create a __version__.py file inside your package:

# my_package/__version__.py
__version__ = "1.2.3"

2️⃣ Use It in setup.py

Instead of manually entering a version, import it dynamically:

from my_package.__version__ import __version__

setup(
    name="my_package",
    version=__version__,
    ...
)

3️⃣ Sync with pyproject.toml (for Poetry)

If you're using Poetry, manually update pyproject.toml:

[tool.poetry]
name = "my-package"
version = "1.2.3"

📌 Poetry does not support dynamic version imports—so keeping this updated manually (or via automation) is necessary.


Aligning Versions with GitHub Tags

To ensure GitHub releases match your code, follow this release process:

  1. Update the version in __version__.py and pyproject.toml.
  2. Commit the change:
git commit -am "Release version 1.2.3"
  1. Create a tag matching the version:
git tag v1.2.3
   git push origin main --tags
  1. Ensure the tag and package version match before deploying.

🚨 If the tag and package version don’t match, CI/CD should catch the issue and stop the release.


Keeping Dependencies in Sync

Beyond versioning, managing dependencies properly prevents unexpected failures.

Lock Dependencies in requirements.txt

For reproducible builds, lock dependencies:

pip freeze > requirements.txt

Separate Dev Dependencies

Use a separate file for development dependencies:

pip install -r requirements-dev.txt

Alternatively, if using Poetry, do:

poetry add pytest --dev

This ensures production installs don’t pull unnecessary dev dependencies.


Using bump-my-version for Automated Versioning

What is bump-my-version?

bump-my-version is the modern replacement for bump2version (which is no longer maintained).

It updates version numbers across all necessary files (e.g., __version__.py, setup.py, pyproject.toml).

How to Install It

pip install bump-my-version

How to Use It

Increment version numbers automatically:

bump-my-version patch   # Updates 1.2.3 → 1.2.4
bump-my-version minor   # Updates 1.2.3 → 1.3.0
bump-my-version major   # Updates 1.2.3 → 2.0.0

This ensures versioning consistency, preventing human errors in updates.


Validating Versions in CI/CD

To prevent mismatched versions between GitHub tags and your package metadata, add a validation step to GitHub Actions.

CI Workflow to Validate Versions

Create .github/workflows/version-check.yml:

name: Version Check

on:
  push:
    tags:
      - 'v*'  # Runs only on version tags

jobs:
  check-version:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Validate package version consistency
        run: |
          TAG_VERSION=${GITHUB_REF#refs/tags/v}
          PACKAGE_VERSION=$(python -c "import my_package.__version__ as v; print(v.__version__)")

          if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
            echo "Version mismatch! GitHub tag is $TAG_VERSION but package version is $PACKAGE_VERSION."
            exit 1
          fi

If the versions don’t match, the pipeline will fail, preventing a broken release.


Mistakes to Avoid

🚨 Hardcoding Versions in Multiple Files – Instead, use __version__.py

🚨 Pushing GitHub Tags Without Updating Files First

🚨 Ignoring Dependency Locking (requirements.txt)

🚨 Manual Version Updates Instead of Automation (bump-my-version)

Avoid these, and your releases will be smooth! 🚀


Final Checklist

✅ Keep version centralized in __version__.py

Always sync pyproject.toml (for Poetry users)

✅ Automate with bump-my-version

✅ Validate version consistency in CI/CD

✅ Lock dependencies for reliable builds


FAQs

1. Why is bump2version no longer recommended?

bump2version is no longer maintained. bump-my-version is the modern alternative with active support.

2. How do I ensure my GitHub release matches my package version?

Use GitHub Actions to verify that the Git tag matches __version__.py before releasing.

3. Should I use setup.py or Poetry?

  • If you use setuptools, update setup.py
  • If you use Poetry, manually update pyproject.toml

4. Do I still need requirements.txt if using Poetry?

No! Poetry manages dependencies internally, so requirements.txt is unnecessary.

5. Is bump-my-version required?

No, but it automates versioning, preventing human mistakes.


Conclusion

Keeping your Python packaging metadata in sync with GitHub release tags prevents deployment issues, enables automation, and ensures smooth releases.

By following best practices like centralized versioning, GitHub Actions validation, and automated version bumps, you'll create a robust, foolproof versioning system!

Want to take it a step further? Integrate this workflow into your CI/CD pipeline today! 🚀