Usual (pythonic) way to add/inject the git-hash to the version string

There are applications out there displaying not only the regular SemVer version string (e.g. “1.4.2-dev”) but also the git hash and sometimes branch name (e.g. “1.4.2-dev.hash123”).

How do I realize this when it comes to modern Python packaging. IN that case there is a version string in the pyproject.toml. That info is added to the packages meta date while installing it via (pip(x) install) and the application to ask the Python environment about that meta data to get its own version string.

I do use this process also to set __version__. It is not hardcoded because I try to avoid redunancies. I don’t need a version string in pyproject.toml and in an init.py file.

How other projects realizing this?

Have a look at

Be aware that dynamically updating the version when the project is installed in editable mode does not work.

1 Like

I do this using Hatch, which has a hatch-vcs plugin which can construct the version string using that information (and more). It’s entirely configured within pyproject.toml, and if desired can also emit the version string into a source file although that’s not really necessary since the package’s version string can be obtained at runtime using importlib.metadata.version('<package name').

Thanks for this tips. Can you point to one of your public repos using this?

F***** awesome. Just the pyproject.toml get me excited a lot. :smiley: Interesting what all could be achieved with it.

Do you have a blog? You should write just about your packaging setup and all the little tiny helpers in there. I have so many questions. Don’t write only about that tools. We can read that in their docs. Write about how you integrate them into your workflow and how they help to improve your workflow.

PBR (a SetupTools plugin and PEP-517 build backend) takes a
different approach. Since Git commit IDs aren’t valid components of
PEP-440 version strings, it automatically stores the hash in
separate package metadata which can then be accessed easily at
runtime, while auto-calculating a PEP-440 compliant version string
based on the most recent tag in the branch history along with a
commit count if necessary (i.e. for -devNNN suffixes). This way the
versions meet expected standards for tools which care about that,
yet anything which needs the commit ID (like a version function
embedded in your application) can use standard package metadata
tools to query and return it.

1 Like

Thanks for the kind words! No, I don’t have a blog, as I can never find the time to do it, but I follow many others in the Python community and much of what you see in that pyproject.toml was inspired by (or directly copied from) various projects owned by @hynek. You can look at his attrs project to see the similarities and to get even more ideas!

2 Likes

setuptools-git-versioning is another mature option (we use it in Bokeh)

1 Like

I’ve used setuptools-scm in my project sciform for this.

The important parts of the pyproject.toml are

[build-system]
requires = ["setuptools>=61.0", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
...
dynamic = ["version", ...]

I forget exactly what is important under [build-system] for setuptools-scm to work.

When the build happens the version is resolved from the git information. If the HEAD is on a commit with a valid version tag then that version tag will be written as the version in the metadata. If it is not on a tagged commit then it searches for the closest ancestor commit that has a version tag and creates a version number like

0.34.1.dev5+gfd596e3

Here dev indicates that it is not a tagged version, the 5 indicates it is 5 commits ahead of tagged version 0.34.1, g indicates git is used for version control, and fd596e3 is the start of the commit hash that HEAD is currently at.

Right now if a build happens on a tagged commit the version just comes out as, e.g. 0.34.1, that is the commit information is excluded. There are probably options that allow you to include the commit information even when the HEAD is on a version tagged commit if that’s what you want.


I used to define __version__ in my main __init__.py until I learned about importlib.metadata.version("my_pkg"). Once I learned about that + setuptools-scm I removed __version__ from __init__.py. The one place in sciform I need the version number in code is in \docs\source\conf.py which is used to configure sphinx which helps build the documentation. There I need to provide the version number, so I simply do

from importlib import metadata

project = "sciform"
version = metadata.version(project)
1 Like

That sounds all great.

What happens when distro maintainers taking over. For example Debian Python Team do use salsa (GitLab instance) to manage their packages including upstream code. Then setuptools-scm would read version info from a git repo that do not belong to upstream anymore.

I am also aware that something similar happens with Arch AUR packages.

What happens when distro maintainers taking over. For example
Debian Python Team do use salsa (GitLab instance) to manage their
packages including upstream code. Then setuptools-scm would read
version info from a git repo that do not belong to upstream
anymore.

I can’t speak for all the possible options, but for PBR and
setuptools-scm there are environment variables which can be used to
override the Git-based guessing. That’s the typical escape hatch for
distro package maintainers who want to specify an explicit version
at build time without altering the upstream source code at all.

1 Like