Exposing the package version defined in the pyproject.toml as a __version__ variable
Table of contents
By convention, Python packages expose their version through a __version__
variable:
>>> import package
>>> package.__version__
0.4.2
Ideally, the package version should be defined in one place only. Typical approaches include hardcoding it in the package's __init__.py
or maintaining a VERSION
text file. In these cases, the pyproject.toml
file, which specifies the package metadata, contains a dynamic reference to the version.
Python project managers like Hatch and Poetry provide CLI utilities for incrementing a package version. While Hatch supports maintaining the version in the code, Poetry does not. Instead, it expects a version
field in the [tool.poetry]
block.
In general, I find it preferable to define the version in the pyproject.toml
instead of placing it elsewhere in the package. The pyproject.toml
is where all the metadata and dependencies are defined in an organized and human-readable way.
When a package is installed, we can retrieve its version through importlib.metadata
. However, when working with local code, this option is not available. If the package is also installed in the local environment, it will return the installed version, even if you're executing the local copy of the code.
So here's a simple way to expose the version as package.__version__
that will always return the version of the package that you're actually importing.
Setup
We assume that we have the following directory and file structure:
|- package
| |- __init__.py
| |- ...
|
| pyproject.toml
It's straightforward to adapt the following code to a different directory structure (e.g., a src/package
layout). The only important thing is the relative location of the pyproject.toml
to the main __init__.py
file.
Exposing the version as __init.py__.__version__
In our __init__.py
, we read the version from the pyproject.toml
when running from source, or fetch it from the package metadata when running as an installed package:
import importlib.metadata
import pathlib
import tomllib
source_location = pathlib.Path(__file__).parent
if (source_location.parent / "pyproject.toml").exists():
with open(source_location.parent / "pyproject.toml", "rt") as f:
__version__ = tomllib.load(f)['project']['version']
else:
__version__ = importlib.metadata.version("package")
For Poetry, access to the pyproject.toml
changes as follows:
with open(source_location.parent / "pyproject.toml", "rt") as f:
__version__ = tomllib.load(f)['tool']['poetry']['version']
Subscribe to my newsletter
Read articles from Kilian Kluge directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Kilian Kluge
Kilian Kluge
My journey into software and infrastructure engineering started in a physics research lab, where I discovered the merits of loose coupling and adherence to standards the hard way. I like automated testing, concise documentation, and hunting complex bugs.