Useful Scripts

  • 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.
  • Ensuring Data Integrity in Custom Scoped Applications with Contractual

    Written on

    under

    Building custom scoped applications in ServiceNow, it is important to make sure data is clean, consistent, and fits the expected format. IMO – ServiceNow doesn’t do enough to enforce data validation. To amend that, I wrote Contractual.

    What is Contractual?

    Contractual is a handy script include for consistent data validation. You can wrap variables or objects and run all kinds of checks on them. If something’s off, it throws a clear error. Its fluent interface makes validation code neat and readable.

    (more…)