Стандартный способ встраивания версии в пакет python?
Есть ли стандартный способ связать строку версии с пакетом python таким образом, что я мог бы сделать следующее?
import foo
print foo.version
Я бы предположил, что есть способ извлечь эти данные без какого-либо дополнительного жесткого кодирования, поскольку в setup.py
уже указаны младшие/основные строки. Альтернативное решение, которое я нашел, должно было иметь import __version__
в моем foo/__init__.py
, а затем __version__.py
, сгенерированное setup.py
.
Ответы
Ответ 1
Непосредственно ответ на ваш вопрос, но вы должны рассмотреть его именование __version__
, а не version
.
Это почти квазистандарт. Многие модули стандартной библиотеки используют __version__
, и это также используется в lots сторонних модулей, поэтому это квазистандарт.
Обычно __version__
является строкой, но иногда она также является поплавком или кортежем.
Изменить: как упоминал С.Лотт (спасибо!), PEP 8 говорит это явно:
Ведение журнала версий
Если вы должны иметь Subversion, CVS или RCS crud в исходном файле, сделайте это следующим образом.
__version__ = "$Revision: 63990 $"
# $Source$
Эти строки должны быть включены после модуля docstring, прежде любой другой код, разделенный пустой строкой сверху и снизу.
Вы также должны убедиться, что номер версии соответствует формату, описанному в PEP 440 (PEP 386 предыдущая версия этого стандарта).
Ответ 2
Я использую один файл _version.py
в качестве "неканонического места" для хранения информации о версии:
Он предоставляет атрибут __version__
.
Он предоставляет стандартную версию метаданных. Поэтому он будет обнаружен pkg_resources
или другими инструментами, которые анализируют метаданные пакета (EGG-INFO и/или PKG-INFO, PEP 0345).
Он не импортирует ваш пакет (или что-либо еще) при сборке пакета, что может вызвать проблемы в некоторых ситуациях. (См. комментарии ниже о том, какие проблемы это может вызвать.)
Номер версии записывается только в одном месте, поэтому есть только одно место, где его можно изменить при изменении номера версии, и меньше вероятность несовместимости версий.
Вот как это работает: "одним каноническим местом" для хранения номера версии является файл .py с именем "_version.py", который находится в вашем пакете Python, например, в myniftyapp/_version.py
. Этот файл является модулем Python, но ваш setup.py не импортирует его! (Это победит функцию 3.) Вместо этого ваш setup.py знает, что содержимое этого файла очень просто, что-то вроде:
__version__ = "3.6.5"
Итак, ваш setup.py открывает файл и анализирует его с помощью следующего кода:
import re
VERSIONFILE="myniftyapp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
verstr = mo.group(1)
else:
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
Затем ваш setup.py передает эту строку в качестве значения аргумента "версия" в setup()
, удовлетворяя тем самым функцию 2.
Чтобы удовлетворить функцию 1, вы можете сделать так, чтобы ваш пакет (во время выполнения, а не во время установки!) Импортировал файл _version из myniftyapp/__init__.py
следующим образом:
from _version import __version__
Вот пример этой техники, которой я пользуюсь годами.
Код в этом примере немного сложнее, но упрощенный пример, который я написал в этом комментарии, должен быть полной реализацией.
Вот пример кода импорта версии.
Если вы видите что-то не так с этим подходом, пожалуйста, дайте мне знать.
Ответ 3
Переписано 2017-05-05
После более чем десяти лет написания кода на Python и управления различными пакетами я пришел к выводу, что DIY, возможно, не лучший подход.
Я начал использовать пакет pbr
для работы с версиями в моих пакетах. Если вы используете git в качестве SCM, это будет вписываться в ваш рабочий процесс, как волшебство, сохраняя ваши недели работы (вы будете удивлены, насколько сложной может быть проблема).
На сегодняшний день pbr занимает 11-е место по популярности среди пакетов python, и достижение этого уровня не включало в себя никаких хитростей: было только одно: очень просто решить обычную проблему с упаковкой.
pbr
может сделать больше бремени обслуживания пакета, не ограничиваясь версионированием, но не заставляет вас использовать все его преимущества.
Итак, чтобы дать вам представление о том, как выглядит использование pbr в одном коммите, посмотрите упаковку в pbr
Вероятно, вы заметили, что версия не хранится вообще в хранилище. PBR обнаруживает его по веткам и тегам Git.
Не нужно беспокоиться о том, что происходит, когда у вас нет репозитория git, потому что pbr "компилирует" и кэширует версию, когда вы упаковываете или устанавливаете приложения, поэтому нет зависимости во время выполнения от git.
Старое решение
Вот лучшее решение, которое я когда-либо видел, и оно также объясняет, почему:
Внутри yourpackage/version.py
:
# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.12'
Внутри yourpackage/__init__.py
:
from .version import __version__
Внутри setup.py
:
exec(open('yourpackage/version.py').read())
setup(
...
version=__version__,
...
Если вы знаете другой подход, который кажется лучше, дайте мне знать.
Ответ 4
Согласно отложенному PEP 396 (номерам версий модулей), есть предложенный способ сделать это. Он описывает с обоснованием (по общему признанию необязательный) стандарт для модулей, которым необходимо следовать. Вот фрагмент:
3) Если модуль (или пакет) включает номер версии, версия ДОЛЖНА быть доступна в атрибуте __version__
.
4) Для модулей, которые находятся внутри пакета пространства имен, модуль ДОЛЖЕН включать атрибут __version__
. Сам пакет пространства имен НЕ ДОЛЖЕН включать собственный атрибут __version__
.
5) Значение атрибута __version__
ДОЛЖНО быть строкой.
Ответ 5
Хотя это, вероятно, слишком поздно, есть немного более простая альтернатива предыдущему ответу:
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(И было бы довольно просто преобразовать автоматически увеличивающиеся части номеров версий в строку с помощью str()
.)
Конечно, из того, что я видел, люди обычно используют что-то вроде ранее упомянутой версии при использовании __version_info__
, и, как таковой, хранят его как набор int; однако я не совсем понимаю это, так как я сомневаюсь, что есть ситуации, когда вы выполняете математические операции, такие как сложение и вычитание на части номеров версий для любых целей, кроме любопытства или автоинкрементации (и даже тогда, int()
и str()
можно использовать довольно легко). (С другой стороны, есть вероятность, что кто-то другой код ожидает числовой кортеж, а не строковый кортеж и, следовательно, не работает.)
Это, конечно, мой собственный взгляд, и я с удовольствием хотел бы, чтобы другие использовали ввод числового кортежа.
Как напомнил мне Шези, (лексические) сравнения числовых строк не обязательно имеют тот же результат, что и прямые численные сравнения; для этого потребуются ведущие нули. Поэтому, в конце концов, сохранение __version_info__
(или того, что он будет вызывать) как кортеж целочисленных значений, позволит более эффективно сравнивать версии.
Ответ 6
Я использую JSON файл в каталоге dir. Это соответствует требованиям Zooko.
Внутри pkg_dir/pkg_info.json
:
{"version": "0.1.0"}
Внутри setup.py
:
from distutils.core import setup
import json
with open('pkg_dir/pkg_info.json') as fp:
_info = json.load(fp)
setup(
version=_info['version'],
...
)
Внутри pkg_dir/__init__.py
:
import json
from os.path import dirname
with open(dirname(__file__) + '/pkg_info.json') as fp:
_info = json.load(fp)
__version__ = _info['version']
Я также помещал другую информацию в pkg_info.json
, как и автор. я
как использовать JSON, потому что я могу автоматизировать управление метаданными.
Ответ 7
Многие из этих решений здесь игнорируют теги версии git
, которые все еще означают, что вам нужно отслеживать версию в нескольких местах (плохая). Я подошел к этому со следующими целями:
- Вывести все ссылки на версию python из тега в
git
repo
- Автоматизация шагов
git tag
/push
и setup.py upload
с помощью одной команды, которая не принимает никаких входов.
Как это работает:
-
С помощью команды make release
последняя помеченная версия в репозитории git найдена и увеличена. Тег возвращается к origin
.
-
Makefile
хранит версию в src/_version.py
, где она будет считана setup.py
и также включена в выпуск. Не проверять _version.py
на исходный контроль!
-
setup.py
команда считывает строку новой версии из package.__version__
.
Детали:
Makefile
# remove optional 'v' and trailing hash "v1.0-N-HASH" -> "v1.0-N"
git_describe_ver = $(shell git describe --tags | sed -E -e 's/^v//' -e 's/(.*)-.*/\1/')
git_tag_ver = $(shell git describe --abbrev=0)
next_patch_ver = $(shell python versionbump.py --patch $(call git_tag_ver))
next_minor_ver = $(shell python versionbump.py --minor $(call git_tag_ver))
next_major_ver = $(shell python versionbump.py --major $(call git_tag_ver))
.PHONY: ${MODULE}/_version.py
${MODULE}/_version.py:
echo '__version__ = "$(call git_describe_ver)"' > [email protected]
.PHONY: release
release: test lint mypy
git tag -a $(call next_patch_ver)
$(MAKE) ${MODULE}/_version.py
python setup.py check sdist upload # (legacy "upload" method)
# twine upload dist/* (preferred method)
git push origin master --tags
Цель release
всегда увеличивает цифру третьей версии, но вы можете использовать next_minor_ver
или next_major_ver
для увеличения других цифр. Команды полагаются на versionbump.py
script, который проверяется в корне репо
versionbump.py
"""An auto-increment tool for version strings."""
import sys
import unittest
import click
from click.testing import CliRunner # type: ignore
__version__ = '0.1'
MIN_DIGITS = 2
MAX_DIGITS = 3
@click.command()
@click.argument('version')
@click.option('--major', 'bump_idx', flag_value=0, help='Increment major number.')
@click.option('--minor', 'bump_idx', flag_value=1, help='Increment minor number.')
@click.option('--patch', 'bump_idx', flag_value=2, default=True, help='Increment patch number.')
def cli(version: str, bump_idx: int) -> None:
"""Bumps a MAJOR.MINOR.PATCH version string at the specified index location or 'patch' digit. An
optional 'v' prefix is allowed and will be included in the output if found."""
prefix = version[0] if version[0].isalpha() else ''
digits = version.lower().lstrip('v').split('.')
if len(digits) > MAX_DIGITS:
click.secho('ERROR: Too many digits', fg='red', err=True)
sys.exit(1)
digits = (digits + ['0'] * MAX_DIGITS)[:MAX_DIGITS] # Extend total digits to max.
digits[bump_idx] = str(int(digits[bump_idx]) + 1) # Increment the desired digit.
# Zero rightmost digits after bump position.
for i in range(bump_idx + 1, MAX_DIGITS):
digits[i] = '0'
digits = digits[:max(MIN_DIGITS, bump_idx + 1)] # Trim rightmost digits.
click.echo(prefix + '.'.join(digits), nl=False)
if __name__ == '__main__':
cli() # pylint: disable=no-value-for-parameter
Это делает тяжелый подъем, как обрабатывать и увеличивать номер версии от git
.
__ __ INIT. Ру
Файл my_module/_version.py
импортируется в my_module/__init__.py
. Поместите любую статическую конфигурацию установки, которую вы хотите распространять вместе с вашим модулем.
from ._version import __version__
__author__ = ''
__email__ = ''
setup.py
Последний шаг - прочитать информацию о версии из модуля my_module
.
from setuptools import setup, find_packages
pkg_vars = {}
with open("{MODULE}/_version.py") as fp:
exec(fp.read(), pkg_vars)
setup(
version=pkg_vars['__version__'],
...
...
)
Конечно, для всего этого вам нужно будет иметь хотя бы один тег версии в вашем репо, чтобы начать.
git tag -a v0.0.1
Ответ 8
Кажется, что нет стандартного способа встраивания строки версии в пакет python. Большинство пакетов, которые я видел, используют какой-то вариант вашего решения, т.е. Eitner
-
Вставьте версию в setup.py
и создайте setup.py
модуль (например, version.py
), содержащий только информацию о версии, импортированную вашим пакетом, или
-
Обратное: поместите информацию о версии в свой пакет и импортируйте это, чтобы установить версию в setup.py
Ответ 9
Также стоит отметить, что __version__
является полу-std. в python, так что __version_info__
, который является кортежем, в простых случаях вы можете просто сделать что-то вроде:
__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])
... и вы можете получить строку __version__
из файла или что-то еще.
Ответ 10
Я также видел другой стиль:
>>> django.VERSION
(1, 1, 0, 'final', 0)
Ответ 11
стрелка обрабатывает это интересным способом.
Сейчас (с 2e5031b)
В arrow/__init__.py
:
__version__ = 'x.y.z'
В setup.py
:
from arrow import __version__
setup(
name='arrow',
version=__version__,
# [...]
)
Перед
В arrow/__init__.py
:
__version__ = 'x.y.z'
VERSION = __version__
В setup.py
:
def grep(attrname):
pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
strval, = re.findall(pattern, file_text)
return strval
file_text = read(fpath('arrow/__init__.py'))
setup(
name='arrow',
version=grep('__version__'),
# [...]
)
Ответ 12
Для чего стоит, если вы используете NumPy distutils, numpy.distutils.misc_util.Configuration
имеет make_svn_version_py()
метод, который включает номер версии внутри package.__svn_version__
в переменной version
.
Ответ 13
- Используйте файл
version.py
только с параметром __version__ = <VERSION>
в файле. В файле setup.py
импортируйте параметр __version__
и поместите его значение в файл setup.py
следующим образом:version=__version__
- Другой способ - использовать только файл
setup.py
с version=<CURRENT_VERSION>
- код CURRENT_VERSION жестко закодирован.
Поскольку мы не хотим вручную изменять версию в файле каждый раз, когда создаем новый тег (готовый выпустить новую версию пакета), мы можем использовать следующее.
Я настоятельно рекомендую пакет bumpversion. Я использовал его в течение многих лет, чтобы поднять версию.
начните с добавления version=<VERSION>
в файл setup.py
, если у вас его еще нет.
Вы должны использовать такой короткий сценарий каждый раз, когда сталкиваетесь с версией:
bumpversion (patch|minor|major) - choose only one option
git push
git push --tags
Затем добавьте один файл для каждого репо с именем: .bumpversion.cfg
:
[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]
Примечание:
-
Вы можете использовать параметр
__version__
в файле version.py
, как это было предложено в других публикациях, и обновить файл bumpversion следующим образом:
[bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
- Вы должны
git commit
или git reset
все в своем репо, иначе вы получите грязную ошибку репо.
- Убедитесь, что ваша виртуальная среда включает в себя пакет bumpversion, без него он не будет работать.
Ответ 14
Если вы используете CVS (или RCS) и хотите быстрое решение, вы можете использовать:
__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])
(Конечно, номер версии будет заменен вами CVS.)
Это дает вам версию для печати и информацию о версии, которую вы можете использовать для проверки того, что импортируемый модуль имеет как минимум ожидаемую версию:
import my_module
assert my_module.__version_info__ >= (1, 1)