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

StackOverflow https://stackoverflow.com/questions/458550

  •  19-08-2019
  •  | 
  •  

Вопрос

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

import foo
print foo.version

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

Это было полезно?

Решение

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

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

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

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

  

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

     

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

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

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

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

Другие советы

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

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

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

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

  • Существует только одно место, в котором записан номер версии, поэтому есть только одно место, где его можно изменить при изменении номера версии, и меньше вероятность несовместимости версий.

  • Вот как это работает: & "одно каноническое место &"; Для хранения номера версии используется файл .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, вы можете сделать так, чтобы ваш пакет (во время выполнения, а не во время установки!) импортировал файл _version из myniftyapp/__init__.py следующим образом:

    from _version import __version__
    

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

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

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

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

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

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

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

    На сегодняшний день pbr занимает 11-е место по популярности среди пакетов python , и достижение этого уровня не не включал в себя никаких подвохов: было только одно: исправить распространенную проблему упаковки очень простым способом.

    yourpackage/version.py может сделать больше бремени обслуживания пакета, не ограничиваясь версионированием, но не заставляет вас использовать все его преимущества.

    Чтобы дать вам представление о том, как выглядит использование pbr в одном коммите, посмотрите swing упаковка в pbr

    Вероятно, вы заметили, что версия вообще не хранится в хранилище. PBR обнаруживает его по веткам и тегам Git.

    Не нужно беспокоиться о том, что происходит, когда у вас нет git-репозитория, потому что pbr выполняет " compile " и кэшируйте версию, когда вы упаковываете или устанавливаете приложения, чтобы не было зависимости времени выполнения от git.

    Старое решение

    Вот лучшее решение, которое я когда-либо видел, и оно также объясняет, почему:

    Внутри yourpackage/__init__.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'
    

    Внутри setup.py:

    from .version import __version__
    

    Внутри <=>:

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

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

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

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

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

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

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

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

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

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

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

    <Ч>

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

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

    Многие из этих решений здесь игнорируют 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 target всегда увеличивает третью цифру версии, но вы можете использовать next_minor_ver или next_major_ver для увеличения остальных цифр.Команды полагаются на versionbump.py скрипт, который проверяется в корне репо

    версияbump.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
    

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

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

  • Обратное: поместите информацию о версии в сам пакет и импортируйте , что , чтобы установить версию в <=>

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

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

    ... и вы можете получить строку <=> из файла или чего-либо еще.

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

    >>> 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.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, без него работать не будет.

    Если вы используете 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)
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top