Python project version single-sourcing

Problem

It is not entirely straightforward where the version string should be written within a Python project.

A couple of things are sure:

  • the version must be written in a __version__ attribute as a string (see PEP 396)

  • the version string must be available from the setup script

  • the version string should be in the changelog

It is annoying to have to keep the version string up to date in these three locations. A solution for single-sourcing the project version would fix that.

Solution

This solution shows how to keep the Python project version string in just one place. The suggested location is in the change log:

CHANGELOG.rst
01.2.3
1=====
2
3* More bugs fixed
4
51.2.2
6=====
7
8* Bugs fixed

The current version string should always be on the same line and on its own so that the setup script can easily find it and extract it:

setup.py
import os
import setuptools

with open(os.path.join(HERE, 'CHANGELOG.rst')) as file_:
    changelog = file_.read()

setuptools.setup(
    name='Example',
    version=changelog.splitlines()[0],
    # ...
)

From the actual code of the project the version number should be accessed via importlib.metadata. Knowing the name of the project it is easy to get the version string:

src/example/__init__.py
import importlib.metadata

__version__ = importlib.metadata.version('Example')

The importlib.metadata package is part of the standard library starting with Python 3.8. For earlier versions use importlib-metadata instead.

As a positive side effect, changing the version number forces the project maintainer to modify the change log and thus they always get at least one chance to keep it up to date.