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



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



import foo
print foo.version


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

769   14  

14 ответов:

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

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

как правило, __version__ - это строка, но иногда это также поплавок или кортеж.

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

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

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

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

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

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

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

  1. обеспечивает .

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

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

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

вот как это работает: "одно каноническое место" для хранения номера версии-это файл .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

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

  2. обратное: поместите информацию о версии в сам пакет и импортируйте это чтобы установить версию в 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 шаги с одной командой, которая не принимает никаких входов.

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

  1. С , в последняя помеченная версия в репозитории git найдена и увеличена. Тег возвращается в origin.

  2. The 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/(.*)-.*//')
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

The release target всегда увеличивает 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

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

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

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

на 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 .

  1. использовать только с __version__ = <VERSION> param в файле. В импорт __version__ param и положить его значение в такой: version=__version__
  2. другой способ-использовать просто С 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

    Ничего не найдено.