Стандартный способ встраивания версии в пакет python?
есть ли стандартный способ связать строку версии с пакетом python таким образом, чтобы я мог сделать следующее?
import foo
print foo.version
Я бы предположил, что есть какой-то способ получить эти данные без какого-либо дополнительного жесткого кодирования, поскольку младшие/главные строки указаны в setup.py уже. Альтернативное решение, которое я нашел было import __version__ в своем foo/__init__.py а потом __version__.py создается с помощью setup.py.
14 ответов:
не является прямым ответом на ваш вопрос, но вы должны рассмотреть возможность назвать его
__version__, а неversion.Это практически стандарт. Многие модули в стандартной библиотеке
__version__, и это также используется в много сторонних модулей, так что это квази-стандарт.как правило,
__version__- это строка, но иногда это также поплавок или кортеж.Edit: как упоминалось С. Лотт (спасибо!),PEP 8 говорит он в явном виде:
Версия Бухгалтерии
если у вас есть Subversion, CVS или RCS crud в исходном файле, сделайте это следующим образом.
__version__ = "$Revision: 63990 $" # $Source$эти строки должны быть включены после docstring модуля, прежде чем любой другой код, разделенный пустой строкой выше и ниже.
вы также должны убедиться, что номер версии соответствует формату, описанному в PEP 440 ( PEP 386 a предыдущая версия этого стандарта).
Я использую один
_version.pyфайл как "когда-то пушечное место" для хранения информации о версии:
обеспечивает .
он предоставляет стандартную версию метаданных. Поэтому он будет обнаружен
pkg_resourcesили другие инструменты, которые анализируют метаданные пакета (EGG-INFO и/или PKG-INFO, PEP 0345).он не импортирует ваш пакет (или что-нибудь еще) при создании вашего пакета, который может вызвать проблемы в некоторых ситуациях. (См. комментарии ниже о том, какие проблемы это может вызвать.)
есть только одно место, где записывается номер версии, поэтому есть только одно место, чтобы изменить его при изменении номера версии, и есть меньше шансов несогласованных версий.
вот как это работает: "одно каноническое место" для хранения номера версии-это файл .py с именем "_version.py" который находится в вашем Питоне пакет, например в
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 передает эту строку в качестве значения аргумента "version" в
setup(), таким образом удовлетворяя особенность 2.удовлетворить характеристика 1, Вы можете иметь свой пакет (во время выполнения, а не во время установки!) импортируйте файл _version из
myniftyapp/__init__.pyтакой:from _version import __version__здесь пример этой техники что я использовал в течение многих лет.
код в этом примере немного сложнее, но упрощенный пример, который я написал в этом комментарии, должен быть полной реализацией.
здесь пример кода импорта версия.
если вы видите что-то неправильно с этим подходом, пожалуйста, дайте мне знать.
переписано 2017-05
после более чем десяти лет написания кода Python и управления различными пакетами я пришел к выводу, что DIY, возможно, не лучший подход.
я начал использовать
pbrпакет для работы с версиями в моих пакетах. Если вы используете git в качестве SCM, это будет вписываться в ваш рабочий процесс, как магия, экономя ваши недели работы (вы будете удивлены тем, насколько сложной может быть проблема).на сегодняшний день pbr is рейтинг #11 наиболее часто используемый пакет python и достижение этого уровня не включало никаких грязных трюков: было только одно: исправление общей проблемы упаковки очень простым способом.
pbrсмогите сделать больше из тяготы обслуживания пакета, не ограничено к versioning но оно не принуждает вас принять все свои преимущества.Итак, чтобы дать вам представление о том, как это выглядит, чтобы принять pbr в одном фиксации есть взгляд swiching упаковки ПБР
Вероятно, вы бы заметили, что версия вообще не хранится в репозитории. 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__, ...если вы знаете другой подход, который, кажется, лучше, дайте мне знать.
в отложенные PEP 396 (номера версий модулей) есть предлагаемый способ сделать это. Он описывает, с обоснованием, (по общему признанию, необязательный) стандарт для модулей, чтобы следовать. Вот фрагмент:
3) Если модуль (или пакет) содержит номер версии, версия должна быть доступна в .
4) Для модулей, которые живут внутри пакета пространства имен, модуль должен включать . Этот сам пакет пространства имен не должен включать свой собственный .
5) Элемент
__version__значение атрибута должно быть строкой.
хотя это, вероятно, слишком поздно, есть немного более простая альтернатива предыдущему ответу:
__version_info__ = ('1', '2', '3') __version__ = '.'.join(__version_info__)(и было бы довольно просто преобразовать автоматически увеличивающиеся части номеров версий в строку с помощью
str().)конечно, из того, что я видел, люди, как правило, используют что-то вроде ранее упомянутой версии при использовании
__version_info__, и как таковой хранить его как кортеж ints; однако, я не совсем вижу смысла в этом, как я сомневаюсь есть ситуации, когда вы будете выполнять математические операции, такие как сложение и вычитание на части номеров версий для любых целей, кроме любопытства или автоматического увеличения (и даже тогда,int()иstr()можно использовать довольно легко). (С другой стороны, существует возможность того, что чей-то код ожидает числовой Кортеж, а не строковый кортеж и, таким образом, терпит неудачу.)это, конечно, моя собственная точка зрения, и я с удовольствием хотел бы, чтобы другие внесли свой вклад в использование числовой кортеж.
как напомнил мне шези, (лексические) сравнения числовых строк не обязательно имеют тот же результат, что и прямые числовые сравнения; для этого потребуются ведущие нули. Так что в конце концов, хранение
__version_info__(или как бы это ни называлось) как кортеж целочисленных значений позволит более эффективно сравнивать версии.
Я использую 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, потому что я могу автоматизировать управление метаданными.
похоже, что нет стандартного способа встроить строку версии в пакет python. Большинство пакетов, которые я видел, используют какой-то вариант вашего решения, т. е. eitner
встроить версию в
setup.pyиsetup.pyсоздать модуль (например,version.py) содержит только информацию о версии, которая импортируется вашим пакетом, илиобратное: поместите информацию о версии в сам пакет и импортируйте это чтобы установить версию в
setup.py
также стоит отметить, что как
__version__будучи полу-std. в python так есть__version_info__, который является кортежем, в простых случаях вы можете просто сделать что-то вроде:__version__ = '1.2.3' __version_info__ = tuple([ int(num) for num in __version__.split('.')])...и вы можете получить
__version__строка из файла или что-то еще.
многие из этих решений игнорировать
gitверсия теги, которые по-прежнему означает, что вы должны отслеживать версию в нескольких местах (плохо). Я подошел к этому со следующими целями:
- вывести все ссылки на версии python из тега в
gitРЕПО- автоматизация
git tag/pushиsetup.py uploadшаги с одной командой, которая не принимает никаких входов.как работает:
С , в последняя помеченная версия в репозитории git найдена и увеличена. Тег возвращается в
origin.The
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/(.*)-.*//') 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)"' > $@ .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 --tagsThe
releasetarget всегда увеличивает 3-ю цифру версии, но вы можете использоватьnext_minor_verилиnext_major_verдля увеличения других цифр. Команды полагаются наversionbump.pyскрипт, который проверяется в корень РЕПО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__.py
The
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
стрелка обрабатывает его интересным способом.
на
arrow/__init__.py:__version__ = '0.8.0' VERSION = __version__на
setup.py:def grep(attrname): pattern = r"{0}\W*=\W*'([^']+)'".format(attrname) strval, = re.findall(pattern, file_text) return strval setup( name='arrow', version=grep('__version__'), # [...] )
для чего это стоит, если вы используете numpy distutils,
numpy.distutils.misc_util.Configurationестьmake_svn_version_py()метод, который вставляет номер ревизии внутриpackage.__svn_version__в переменнойversion.
- использовать только с
__version__ = <VERSION>param в файле. В импорт__version__param и положить его значение в такой:version=__version__- другой способ-использовать просто С
version=<CURRENT_VERSION>- CURRENT_VERSION жестко закодирован.поскольку мы не хотим вручную изменять версию в файле каждый раз, когда мы создаем новый тег (готовый к выпуску новой версии пакета), мы можем использовать следующий..
рекомендую bumpversion. Я использую его в течение многих лет, чтобы поднять версию.
начните с добавления
version=<VERSION>наsetup.pyфайл, если у вас его еще нет.вы должны использовать короткий сценарий, как это каждый раз, когда вы bump версия:
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>]Примечание:
- вы можете использовать
Если вы используете 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)
Comments