54

My package version is defined in two places:

  • __version__ = 1.2.3 in mypackage/__init__.py
  • version = "1.2.3" in pyproject.toml (I am using Poetry)

I have to update both whenever I bump the version which is annoying and not DRY. Is there a way to make Python read the version from the TOML, or to make the TOML read the version from Python?

bad_coder
  • 11,289
  • 20
  • 44
  • 72
Haterind
  • 1,095
  • 1
  • 8
  • 16
  • 10
    You sniffed something fishy and you were right. Why would you need another place to track your project's metadata if you already use `pyproject.toml`? – z33k Dec 01 '21 at 14:31

4 Answers4

50

After you have installed your project - either in editable mode by poetry install or from the wheel - you can access several metadata via importlib.metadata (importlib_metadata for python < 3.8).

So keep the version only in the pyproject.toml and use this in your python code:

import importlib.metadata

__version__ = importlib.metadata.version("mypackage")
finswimmer
  • 10,896
  • 3
  • 34
  • 44
  • 1
    Thanks! Out of curiosity, where would I find this information in the docs? https://python-poetry.org/docs/pyproject/#version is quite terse. – Haterind Apr 14 '21 at 19:02
  • 1
    This works quite well until it runs in a GitLab pipeline. Any clues on how to do this *without* the ability to install the package? – Jonathan Belden Mar 07 '23 at 16:48
12

This code worked for me:

import importlib.metadata

__version__ = importlib_metadata.version(__package__ or __name__)

However, this only works if the package is already installed using pip or poetry.

On newer version (dot instead of underscore):

__version__ = importlib.metadata.version(__package__ or __name__)
Alwin
  • 786
  • 9
  • 19
  • 3
    would this work for editable installs? from what i am seeing when editable install and change version in pyproject.toml wouldnt be reflected? – kamster Nov 15 '22 at 19:42
5

Maybe overly complicated, but in order to not confuse an installed version of a package with an instance lingering around locally I use this code:


from contextlib import suppress
import importlib.metadata
from pathlib import Path


def extract_version() -> str:
    """Returns either the version of installed package or the one
    found in nearby pyproject.toml"""
    with suppress(FileNotFoundError, StopIteration):
        with open((root_dir := Path(__file__).parent.parent)
                  / "pyproject.toml", encoding="utf-8") as pyproject_toml:
            version = (
                next(line for line in pyproject_toml if line.startswith("version"))
                .split("=")[1]
                .strip("'\"\n ")
            )
            return f"{version}-dev (at {root_dir})"
    return importlib.metadata.version(__package__
                                      or __name__.split(".", maxsplit=1)[0])

__version__ = extract_version()

Giving me either 1.2.23 for installed packages or something like 1.3.42-dev (at /project/location)

matt.baker
  • 232
  • 1
  • 9
frans
  • 8,868
  • 11
  • 58
  • 132
1

All of the current answers address the issue of getting the version after install. However, as Jonathan Belden points out in a comment on @finswimmer's answer, these methods break in CI pipelines and the like when the package isn't installed. In keeping with @z33k's comment on the question, one solution would be to read the value from pyproject.toml.

With poetry as your package manager, another solution would be to use the poetry-bumpversion plugin to manage version bumps using poetry's version command. For example, say you have a package called widget, with __version__ defined in widget/__init__.py, with the same value as pyproject.toml has for version. With the poetry-bumpversion plugin, you would add

[tool.poetry_bumpversion.file."widget/__init__.py"]

to pyproject.toml, then

% poetry version patch
Bumping version from 0.7.9 to 0.7.10
poetry_bumpversion: processed file widget/__init__.py

% git diff -U0
diff --git a/widget/__init__.py b/widget/__init__.py
index 18c7cbf..9ff9982 100644
--- a/widget/__init__.py
+++ b/widget/__init__.py
@@ -1 +1 @@
-__version__ = "0.7.9"
+__version__ = "0.7.10"
diff --git a/pyproject.toml b/pyproject.toml
index 1b86c6e..5de1ce1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3 +3 @@ name = "widget"
-version = "0.7.9"
+version = "0.7.10"
@@ -55,0 +56,2 @@ tox = "^4.6.4"
+[tool.poetry_bumpversion.file."widget/__init__.py"]
+

hlongmore
  • 1,603
  • 24
  • 28