Reputation: 1465
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?
Upvotes: 80
Views: 34371
Reputation: 95
I might be late to the party, but this is how I solved it.
pyproject.toml
is using the version as it is defined in the package version file (some_package/version.py
) using poetry-dynamic-versioning which allows "dynamic versioning based on tags in your version control system" but offers also the possibility to utilize Jinja templating to fill in the version.
And that version.py
file again is, in my case, generated by changelog2version which updates the version file based on a markdown changelog file following SemVer.
Ensure to once call poetry self add "poetry-dynamic-versioning[plugin]"
, as documented in poetry-dynamic-versioning.
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version_info__ = ("0", "1", "0")
__version__ = ".".join(__version_info__)
[tool.poetry]
name = "my-package-name"
version = "0.0.0+will-be-updated-automatically"
description = "Cool stuff"
authors = ["brainelectronics"]
packages = [
{include = "some_package/**/*.py"}
]
# https://github.com/mtkennerly/poetry-dynamic-versioning/tree/v1.3.0
[tool.poetry-dynamic-versioning]
enable = true
format-jinja-imports = [
{ module = "subprocess", item = "check_output" },
]
format-jinja = """{{ check_output(["python", "-c", "from pathlib import Path; exec(Path('some_package/version.py').read_text()); print(__version__)"]).decode().strip() }}"""
[tool.poetry.dependencies]
python = "^3.10.4"
[tool.poetry.group.dev.dependencies]
poetry-dynamic-versioning = "~1.3.0"
[build-system]
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
build-backend = "poetry_dynamic_versioning.backend"
Upvotes: 1
Reputation: 5485
I provided the answer to the same question in the similar discussion. Here is a copy:
In the project.toml
you can specify version in different ways:
[tool.poetry]
:[tool.poetry]
name = "my-package"
version = "0.1.0"
[project]
section:[project]
name = "my-package"
version = "2020.0.0"
The next point is that you may want to see the version of installed packages (e.g. via pip
or poetry
) or your application. The next:
import importlib.metadata
version = importlib.metadata.version("my-package")
print(version)
This code works fine with installed packages. To make working it with your project, you should install it using pip install -e .
or something similar.
If you want to check version of your project without installing, then you can use the following snippet:
from pathlib import Path
import toml
version = "unknown"
# adopt path to your pyproject.toml
pyproject_toml_file = Path(__file__).parent / "pyproject.toml"
if pyproject_toml_file.exists() and pyproject_toml_file.is_file():
data = toml.load(pyproject_toml_file)
# check project.version
if "project" in data and "version" in data["project"]:
version = data["project"]["version"]
# check tool.poetry.version
elif "tool" in data and "poetry" in data["tool"] and "version" in data["tool"]["poetry"]:
version = data["tool"]["poetry"]["version"]
print(version)
To make it working you should install toml
package in advance.
Upvotes: 0
Reputation: 1856
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"]
+
Upvotes: 5
Reputation: 9808
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)
Upvotes: 7
Reputation: 1077
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__)
Upvotes: 18
Reputation: 15252
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")
Upvotes: 73