Стандартный способ встраивания версии в пакет Python? - PullRequest
222 голосов
/ 19 января 2009

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

import foo
print foo.version

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

Ответы [ 14 ]

110 голосов
/ 20 января 2009

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

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

Обычно __version__ - это строка, но иногда это также число с плавающей точкой или кортеж.

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

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

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

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

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

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

98 голосов
/ 16 августа 2011

Я использую один _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__

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

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

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

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

90 голосов
/ 18 апреля 2013

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

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

25 голосов
/ 11 апреля 2013

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

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

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

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

21 голосов
/ 15 июля 2009

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

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

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

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

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


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

10 голосов
/ 13 августа 2017

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

  • Извлечение всех ссылок на версии Python из тега в git repo
  • Автоматизируйте 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. Ру

Файл 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
10 голосов
/ 26 января 2014

Я использую файл 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 голосов
/ 19 января 2009

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

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

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

6 голосов
/ 21 января 2009

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

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

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

4 голосов
/ 11 октября 2009

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

>>> django.VERSION
(1, 1, 0, 'final', 0)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...