Exposing the package version defined in the pyproject.toml as a __version__ variable

Exposing the package version defined in the pyproject.toml as a __version__ variable

By convention, Python packages expose their version through a __version__ variable:

>>> import package
>>> package.__version__

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.


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']
    __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']