Стандартный способ встраивания версии в пакет python?

Есть ли стандартный способ связать строку версии с пакетом python таким образом, что я мог бы сделать следующее?

import foo print foo.version 

Я бы предположил, что есть какой-то способ получить эти данные без какого-либо дополнительного жесткого кодирования, поскольку в setup.py уже указаны младшие / основные строки. Альтернативное решение, которое я обнаружил, состояло в том, чтобы import __version__ в мою foo/__init__.py а затем создать __version__.py сгенерированный setup.py .

13 Solutions collect form web for “Стандартный способ встраивания версии в пакет python?”

Не прямо ответ на ваш вопрос, но вы должны подумать о том, чтобы назвать его __version__ , а не version .

Это почти квазистандарт. Многие модули в стандартной библиотеке используют __version__ , и это также используется во многих сторонних модулях, поэтому это квазистандарт.

Обычно __version__ является строкой, но иногда она также является поплавком или кортежем.

Изменить: как упоминал С.Лотт (спасибо!), PEP 8 прямо говорит:

Бухгалтерия версий

Если вы должны иметь Subversion, CVS или RCS в исходном файле, сделайте это следующим образом.

  __version__ = "$Revision: 63990 $" # $Source$ 

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

Вы также должны убедиться, что номер версии соответствует формату, описанному в PEP 440 ( PEP 386 – предыдущая версия этого стандарта).

Вот как я это делаю. Преимущества следующего метода:

  1. Он предоставляет атрибут __version__ .

  2. Он предоставляет стандартную версию метаданных. Поэтому он будет обнаружен pkg_resources или другими инструментами, которые анализируют метаданные пакета (EGG-INFO и / или PKG-INFO, PEP 0345).

  3. При создании пакета он не импортирует ваш пакет (или что-то еще), что может вызвать проблемы в некоторых ситуациях. (См. Комментарии ниже о том, какие проблемы это может вызвать.)

  4. Существует только одно место, где записывается номер версии, поэтому есть только одно место, чтобы изменить его при изменении номера версии, и вероятность появления несогласованных версий меньше.

Вот как это работает: «одно каноническое место» для хранения номера версии – это .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 передает эту строку в качестве значения аргумента «version» для setup() , тем самым удовлетворяя функцию 2.

Чтобы удовлетворить функцию 1, вы можете иметь свой пакет (во время выполнения, а не во время установки!) Импортировать файл myniftyapp/__init__.py из myniftyapp/__init__.py следующим образом:

 from _version import __version__ 

Вот пример этой техники, которую я использую годами.

Код в этом примере немного сложнее, но упрощенный пример, который я написал в этом комментарии, должен быть полной реализацией.

Вот пример кода импорта версии .

Если вы видите что-то не так с этим подходом, пожалуйста, дайте мне знать.

Переписано 2017-05

После более чем десяти лет написания кода Python и управления различными пакетами я пришел к выводу, что DYI, возможно, не самый лучший подход.

Я начал использовать пакет 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__, ... 

Если вы знаете другой подход, который, кажется, лучше, дайте мне знать.

Для отложенного PEP 396 (номера версии модуля) существует предлагаемый способ сделать это. Он описывает, с обоснованием, (по общему признанию, необязательный) стандарт для модулей, которым нужно следовать. Вот фрагмент:

3) Если модуль (или пакет) содержит номер версии, версия ДОЛЖНА быть доступна в __version__ .

4) Для модулей, которые живут внутри пакета пространства имен, модуль ДОЛЖЕН включать атрибут __version__ . Сам пакет пространства имен НЕ ДОЛЖЕН включать свой собственный атрибут __version__ .

5) __version__ атрибута __version__ СЛЕДУЕТ быть строкой.

Хотя это, вероятно, слишком поздно, есть немного более простая альтернатива предыдущему ответу:

 __version_info__ = ('1', '2', '3') __version__ = '.'.join(__version_info__) 

(И было бы довольно просто преобразовать автоматически увеличивающиеся части номеров версий в строку с помощью str() .)

Конечно, из того, что я видел, люди обычно используют что-то вроде ранее упомянутой версии при использовании __version_info__ и, как таковой, хранят ее как набор int; однако я не совсем понимаю это, так как я сомневаюсь, что есть ситуации, когда вы выполняете математические операции, такие как сложение и вычитание на части номеров версий для любых целей, кроме любопытства или автоинкрементации (и даже тогда, 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

  1. Вставьте версию в setup.py и setup.py модуль (например, version.py ), содержащий только информацию о версии, импортированную вашим пакетом, или

  2. Обратное: поместите информацию о версии в свой пакет и импортируйте это, чтобы установить версию в setup.py

Я также видел другой стиль:

 >>> django.VERSION (1, 1, 0, 'final', 0) 

Также стоит отметить, что __version__ является полу-std. в python, так __version_info__ который является кортежем, в простых случаях вы можете просто сделать что-то вроде:

 __version__ = '1.2.3' __version_info__ = tuple([ int(num) for num in __version__.split('.')]) 

… и вы можете получить строку __version__ из файла или что угодно.

стрелка обрабатывает его интересным способом.

В 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__'), # [...] ) 

Многие из этих решений здесь игнорируют теги версии git которые по-прежнему означают, что вам нужно отслеживать версию в нескольких местах (плохая). Я подошел к этому со следующими целями:

  • Вывести все ссылки на версию python из тега в git repo
  • Автоматизируйте шаги git tag / push и setup.py upload с помощью одной команды, которая не принимает никаких входов.

Как это работает:

  1. Из команды make release последняя помеченная версия в git repo найдена и увеличена. Тег возвращается в origin .

  2. src/_version.py Makefile хранит версию в src/_version.py где он будет считываться setup.py и также включен в версию. Не проверяйте _version.py в исходное управление!

  3. Команда 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)"' > $@ .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 который проверяется в корне репо

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

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 

Для чего стоит, если вы используете NumPy distutils, numpy.distutils.misc_util.Configuration имеет метод make_svn_version_py() который встраивает номер версии внутри package.__svn_version__ в переменную version .

Если вы используете 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) 
  • Как экспортировать std :: vector
  • Возвращает элемент DataFrame с использованием частичного совпадения строк в строках pandas python
  • Как я могу разбить строку математических выражений в python?
  • Как форматировать строку, используя словарь в python-3.x?
  • Есть ли способ сравнить арабских персонажей без учета их начальной / медиальной / окончательной формы?
  • Форматирование строки Python - старое `%` vs new `str.format`
  • Ускорьте миллионы регулярных выражений в Python 3
  • SWIG-упаковка C ++ для Python: перевод списка строк в вектор STL строк STL
  • Python - лучший язык программирования в мире.