Вы когда-нибудь сталкивались с ситуацией, когда после обновления версии пакета в pyproject.toml вам приходилось вручную обновлять версию в коде? Эта рутина не только отнимает время, но и чревата ошибками. В современной разработке на Python управление зависимостями и публикация пакетов давно вышли на новый уровень благодаря таким инструментам, как Poetry. Он объединяет в себе функции сборщика, менеджера виртуальных окружений и инструмента для публикации. Однако разработчики часто сталкиваются с классической дилеммой: как сделать так, чтобы версия пакета, указанная в файле pyproject.toml, была доступна внутри самого кода приложения во время его выполнения (runtime)?

Этот вопрос относится к фундаментальному принципу проектирования программного обеспечения — Single Source of Truth (Единый источник истины). Если хранить версию в нескольких местах (например, в pyproject.toml и в переменной __version__ в файле __init__.py), рано или поздно возникнет рассинхронизация. Разработчик забудет обновить версию в коде перед релизом, что приведет к путанице в логах, баг-трекерах и у конечных пользователей. В этой статье мы подробно разберем, как элегантно и надежно экспортировать версию из конфигурации Poetry в код вашего пакета.

1. Проблема дублирования версий и философия Single Source of Truth

Рассмотрим типичную структуру Python-проекта, управляемого Poetry:

my_package/
├── my_package/
│ ├── __init__.py
│ └── main.py
├── pyproject.toml
└── README.md

В файле pyproject.toml версия определяется в секции метаданных:

[tool.poetry]
name = "my_package"
version = "1.4.2"
description = "Полезный инструмент для автоматизации"
authors = ["Иван Иванов <ivan@example.com>"]

Если мы хотим, чтобы наше приложение могло выводить свою версию (например, при вызове команды my-package --version), нам необходимо как-то прочитать это значение. Исторически разработчики просто создавали переменную в коде:

my_package/__init__.py
__version__ = "1.4.2"

Такой подход порождает серьезные проблемы:

  • Человеческий фактор: При выпуске новой версии (например, через команду poetry version patch) Poetry обновит только pyproject.toml. Разработчик должен вручную изменить __init__.py. Вероятность забыть об этом крайне высока.
  • Сложность автоматизации: В рамках CI/CD пайплайнов приходится писать дополнительные регулярные выражения для поиска и замены строк в файлах исходного кода.
  • Проблемы с линтерами: Статические анализаторы могут выдавать предупреждения, если переменные версии объявлены некорректно или не импортируются должным образом.

Чтобы избежать этих проблем, нам нужно научить наше приложение динамически извлекать версию, которую Poetry записывает в дистрибутив при сборке пакета.

2. Современный стандарт: Использование importlib.metadata

Начиная с Python 3.8, в стандартную библиотеку вошел модуль importlib.metadata, который позволяет получить доступ к метаданным пакета, включая версию, без необходимости прямого чтения файлов конфигурации.

import importlib.metadata

def get_package_version():
try:
return importlib.metadata.version("my_package")
except importlib.metadata.PackageNotFoundError:
return "Версия не найдена"

print(get_package_version())

Этот подход гарантирует, что версия пакета всегда синхронизирована с версией, указанной в pyproject.toml, без необходимости ручного обновления переменных в коде.