Skip to main content

Dynamic version generation

Project description

Dunamai

Dunamai is a Python 3.5+ library and command line tool for producing dynamic, standards-compliant version strings, derived from tags in your version control system. This facilitates uniquely identifying nightly or per-commit builds in continuous integration and releasing new versions of your software simply by creating a tag.

Dunamai is also available as a GitHub Action.

Features

Usage

Installation

pip install dunamai

CLI

# Suppose you are on commit g29045e8, 7 commits after the v0.2.0 tag.

# Auto-detect the version control system and generate a version:
$ dunamai from any
0.2.0.post7.dev0+g29045e8

# Or use an explicit VCS and style:
$ dunamai from git --no-metadata --style semver
0.2.0-post.7

# Custom formats:
$ dunamai from any --format "v{base}+{distance}.{commit}"
v0.2.0+7.g29045e8

# If you'd prefer to frame the version in terms of progress toward the next
# release rather than distance from the latest one, you can bump it:
$ dunamai from any --bump
0.2.1.dev7+g29045e8

# Validation of custom formats:
$ dunamai from any --format "v{base}" --style pep440
Version 'v0.2.0' does not conform to the PEP 440 style

# Validate your own freeform versions:
$ dunamai check 0.01.0 --style semver
Version '0.01.0' does not conform to the Semantic Versioning style

# More info:
$ dunamai --help
$ dunamai from --help
$ dunamai from git --help

Library

from dunamai import Version, Style

# Let's say you're on commit g644252b, which is tagged as v0.1.0.
version = Version.from_git()
assert version.serialize() == "0.1.0"

# Let's say there was a v0.1.0rc5 tag 44 commits ago
# and you have some uncommitted changes.
version = Version.from_any_vcs()
assert version.serialize() == "0.1.0rc5.post44.dev0+g644252b"
assert version.serialize(metadata=False) == "0.1.0rc5.post44.dev0"
assert version.serialize(dirty=True) == "0.1.0rc5.post44.dev0+g644252b.dirty"
assert version.serialize(style=Style.SemVer) == "0.1.0-rc.5.post.44+g644252b"

The serialize() method gives you an opinionated, PEP 440-compliant default that ensures that versions for untagged commits are compatible with Pip's --pre flag. The individual parts of the version are also available for you to use and inspect as you please:

assert version.base == "0.1.0"
assert version.stage == "rc"
assert version.revision == 5
assert version.distance == 44
assert version.commit == "g644252b"
assert version.dirty is True

# Available if the latest tag includes metadata, like v0.1.0+linux:
assert version.tagged_metadata == "linux"

Tips

By default, the "v" prefix on the tag is required, unless you specify a custom tag pattern. You can either write a regular expression:

  • Console:
    $ dunamai from any --pattern "(?P<base>\d+\.\d+\.\d+)"
    
  • Python:
    from dunamai import Version
    version = Version.from_any_vcs(pattern=r"(?P<base>\d+\.\d+\.\d+)")
    

...or use a named preset:

  • Console:
    $ dunamai from any --pattern default-unprefixed
    
  • Python:
    from dunamai import Version, Pattern
    version = Version.from_any_vcs(pattern=Pattern.DefaultUnprefixed)
    

You can also keep the default pattern and just specify a prefix. For example, this would match tags like some-package-v1.2.3:

  • Console:
    $ dunamai from any --pattern-prefix some-package-
    
  • Python:
    from dunamai import Version
    version = Version.from_any_vcs(pattern_prefix="some-package-")
    

VCS archives

Sometimes, you may only have access to an archive of a repository (e.g., a zip file) without the full history. Dunamai can still detect a version in some of these cases:

  • For Git, you can configure git archive to produce a file with some metadata for Dunamai.

    Add a .git_archival.json file to the root of your repository with this content:

    {
      "hash-full": "$Format:%H$",
      "hash-short": "$Format:%h$",
      "timestamp": "$Format:%cI$",
      "refs": "$Format:%D$",
      "describe": "$Format:%(describe:tags=true,match=v[0-9]*)$"
    }
    

    Add this line to your .gitattributes file. If you don't already have this file, add it to the root of your repository:

    .git_archival.json  export-subst
    
  • For Mercurial, Dunamai will detect and use an .hg_archival.txt file created by hg archive. It will also recognize .hgtags if present.

Custom formats

Here are the available substitutions for custom formats. If you have a tag like v9!0.1.2-beta.3+other, then:

  • {base} = 0.1.2
  • {stage} = beta
  • {revision} = 3
  • {distance} is the number of commits since the last
  • {commit} is the commit hash (defaults to short form, unless you use --full-commit)
  • {dirty} expands to either "dirty" or "clean" if you have uncommitted modified files
  • {tagged_metadata} = other
  • {epoch} = 9
  • {branch} = feature/foo
  • {branch_escaped} = featurefoo
  • {timestamp} is in the format YYYYmmddHHMMSS as UTC
  • {major} = 0
  • {minor} = 1
  • {patch} = 2

If you specify a substitution, its value will always be included in the output. For conditional formatting, you can do something like this (Bash):

distance=$(dunamai from any --format "{distance}")
if [ "$distance" = "0" ]; then
    dunamai from any --format "v{base}"
else
    dunamai from any --format "v{base}+{distance}.{dirty}"
fi

Comparison to Versioneer

Versioneer is another great library for dynamic versions, but there are some design decisions that prompted the creation of Dunamai as an alternative:

  • Versioneer requires a setup.py file to exist, or else versioneer install will fail, rendering it incompatible with non-setuptools-based projects such as those using Poetry or Flit. Dunamai can be used regardless of the project's build system.
  • Versioneer has a CLI that generates Python code which needs to be committed into your repository, whereas Dunamai is just a normal importable library with an optional CLI to help statically include your version string.
  • Versioneer produces the version as an opaque string, whereas Dunamai provides a Version class with discrete parts that can then be inspected and serialized separately.
  • Versioneer provides customizability through a config file, whereas Dunamai aims to offer customizability through its library API and CLI for both scripting support and use in other libraries.

Integration

  • Setting a __version__ statically:

    $ echo "__version__ = '$(dunamai from any)'" > your_library/_version.py
    
    # your_library/__init__.py
    from your_library._version import __version__
    

    Or dynamically (but Dunamai becomes a runtime dependency):

    # your_library/__init__.py
    import dunamai as _dunamai
    __version__ = _dunamai.get_version("your-library", third_choice=_dunamai.Version.from_any_vcs).serialize()
    
  • setup.py (no install-time dependency on Dunamai as long as you use wheels):

    from setuptools import setup
    from dunamai import Version
    
    setup(
        name="your-library",
        version=Version.from_any_vcs().serialize(),
    )
    

    Or you could use a static inclusion approach as in the prior example.

  • Poetry:

    $ poetry version $(dunamai from any)
    

    Or you can use the poetry-dynamic-versioning plugin.

Other notes

  • Dunamai needs access to the full version history to find tags and compute distance. Be careful if your CI system does a shallow clone by default.

    • For GitHub workflows, invoke actions/checkout@v3 with fetch-depth: 0.
    • For GitLab pipelines, set the GIT_DEPTH variable to 0.
    • For Docker builds, copy the VCS history (e.g., .git folder) into the container.

    For Git, you can also avoid doing a full clone by specifying a remote branch for tags (e.g., --tag-branch remotes/origin/master).

  • When using Git, remember that lightweight tags do not store their creation time. Therefore, if a commit has multiple lightweight tags, we cannot reliably determine which one should be considered the newest. The solution is to use annotated tags instead.

  • When using Git, the initial commit should not be both tagged and empty (i.e., created with --allow-empty). This is related to a reporting issue in Git. For more info, click here. Dunamai tries to work around this, but multiple tags on an empty initial commit may not be sorted correctly.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dunamai-1.24.0.tar.gz (45.6 kB view details)

Uploaded Source

Built Distribution

dunamai-1.24.0-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

Details for the file dunamai-1.24.0.tar.gz.

File metadata

  • Download URL: dunamai-1.24.0.tar.gz
  • Upload date:
  • Size: 45.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.10.7 Windows/10

File hashes

Hashes for dunamai-1.24.0.tar.gz
Algorithm Hash digest
SHA256 c2d1a9f7359033c04dfc1865481d890acc5be4ac02596ad3275b854aba342294
MD5 20bcc2097d56742af8b04dc238d0a039
BLAKE2b-256 7b391d9099f7529c61c80ef00c88b385493b9f6183582ac9bca5af84fe62311b

See more details on using hashes here.

File details

Details for the file dunamai-1.24.0-py3-none-any.whl.

File metadata

  • Download URL: dunamai-1.24.0-py3-none-any.whl
  • Upload date:
  • Size: 26.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.10.7 Windows/10

File hashes

Hashes for dunamai-1.24.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4f4367a74450c70b2d6b8469449cb0ed57db0040a9b463afb11c44ac18f58dff
MD5 dbf70ed4299a4a710d14e21b4be7c89d
BLAKE2b-256 455677457e6b31b0c507d79c8baf5a808783d64e7a5890b27bc623ed748c13ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page