Skip to main content

Use git repo data for building a version number according PEP-440

Project description

setuptools-git-versioning

PyPI version PyPI - Python Version Build Status pre-commit.ci status

Use git repo data (latest tag, current commit hash, etc) for building a version number according PEP-440.

Comparing with other packages

Package/Function Latest release License Python2 support Python3 support Windows support PEP 440 compatible Type hints Way of configuration Supported substitutions Dev template support Dirty template support Initial version support Callback/variable as current version Read some file content as current version Write discovered version to a file Development releases (prereleases) support Reusing functions in other packages
setuptools-git-versioning 2021 MIT + 3.3+ + + + setup.py/setup.cfg tag, commits_count, short_sha, full_sha, branch, env, timestamp + + + + + - + +
setuptools-scm 2021 MIT removed in 6.0.0 release 3.6+ + + + pyproject.toml tag, commits_count, short_sha, timestamp + + + - - + + +
versioneer 2020 Public Domain - 3.6+ + + - setup.cfg tag, commits_count, short_sha, full_sha, timestamp + + - - + + + +
setuptools-git-ver (Base package) 2019 MIT - 3.7+ + - - setup.py/setup.cfg tag, commits_count, short_sha + + - - - - - -
another-setuptools-git-version 2020 MIT - 3.5+ - + + setup.py/setup.cfg tag, commits_count + - + - - - - +
bad-setuptools-git-version 2020 MIT + + + + - setup.py/setup.cfg tag, commits_count + - + - - - - +
even-better-setuptools-git-version 2019 MIT - + - + - setup.py/setup.cfg tag, short_sha - - + - - - - +
better-setuptools-git-version 2018 MIT - + - - - setup.py/setup.cfg tag, short_sha - - + - - - - +
very-good-setuptools-git-version 2018 MIT - + - - - setup.py/setup.cfg tag, commits_count, short_sha - - - - - - - +
setuptools-git-version 2018 Unknown + + + - - setup.py/setup.cfg tag, commits_count, short_sha - - - - - - - -

Installation

No need.

Adding setup_requires=['setuptools-git-versioning'] somewhere in setup.py will automatically download the latest version from PyPi and save it in the .eggs folder when setup.py is run.

Usage

Just add these lines into your setup.py:

setuptools.setup(version_config=True, setup_requires=["setuptools-git-versioning"], ...)

Release version = git tag

You want to use git tag as a release number instead of duplicating it setup.py or other file.

For example, current repo state is:

commit 86269212 Release commit (HEAD, master)
|
commit e7bdbe51 Another commit
|
...
|
commit 273c47eb Long long ago
|
...

Then you decided to release new version:

  • Tag commit with a proper release version (e.g. v1.0.0 or 1.0.0):

    commit 86269212 Release commit (v1.0.0, HEAD, master)
    |
    commit e7bdbe51 Another commit
    |
    ...
    |
    commit 273c47eb Long long ago
    |
    ...
    
  • Check current version with command python setup.py --version.

  • You'll get 1.0.0 as a version number. If tag number had v prefix, like v1.0.0, it will be trimmed.

Version number template

By default, when you try to get current version, you'll receive version number like 1.0.0.

You can change this template just in the same setup.py file:

setuptools.setup(
    version_config={
        "template": "2021.{tag}",
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

In this case, for tag 3.4 version number will be 2021.3.4

Dev template

For example, current repo state is:

commit 86269212 Current commit (HEAD, master)
|
commit 86269212 Release commit (v1.0.0)
|
commit e7bdbe51 Another commit
|
...
|
commit 273c47eb Long long ago
|
...

By default, when you try to get current version, you'll receive version number like 1.0.0.post1+git.64e68cd.

This is a PEP-440 compliant value, but sometimes you want see just 1.0.0.post1 value or even 1.0.0.

You can change this template just in the same setup.py file:

  • For values like 1.0.0.post1. N in .postN suffix is a number of commits since previous release (tag):

    setuptools.setup(
        version_config={
            "dev_template": "{tag}.dev{ccount}",
        },
        setup_requires=["setuptools-git-versioning"],
        ...,
    )
    
  • To return just the latest tag value, like 1.0.0,use these options:

    version_config = {
        "dev_template": "{tag}",
    }
    

Dirty template

For example, current repo state is:

Unstashed changes (HEAD)
|
commit 86269212 Current commit (master)
|
commit 86269212 Release commit (v1.0.0)
|
commit e7bdbe51 Another commit
|
...
|
commit 273c47eb Long long ago
|
...

By default, when you try to get current version, you'll receive version number like 1.0.0.post1+git.64e68cd.dirty. This is a PEP-440 compliant value, but sometimes you want see just 1.0.0.post1 value or even 1.0.0.

You can change this template just in the same setup.py file:

  • For values like 1.0.0.post1. N in .postN suffix is a number of commits since previous release (tag):

    setuptools.setup(
        version_config={
            "dirty_template": "{tag}.dev{ccount}",
        },
        setup_requires=["setuptools-git-versioning"],
        ...,
    )
    
  • To return just the latest tag value, like 1.0.0,use these options:

    version_config = {
        "dirty_template": "{tag}",
    }
    

Set initial version

For example, current repo state is:

commit 86269212 Current commit (HEAD, master)
|
commit e7bdbe51 Another commit
|
...
|
commit 273c47eb Long long ago
|
...

And there are just no tags in the current branch.

By default, when you try to get current version, you'll receive some initial value, like 0.0.1

You can change this template just in the same setup.py file:

setuptools.setup(
    version_config={
        "starting_version": "1.0.0",
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

Callback/variable as current version

For example, current repo state is:

commit 233f6d72 Dev branch commit (HEAD, dev)
|
|    commit 86269212 Current commit (v1.0.0, master)
|    |
|   commit e7bdbe51 Another commit
|    /
...
|
commit 273c47eb Long long ago
|
...

And there are just no tags in the current branch (dev) because all of them are placed in the master branch only.

By default, when you try to get current version, you'll receive some initial value. But if you want to get synchronized version numbers in both on the branches?

You can create a function in some file (for example, in the __init__.py file of your module):

def get_version():
    return "1.0.0"

Then place it in both the branches and update your setup.py file:

from mymodule import get_version

setuptools.setup(
    version_config={
        "version_callback": get_version,
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

When you'll try to get current version in non-master branch, the result of executing this function will be returned instead of latest tag number.

If a value of this option is not a function but just str, it also could be used:

  • __init__.py file:

    __version__ = "1.0.0"
    
  • setup.py file:

    from mymodule import __version__
    
    setuptools.setup(
        version_config={
            "version_callback": __version__,
        },
        setup_requires=["setuptools-git-versioning"],
        ...,
    )
    

Please take into account that no tag means that dev_template or dirty_template values are not used because current repo state is ignored in such a case

Read some file content as current version

Just like the previous example, but instead you can save current version in a simple test file instead of .py script.

Just create a file (for example, VERSION or VERSION.txt) and place here a version number:

1.0.0

Then place it in both the branches and update your setup.py file:

import os

HERE = os.path.dirname(__file__)
VERSION_FILE = os.path.join(HERE, "VERSION")

setuptools.setup(
    version_config={
        "version_file": VERSION_FILE,
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

When you'll try to get current version in non-master branch, the content of this file will be returned instead.

Development releases (prereleases) from dev branch

For example, current repo state is:

commit 233f6d72 Dev branch commit (HEAD, dev)
|
|    commit 86269212 Current commit (v1.0.0, master)
|    |
|   commit e7bdbe51 Another commit
|    /
...
|
commit 273c47eb Long long ago
|
...

You want to make development releases (prereleases) from commits to a dev branch. But there are just no tags here because all of them are placed in the master branch only.

Just like the examples above, create a file with a release number (e.g. 1.1.0) in the dev branch, e.g. VERSION.txt:

1.1.0

But place here next release number instead of current one.

Then update your setup.py file:

import os

HERE = os.path.dirname(__file__)
VERSION_FILE = os.path.join(HERE, "VERSION.txt")

setuptools.setup(
    version_config={
        "count_commits_from_version_file": True,
        "dev_template": "{tag}.dev{ccount}",  # suffix now is not .post, but .dev
        "dirty_template": "{tag}.dev{ccount}",  # same thing here
        "version_file": VERSION_FILE,
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

Then you decided to release new version:

  • Merge dev branch into master branch.
  • Tag commit in the master branch with a proper release version (e.g. v1.1.0). Tag will be used as a version number for the release.
  • Save next release version (e.g. 1.2.0) in VERSION or version.py file in the dev branch. Do not place any tags in the dev branch!
  • Next commits to a dev branch will lead to returning this next release version plus dev suffix, like 1.1.0.dev1 or so.
  • N in .devN suffix is a number of commits since the last change of a certain file.
  • Note: every change of this file in the dev branch will lead to this N suffix to be reset to 0. Update this file only in the case when you've setting up the next release version!

Development releases (prereleases) from any branch (feature/bugfix/preview/beta/etc)

Just like previous example, but you want to make development releases (prereleases) with a branch name present in the version number.

In case of branch names which are PEP-440 compatible, you can just use {branch} substitution in a version template.

For example, if the branch name is something like alpha, beta, preview or rc:

setuptools.setup(
    version_config={
        "count_commits_from_version_file": True,
        "dev_template": "{tag}.{branch}{ccount}",
        "dirty_template": "{tag}.{branch}{ccount}",
        "version_file": VERSION_FILE,
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

Adding a commit to the alpha branch will generate a version number like 1.2.3.alpha4, new commit to the beta branch will generate a version number like 1.2.3.beta5 and so on.

It is also possible to use branch names prefixed with a major version number, like 1.0-alpha or 1.1.beta:

setuptools.setup(
    version_config={
        "count_commits_from_version_file": True,
        "dev_template": "{branch}{ccount}",
        "dirty_template": "{branch}{ccount}",
        "version_file": VERSION_FILE,
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

Adding a commit to the 1.0-alpha branch will generate a version number like 1.0.alpha2, new commit to the 1.2.beta branch will generate a version number like 1.2.beta3 and so on.

But if branch name is not PEP-440 compatible at all, like feature/ABC-123 or bugfix/ABC-123, you'll get version number which pip cannot understand.

To fix that you can define a callback which will receive current branch name and return a properly formatted one:

import re


def format_branch_name(name):
    # If branch has name like "bugfix/issue-1234-bug-title", take only "1234" part
    pattern = re.compile("^(bugfix|feature)\/issue-([0-9]+)-\S+")

    match = pattern.search(name)
    if not match:
        return match.group(2)

    # function is called even if branch name is not used in a current template
    # just left properly named branches intact
    if name == "master":
        return name

    # fail in case of wrong branch names like "bugfix/issue-title"
    raise ValueError(f"Wrong branch name: {name}")


setuptools.setup(
    version_config={
        "dev_template": "{branch}.dev{ccount}",
        "dirty_template": "{branch}.dev{ccount}",
        "branch_formatter": format_branch_name,
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)

Options

Default options are:

setuptools.setup(
    version_config={
        "template": "{tag}",
        "dev_template": "{tag}.post{ccount}+git.{sha}",
        "dirty_template": "{tag}.post{ccount}+git.{sha}.dirty",
        "starting_version": "0.0.1",
        "version_callback": None,
        "version_file": None,
        "count_commits_from_version_file": False,
        "branch_formatter": None,
        "sort_by": None,
    },
    setup_requires=["setuptools-git-versioning"],
    ...,
)
  • template: used if no untracked files and latest commit is tagged

  • dev_template: used if no untracked files and latest commit isn't tagged

  • dirty_template: used if untracked files exist or uncommitted changes have been made

  • starting_version: static value, used if not tags exist in repo

  • version_callback: variable or callback function to get version instead of using starting_version

  • version_file: path to VERSION file, to read version from it instead of using static_version

  • count_commits_from_version_file: True to fetch version_file last commit instead of tag commit, False otherwise

  • branch_formatter: callback to be used for formatting a branch name before template substitution

  • sort_by: format string passed to git tag --sort= command to sort the output. Possible values: version:refname (alphanumeric sort), committerdate (commit date of tag), creatordate (tag creation date, default)

Substitutions

You can use these substitutions in template, dev_template or dirty_template options:

  • {tag}: Latest tag in the repository

  • {ccount}: Number of commits since last tag or last version_file commit (see count_commits_from_version_file)

  • {full_sha}: Full sha hash of the latest commit

  • {sha}: First 8 characters of the sha hash of the latest commit

  • {branch}: Current branch name

  • {env:SOMEVAR}: Value of environment variable SOMEVAR. Examples:

    • You can pass default value using {env:SOMEVAR:default} syntax.

    • Default value for missing variables is UNKNOWN. If you need to just skip the substitution, use {env:SOMEVAR:IGNORE} syntax.

    • It is possible to pass another substitution instead of a default value using {env:SOMEVAR:{subst}} syntax, e.g. {env:BUILD_NUMBER:{ccount}}.

  • {timestamp:format}: Current timestamp rendered into a specified format. Examples:

    • {timestamp} or {timestamp:%s} will result 1632181549 (Unix timestamp).

    • {timestamp:%Y-%m-%dT%H-%M-%S} will result 2021-09-21T12:34:56.

Common issues

Every version built by CI is dirty

This is usually caused by some files created by CI pipeline like build artifacts or test reports, e.g. dist/my_package.whl or reports/unit.xml. If they are not mentioned in .gitignore file they will be recognized by git as untracked. Because of that git status will report that you have uncommitted (dirty) changes in the index, so setuptools-git-versioning will detect current version as dirty.

You should such files to the .gitignore file. See current repo .gitignore as an example.

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

setuptools-git-versioning-1.7.3.tar.gz (10.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

setuptools_git_versioning-1.7.3-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

setuptools_git_versioning-1.7.3-py2-none-any.whl (9.9 kB view details)

Uploaded Python 2

File details

Details for the file setuptools-git-versioning-1.7.3.tar.gz.

File metadata

  • Download URL: setuptools-git-versioning-1.7.3.tar.gz
  • Upload date:
  • Size: 10.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.6.15

File hashes

Hashes for setuptools-git-versioning-1.7.3.tar.gz
Algorithm Hash digest
SHA256 8a072b460020d53024805e86b97a8a962570d193a706200727796a4136f017c1
MD5 51e3aaa270233d6847376b7fb08692b8
BLAKE2b-256 c379bd09f604040a114106832c95a4a37b18ac362cc944b4c31b7155d5d8fcc4

See more details on using hashes here.

File details

Details for the file setuptools_git_versioning-1.7.3-py3-none-any.whl.

File metadata

  • Download URL: setuptools_git_versioning-1.7.3-py3-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.6.15

File hashes

Hashes for setuptools_git_versioning-1.7.3-py3-none-any.whl
Algorithm Hash digest
SHA256 8bd9418a039aff77822d8fb7a5df65077d9e0d399905ffae09f925d156358fc5
MD5 6254599e8982819d0b5e5f6771badd88
BLAKE2b-256 0d3f94fe197500e998e5b6ebb417375495e3ff6660cae15ed11593730895c51b

See more details on using hashes here.

File details

Details for the file setuptools_git_versioning-1.7.3-py2-none-any.whl.

File metadata

  • Download URL: setuptools_git_versioning-1.7.3-py2-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 2
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.15.0 pkginfo/1.7.1 requests/2.26.0 setuptools/44.1.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/2.7.18

File hashes

Hashes for setuptools_git_versioning-1.7.3-py2-none-any.whl
Algorithm Hash digest
SHA256 42979a14cd18d29e2540f6de21dbc76e201a78c8e854e7f35b4350d4562a7f95
MD5 d991c6316af5724726137ad9b1efeb21
BLAKE2b-256 2e8cbacc42125fe1b53330c77d14bf49692b4d140ae5cf37ed8516925ac21867

See more details on using hashes here.

Supported by

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