Python

  • Update Python package version and Git tag

    Written on

    under

    I’m always searching for ways to automate small, repetitive tasks in coding. One of these tasks is increasing the version number and creating a Git tag, which starts a CI build for us. I created the following script to make this process easier.

    Python source
    #!/usr/bin/env python3
    import subprocess
    import sys
    from pathlib import Path
    import re
    
    # --- Config ---
    PACKAGE_INIT = Path('mypkg/__init__.py')
    REMOTE = 'origin'
    
    # --- Helper functions ---
    def run_git(*args, capture_output=True, check=True):
        return subprocess.run(['git', *args],
                              capture_output=capture_output,
                              text=True,
                              check=check)
    
    def validate_tag(tag):
        if not re.fullmatch(r'[0-9a-z\.\:\-\~]+', tag):
            print(f'Error: version tag "{tag}" contains invalid characters.')
            print('Version tag should _NOT_ be prefixed with "v" (e.g., only "1.0.0" and not "v1.0.0")')
            sys.exit(1)
    
    def tag_exists(tag):
        result = run_git('tag', capture_output=True)
        return tag in result.stdout.splitlines()
    
    def update_version_file(tag):
        text = PACKAGE_INIT.read_text()
        new_text = re.sub(
            r'__version__\s*=\s*["\'].*?["\']',
            f'__version__ = \'{tag}\'',
            text
        )
        PACKAGE_INIT.write_text(new_text)
        print(f'[*] Updated __version__ to {tag} in {PACKAGE_INIT}')
    
    # --- Main ---
    if len(sys.argv) != 2 or sys.argv[1] in ('-h', '--help', '/?', '-help'):
        print(f'Usage: {sys.argv[0]} <version-tag>')
        print('<version-tag> should _NOT_ be prefixed with "v" (e.g., only "1.0.0" and not "v1.0.0")')
        sys.exit(1)
    
    version_tag = sys.argv[1]
    
    # Validate tag format
    validate_tag(version_tag)
    
    # Verify tag does not exist
    if tag_exists(version_tag):
        print(f'Error: Git tag "{version_tag}" already exists. Use `git tag -l` to view all tags.')
        sys.exit(1)
    
    # Update __init__.py
    update_version_file(version_tag)
    
    # Commit the change
    run_git('add', str(PACKAGE_INIT))
    run_git('commit', '-m', f'Bump version to {version_tag}')
    print('[*] Committed __version__ change.')
    
    # Create Git tag
    run_git('tag', version_tag)
    print(f'[*] Created Git tag "{version_tag}".')
    
    # Push commit and tag
    run_git('push', REMOTE)
    run_git('push', REMOTE, version_tag)
    print(f'[*] Pushed commit and tag "{version_tag}" to {REMOTE}.')

    To use it, simply call the script and pass in the new version number to set.

    $ ./publish_version.py 1.0.2
    [*] Updated __version__ to 1.0.2 in mypkg/__init__.py
    [*] Committed __version__ change.
    [*] Created Git tag "1.0.2".
    [*] Pushed commit and tag "1.0.2" to origin.