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

265

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

import foo
print foo.version

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

Дмитрий Ткачук
источник
7
К вашему сведению, очень хороший обзор можно найти по адресу: packaging.python.org/en/latest/…
ionelmc
1
Версию установленного пакета можно получить из метаданных с помощью setuptools , поэтому во многих случаях достаточно указать только версию setup.py. Смотрите этот вопрос .
saaj
2
К вашему сведению, существует в основном 5 общих шаблонов для поддержания единого источника правды (как при настройке, так и во время выполнения) для номера версии.
КФ Лин
@ionelmc Документация Python содержит 7 различных опций для односоставных материалов . Разве это не противоречит концепции « единого источника правды »?
Stevoisiak
@StevenVascellaro не уверен, что вы спрашиваете. Там перечислено так много способов, потому что руководство по упаковке не хочет быть самоуверенным.
ionelmc

Ответы:

136

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

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

Обычно __version__это строка, но иногда это также float или кортеж.

Отредактируйте: как упомянуто С.Лоттом (Спасибо!), PEP 8 говорит это явно

Имена Dunder уровня модуля

Модуль уровня «dunders» (то есть имена с двумя ведущими и двумя завершающими подчеркиваний) , такие как __all__, __author__, __version__и т.д. , должен быть помещены после модуля , но перед строкой документации любых операторов импорта , за исключением от __future__импорта.

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

oefe
источник
9
Он должен быть строкой и иметь version_info для версии кортежа.
Джеймс Антилл
Джеймс: Почему __version_info__конкретно? (Который «придумывает» ваше собственное двойное подчеркивание.) [Когда Джеймс комментировал, подчеркивания ничего не делали в комментариях, теперь они указывают на акцент, поэтому Джеймс действительно написал __version_info__тоже. --- ред.]
Что-то о том, что должна сказать версия, вы можете увидеть по адресу packages.python.org/distribute/… Эта страница посвящена распространению, но значение номера версии становится стандартом де-факто.
sienkiew
2
Правильно. Кажется, что эти ПКП противоречат друг другу. Что ж, в PEP 8 написано «if» и «crud», поэтому он не поддерживает использование расширения ключевых слов VCS. Кроме того, если вы когда-либо переключитесь на другую VCS, вы потеряете информацию о ревизии. Поэтому я бы предложил использовать информацию о версии, совместимую с PEP 386/440, встроенную в один исходный файл, по крайней мере, для более крупных проектов.
oefe
2
Где бы вы положили эту версию . Учитывая, что это принятая версия, я хотел бы увидеть эту дополнительную информацию здесь.
даркгейм
120

Я использую один _version.pyфайл как «некогда каноническое место» для хранения информации о версии:

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

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

from _version import __version__

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

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

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

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

Zooko
источник
8
Не могли бы вы описать проблемы, которые мотивируют № 3? Глиф сказал, что это как-то связано с «setuptools любит притворяться, что ваш код отсутствует в системе при запуске setup.py», но детали помогут убедить меня и других.
Иван Козик
2
@Iva Теперь, в каком порядке инструмент должен делать это? Он не может (в современной системе setuptools / pip / virtualenv) даже не знает, что такое deps , пока не оценит вашу setup.py. Кроме того, если он попытается выполнить полную глубину и выполнить все операции deps, прежде чем он сделает это, он застрянет, если будут циклические операции deps. Но если он попытается собрать этот пакет перед установкой зависимостей, то, если вы импортируете свой пакет из своего setup.py, он не обязательно сможет импортировать свои deps или правильные версии своих deps.
Zooko
3
Не могли бы вы написать файл "version.py" из "setup.py" вместо его анализа? Это кажется проще.
Джонатан Хартли,
3
Джонатан Хартли: Я согласен, что для вашего "setup.py" было бы немного проще написать файл "version.py" вместо его синтаксического анализа, но это откроет окно для несогласованности, когда вы отредактируете свой setup.py иметь новую версию, но еще не запустил setup.py для обновления файла version.py. Другая причина, по которой каноническая версия должна находиться в небольшом отдельном файле, заключается в том, что другим инструментам, таким как инструменты, которые читают ваше состояние контроля версий, проще написать файл версии.
Zooko
3
Аналогичный подход заключается в том, чтобы execfile("myniftyapp/_version.py")изнутри setup.py, а не пытаться анализировать код версии вручную. Предложено в stackoverflow.com/a/2073599/647002 - обсуждение там также может быть полезным.
medmunds
97

Переписан 2017-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__,
    ...

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

Сорин
источник
12
Нет, нет execfile () не существует в Python 3, поэтому лучше использовать exec (open (). read ()).
Кристоф Ву-Брюгье
4
почему не from .version import __version__в setup.py?
апрель
4
@Aprillion Поскольку пакет не загружается во время setup.pyработы - попробуйте, вы получите ошибку (или, по крайней мере, я сделал :-))
darthbith
3
Ссылка на pbr приводит к плохому шлюзу.
MERose
4
pbr , без сомнения, отличный инструмент, но вы не смогли ответить на этот вопрос. Как получить доступ к текущей версии или установленному пакету через bpr .
nad2000
29

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

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

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

5) Значение __version__атрибута ДОЛЖНО быть строкой.

Oddthinking
источник
13
Этот PEP не принимается / не стандартизируется, но откладывается (из-за отсутствия интереса). Поэтому немного вводить в заблуждение утверждать, что « существует стандартный путь », указанный в нем.
ткач
@ ткач: о мой! Я узнал что-то новое. Я не знал, что это то, что мне нужно было проверить.
Нечетное размышление
4
Отредактировано, чтобы отметить, что это не стандарт. Теперь я чувствую себя смущенным, потому что я поднял запросы на функции для проектов, прося их следовать этому "стандарту".
Странное размышление
1
Возможно, вам следует взять на себя работу по стандартизации этого PEP, так как вы, кажется, заинтересованы :)
ткач
Это будет работать для управления версиями отдельного модуля, но я не уверен, что это применимо к управлению версиями всего проекта.
Stevoisiak
21

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

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

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

Конечно, из того, что я видел, люди склонны использовать что-то похожее на ранее упомянутую версию при использовании __version_info__, и поэтому хранят ее как кортеж целых чисел; однако я не совсем вижу в этом смысла, так как сомневаюсь, что бывают ситуации, когда вы выполняете математические операции, такие как сложение и вычитание частей номеров версий для любых целей, кроме любопытства или автоматического увеличения (и даже тогда, int()и str()может быть использован довольно легко). (С другой стороны, существует вероятность того, что чей-то код ожидает числовой кортеж, а не строковый кортеж, и, следовательно, не сможет.)

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


Как Шези напомнил мне, (лексические) сравнения числовых строк не обязательно имеют тот же результат, что и прямые численные сравнения; для этого потребуются ведущие нули. Таким образом, в конце концов, сохранение __version_info__(или как бы это ни называлось) в виде набора целочисленных значений позволило бы более эффективно сравнивать версии.

JAB
источник
12
приятно (+1), но вы бы предпочли числа вместо строк? например__version_info__ = (1,2,3)
orip
3
Сравнение строк может стать опасным, когда номера версий превышают 9, например, «10» <«2».
D Coetzee
13
Я тоже так делаю, но немного подправил для адресации. __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
rh0dium
2
Проблема в __version__ = '.'.join(__version_info__)том, что __version_info__ = ('1', '2', 'beta')станет 1.2.beta, а не 1.2betaили1.2 beta
nagisa
4
Я думаю, что проблема с этим подходом состоит в том, где поместить строки кода, объявляющие __version__. Если они находятся в setup.py, тогда ваша программа не сможет импортировать их из своего пакета. Возможно, это не проблема для вас, в таком случае, хорошо. Если они входят в вашу программу, то ваш setup.py может импортировать их, но это не должно, так как setup.py запускается во время установки, когда зависимости вашей программы еще не установлены (setup.py используется для определения того, что это за зависимости). .) Отсюда и ответ Zooko: вручную проанализировать значение из исходного файла продукта, не импортируя пакет продукта
Джонатан Хартли,
11

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

  • Получите все ссылки на версии Python из тега в gitрепозитории
  • Автоматизация git tag/ pushи setup.py uploadшаги с помощью одной команды, которая не требует ввода.

Как это устроено:

  1. По make releaseкоманде, последняя помеченная версия в git-репо будет найдена и увеличена. Тег отодвинут назад origin.

  2. В 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Цель всегда увеличивает 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

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
cmcginty
источник
1
действительно - весь этот поток забывает, что VCS очень важен в этом обсуждении. просто obs: приращение версии должно оставаться ручным процессом, поэтому предпочтительным способом будет: 1. вручную создать и отправить тег 2. позволить инструментам VCS обнаружить этот тег и сохранить его там, где это необходимо (вау - этот интерфейс редактирования SO действительно мешает мне - Я должен был отредактировать это дюжину раз только для того, чтобы иметь дело с переводами строки, И ЭТО ЕЩЕ НЕ РАБОТАЕТ! @ # $% ^ & *)
axd
Не нужно использовать, versionbump.pyкогда у нас есть замечательный пакет bumpversion для python.
Оран
@Oran Я посмотрел на versionbump. Документы не очень понятны, и они не очень хорошо справляются с тегами. В моем тестировании это, кажется, попадает в состояния, которые вызывают его сбой: subprocess.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / folder / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngbo '] 'возвращен ненулевой статус выхода 1.
cmcginty
1
Почему не _version.pyследует отслеживать контроль версий?
Stevoisiak
1
@StevenVascellaro Это усложняет процесс выпуска. Теперь вы должны убедиться, что ваши теги и коммиты соответствуют значению в _version.py. Использование одного тега версии является более чистым IMO и означает, что вы можете создать релиз непосредственно из пользовательского интерфейса github.
cmcginty
10

Я использую файл 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, потому что я могу автоматизировать управление метаданными.

Энди Ли
источник
Не могли бы вы рассказать, как использовать json для автоматизации управления метаданными? Спасибо!
ryanjdillon
6

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

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

... и вы можете получить __version__строку из файла или что-то еще.

Джеймс Антилл
источник
1
Есть ли у вас какие-либо ссылки / ссылки относительно происхождения этого использования __version_info__?
Крейг МакКуин
3
Ну, это то же самое, что и sys.version для sys.version_info. Итак: docs.python.org/library/sys.html#sys.version_info
Джеймс Антилл
7
Проще сделать сопоставление в другом направлении ( __version_info__ = (1, 2, 3)и __version__ = '.'.join(map(str, __version_info__))).
Эрик О Лебиго
2
@EOL - __version__ = '.'.join(str(i) for i in __version_info__)немного длиннее, но более питонно.
ArtOfWarfare
2
Я не уверен, что то, что вы предлагаете, явно более питонно, так как содержит фиктивную переменную, которая на самом деле не нужна и значение которой трудно выразить ( iне имеет смысла, version_numнемного длинно и неоднозначно ...). Я даже воспринимаю существование map()в Python как сильный намек на то, что его следует использовать здесь, так как здесь нам нужно сделать типичный пример map()использования (использовать с существующей функцией) - я не вижу много других разумных применений.
Эрик О Лебиго
5

Кажется, не существует стандартного способа встроить строку версии в пакет Python. Большинство пакетов, которые я видел, используют какой-то вариант вашего решения, например, eitner.

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

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

дР.
источник
Мне нравится ваша рекомендация, но как сгенерировать этот модуль из setup.py?
сорин
1
Мне нравится идея варианта (1), он кажется более простым, чем ответ Zooko на разбор номера версии из файла. Но вы не можете гарантировать, что version.py будет создан, когда разработчик просто клонирует ваше хранилище. Если вы не зарегистрируетесь в version.py, который открывает небольшую складку, в которой вы можете изменить номер версии в setup.py, зафиксировать, отпустить, а затем придется (косая черта забыть) зафиксировать изменение в version.py.
Джонатан Хартли
Предположительно, вы можете сгенерировать модуль, используя что-то вроде "" "с open (" mypackage / version.py "," w ") как fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "
Джонатан Хартли
1
Я думаю, что вариант 2. подвержен сбоям во время установки, как отмечено в комментариях к ответу JAB.
Джонатан Хартли,
Что об этом? Вы помещаете __version__ = '0.0.1' "(где версия, конечно, строка) в __init__.py" основного пакета вашего программного обеспечения. Затем перейдите к пункту 2: в настройке вы делаете из пакета .__ init__ импорт __version__ как v, а затем вы устанавливаете переменную v в качестве версии вашего setup.py. Затем импортируйте mypack как my, напечатайте my .__ version__ и распечатайте версию. Версия будет храниться только в одном месте, доступном из всего кода, в файле, который не импортирует ничего другого, связанного с программным обеспечением.
Sef
5

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

Сейчас (с 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__'),
    # [...]
)
Анто
источник
что такое file_text?
Ely
2
обновленное решение на самом деле вредно. Когда запущен файл setup.py, он не обязательно увидит версию пакета из локального пути к файлу. Возможно, он вернется к ранее установленной версии, например, из pip install -e .ветки разработки или чего-то еще при тестировании. setup.py абсолютно не должен полагаться на импорт пакета, который находится в процессе установки, чтобы определить параметры для развертывания. Хлоп.
Ely
Да, я не знаю, почему стрелка решила вернуться к этому решению. Более того, сообщение коммита говорит: «Обновлен setup.py с современными стандартами Python » ... An
Anto
4

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

>>> django.VERSION
(1, 1, 0, 'final', 0)
L1ker
источник
1
Да, я тоже видел. Кстати, каждый ответ принимает другой стиль, так что теперь я не знаю, какой стиль является «стандартным». Ищу упомянутых PEPs ...
kbec
Другой способ увидеть; Клиент Python Mongo использует простую версию без подчеркивания. Так что это работает; $ python >>> import pymongo >>> pymongo.version '2.7'
AnneTheAgile
Реализация .VERSIONне означает, что вам не нужно реализовывать __version__.
Acumenus
Требует ли это реализации djangoв проекте?
Stevoisiak
3

Использование setuptoolsиpbr

Не существует стандартного способа управления версиями, но стандартным способом управления вашими пакетами является setuptools.

Лучшее решение, которое я нашел в целом для управления версией, это использовать setuptoolsс pbrрасширением. Теперь это мой стандартный способ управления версией.

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

PBR перемещает большинство метаданных из setup.pyинструментов в setup.cfgфайл, который затем используется в качестве источника для большинства метаданных, которые могут включать версию. Это позволяет упаковать метаданные в исполняемый файл, используя что-то вроде этого pyinstaller(если это так, вам, вероятно, понадобится эта информация ), и отделить метаданные от других сценариев управления / настройки пакетов. Вы можете напрямую обновить строку версии setup.cfgвручную, и она будет перенесена в*.egg-info папку при сборке выпусков вашего пакета. Затем ваши сценарии могут получить доступ к версии из метаданных, используя различные методы (эти процессы описаны в разделах ниже).

При использовании Git для VCS / SCM эта настройка еще лучше, поскольку она будет извлекать большую часть метаданных из Git, так что ваше хранилище может стать вашим основным источником правды для некоторых метаданных, включая версию, авторов, журналы изменений, и т.д. Специально для версии, он создаст строку версии для текущего коммита на основе тегов git в репозитории.

Поскольку PBR будет извлекать версию, автора, журнал изменений и другую информацию непосредственно из вашего git-репо, поэтому некоторые из метаданных setup.cfgмогут быть опущены и автоматически сгенерированы при создании дистрибутива для вашего пакета (с использованием setup.py)

Текущая версия в реальном времени

setuptools будет получать последнюю информацию в режиме реального времени, используя setup.py :

python setup.py --version

Последняя версия будет извлечена из setup.cfgфайла или из репозитория git, основываясь на последней сделанной фиксации и тегах, существующих в репо. Эта команда не обновляет версию в дистрибутиве.

Обновление версии

Когда вы создаете дистрибутив с помощью setup.py(например py setup.py sdist, например), тогда вся текущая информация будет извлечена и сохранена в дистрибутиве. По сути, это запускает setup.py --versionкоманду, а затем сохраняет информацию о версии в package.egg-infoпапке в наборе файлов, в которых хранятся метаданные распространения.

Примечание о процессе обновления метаданных версии:

Если вы не используете pbr для извлечения данных о версии из git, просто обновите файл setup.cfg напрямую, добавив информацию о новой версии (достаточно просто, но убедитесь, что это стандартная часть процесса выпуска).

Если вы используете git и вам не нужно создавать исходный или двоичный дистрибутив (используя python setup.py sdistодну из python setup.py bdist_xxxкоманд), самый простой способ обновить информацию git-репо в вашей <mypackage>.egg-infoпапке метаданных - просто запустить python setup.py installкоманду. Это запустит все функции PBR, связанные с извлечением метаданных из репозитория git, и обновит вашу локальную .egg-infoпапку, установит исполняемые файлы сценариев для всех точек входа, которые вы определили, и другие функции, которые вы увидите в выходных данных при запуске этой команды.

Обратите внимание, что .egg-infoпапка, как правило, исключается из хранилища в самом git-репо в стандартных .gitignoreфайлах Python (например, из Gitignore.IO ), так как она может быть сгенерирована из вашего исходного кода. Если это исключено, убедитесь, что у вас есть стандартный «процесс выпуска», чтобы метаданные обновлялись локально перед выпуском, и любой пакет, который вы загружаете на PyPi.org или распространяете иным образом, должен включать эти данные, чтобы иметь правильную версию. Если вы хотите, чтобы репозиторий Git содержал эту информацию, вы можете исключить игнорирование определенных файлов (т. Е. Добавить !*.egg-info/PKG_INFOв .gitignore).

Доступ к версии из скрипта

Вы можете получить доступ к метаданным из текущей сборки в скриптах Python в самом пакете. Например, для версии есть несколько способов сделать это:

## This one is a new built-in as of Python 3.8.0 should become the standard
from importlib-metadata import version

v0 = version("mypackage")
print('v0 {}'.format(v0))

## I don't like this one because the version method is hidden
import pkg_resources  # part of setuptools

v1 = pkg_resources.require("mypackage")[0].version
print('v1 {}'.format(v1))

# Probably best for pre v3.8.0 - the output without .version is just a longer string with
# both the package name, a space, and the version string
import pkg_resources  # part of setuptools

v2 = pkg_resources.get_distribution('mypackage').version
print('v2 {}'.format(v2))

## This one seems to be slower, and with pyinstaller makes the exe a lot bigger
from pbr.version import VersionInfo

v3 = VersionInfo('mypackage').release_string()
print('v3 {}'.format(v3))

Вы можете поместить один из них непосредственно в свой __init__.pyпакет для извлечения информации о версии следующим образом, аналогично некоторым другим ответам:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version
LightCC
источник
Переформатировано за одно голосование, чтобы прямо сейчас ответить на вопрос.
LightCC
1

После нескольких часов попыток найти простейшее надежное решение, вот части:

создайте файл version.py ВНУТРИ папки вашего пакета "/ mypackage":

# 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__ = '1.2.7'

в setup.py:

exec(open('mypackage/version.py').read())
setup(
    name='mypackage',
    version=__version__,

в основной папке init .py:

from .version import __version__

exec()Функция запускает сценарий вне какого - либо импорта, так как setup.py запускается до модуля может быть импортирован. Вам все еще нужно управлять номером версии в одном файле в одном месте, но, к сожалению, его нет в файле setup.py. (это недостаток, но недостаток импорта - это плюс)

Марк Максмейстер
источник
1

С тех пор, как этот вопрос впервые был задан, была проделана большая работа по унификации версий и поддержке соглашений . Возможные варианты теперь подробно описаны в Руководстве пользователя по упаковке Python . Также следует отметить, что схемы нумерации версий в Python относительно строгие в соответствии с PEP 440 , и поэтому сохранение нормальных вещей крайне важно, если ваш пакет будет выпущен в Cheese Shop .

Вот сокращенная разбивка вариантов управления версиями:

  1. Прочитать файл в setup.py( setuptools ) и получите версию.
  2. Используйте внешний инструмент сборки (для обновления __init__.pyкак исходного кода), например, bump2version , changes или zest.releaser .
  3. Установите значение для __version__глобальной переменной в определенном модуле.
  4. Поместите значение в простой текстовый файл VERSION для файла setup.py и кода для чтения.
  5. Установите значение через setup.pyрелиз и используйте importlib.metadata, чтобы получить его во время выполнения. (Предупреждение, есть версии до 3.8 и после 3.8.)
  6. Установите значение __version__в sample/__init__.pyи импортируйте образец в setup.py.
  7. Используйте setuptools_scm для извлечения версий из системы контроля версий, чтобы это была каноническая ссылка, а не код.

Обратите внимание, что (7) может быть самым современным подходом (метаданные сборки не зависят от кода, публикуемого автоматизацией). Также ОБРАТИТЕ ВНИМАНИЕ, что если для выпуска пакета используется установка, то простой python3 setup.py --versionбудет сообщать о версии напрямую.

ingyhere
источник
-1

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

Matt
источник
Можете ли вы предоставить более подробную информацию или пример того, как это будет работать?
Stevoisiak
Хм. В 2020 году это было (было всегда?) Для Фортрана . Пакет «numpy.distutils является частью NumPy, расширяющей стандартные дистрибутивы Python для работы с исходными текстами на Фортране».
ingyhere
-1
  1. Используйте version.pyфайл только с __version__ = <VERSION>параметром в файле. В setup.pyфайле импортируйте __version__параметр и поместите его значение в setup.pyфайл следующим образом: version=__version__
  2. Другой способ - использовать только setup.pyфайл с version=<CURRENT_VERSION>- CURRENT_VERSION жестко закодирован.

Поскольку мы не хотим вручную изменять версию в файле каждый раз, когда создаем новый тег (готовый выпустить новую версию пакета), мы можем использовать следующее:

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

начните с добавления 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, без него он не будет работать.
Оран
источник
@cmcginty Извините за задержку, пожалуйста, проверьте мой ответ ^^^ - обратите внимание, что вы должны сделать git commit или git reset все в вашем репо и убедиться, что ваша виртуальная среда включает пакет bumpversion, без которого он не будет работать. Используйте последнюю версию.
Оран
Мне немного неясно, какое решение предлагается здесь. Рекомендуете ли вы вручную отслеживать версию version.pyили отслеживать ее bumpversion?
Stevoisiak
@ StevenVascellaro Я предлагаю использовать bumpversion, никогда не используйте ручное управление версиями. Я попытался объяснить, что вы можете указать bumpversion обновить версию в файле setup.py или, что еще лучше, использовать ее для обновления файла version.py. Чаще всего обновляют файл version.py и __version__сохраняют значение параметра в файле setup.py. Мое решение используется в производстве, и это обычная практика. Примечание: просто для ясности, использование bumpversion как части скрипта - лучшее решение, поместите его в свой CI, и это будет автоматическая операция.
Оран
-3

Если вы используете 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)
Мартин Иберт
источник
В каком файле вы рекомендуете сохранить сохранение __version__в? Как можно увеличить номер версии с помощью этого решения?
Stevoisiak