Стандартний спосіб вбудувати версію в пакет python?


264

Чи є стандартний спосіб пов’язати рядок версії з пакетом python таким чином, щоб я міг зробити наступне?

import foo
print foo.version

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


7
FYI, є дуже хороший огляд за адресою: packaging.python.org/uk/latest/…
ionelmc

1
Версію встановленого пакета можна отримати з метаданих за допомогою setuptools , тому в багатьох випадках виставлення лише версії в setup.py. Дивіться це питання .
саай

2
FYI, в основному існує 5 загальних моделей для підтримки єдиного джерела істини (як під час встановлення, так і під час виконання) для номера версії.
КФ Лін

У документації @ionelmc Python перелічено 7 різних варіантів одноразового введення . Чи не суперечить це поняттю " єдиного джерела істини "?
Stevoisiak

@StevenVascellaro не впевнений, про що ви питаєте. Існує так багато способів, перелічених там, тому що керівництво з упаковки не хоче сумніватися.
ionelmc

Відповіді:


136

Не є безпосередньо відповіддю на ваше запитання, але вам слід подумати, як це назвати __version__, не version.

Це майже квазістандарт. Багато модулів у стандартній бібліотеці використовують __version__, і це також використовується у багатьох сторонніх модулях, тому це квазістандарт.

Зазвичай __version__це струна, але іноді це також поплавок або кортеж.

Редагувати: як згадував С.Лотт (Дякую!), PEP 8 прямо говорить:

Рівень модуля імен

Модуль рівня «dunders» (тобто імена з двома провідними і двома фінальними підкреслень) , такі як __all__, __author__, __version__і т.д. , повинен бути поміщені після модуля , але перед рядком документації будь-яких операторів імпорту , за винятком від __future__імпорту.

Ви також повинні переконатися, що номер версії відповідає формату, описаному в PEP 440 ( PEP 386 - попередня версія цього стандарту).


9
Він повинен бути рядком і мати версію_інфо для версії кортежу.
Джеймс Антілл

Джеймс: Чому __version_info__саме? (Що "вигадує" ваше власне слово з подвійним підкресленням.) [Коли Джеймс коментував, підкреслення нічого не робилося в коментарях, тепер вони вказують наголос, тому Джеймс справді __version_info__теж писав . --- ред.]

Ви можете побачити що - то про те, що версія повинен сказати , на packages.python.org/distribute / ... Це сторінка про поширювати, але сенс номера версії стає стандартом де-факто.
sienkiew

2
Правильно. Здається, ці ПЕП суперечать один одному. Ну, PEP 8 говорить "якщо" та "crud", тому він насправді не схвалює використання розширення ключових слів VCS. Крім того, якщо ви коли-небудь перейдете на інший VCS, ви втратите інформацію про версію. Тому я б запропонував використовувати інформацію про версію PAP 386/440, вбудовану в єдиний вихідний файл, принаймні для великих проектів.
oefe

2
Куди б ви поставили цю версію . Враховуючи, що це прийнята версія, я хотів би побачити цю додаткову інформацію тут.
darkgaze

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__

Ось приклад цієї методики, яку я використовую роками.

Код у цьому прикладі трохи складніший, але спрощений приклад, який я написав у цьому коментарі, повинен бути повноцінною реалізацією.

Ось приклад коду імпорту версії .

Якщо ви бачите щось не так у цьому підході, будь ласка, повідомте мене про це.


8
Скажіть, будь ласка, проблеми, що мотивують №3? Гліф сказав, що це має щось спільне з "setuptools любить робити вигляд, що ваш код ніде в системі, коли ваш setup.py працює", але деталі допоможуть переконати мене та інших.
Іван Козик

2
@Iva Тепер, у якому порядку повинен це робити інструмент? Він не може (в системі setuptools / pip / virtualenv сьогодні) навіть знати, що таке деп , поки він не оцінить ваш setup.py. Крім того, якби він спробував виконати повну глибину спочатку і зробити всі деп-файли, перш ніж це зробити, він застряг би, якщо були кругові деп-файли. Але якщо він спробує скласти цей пакет перед встановленням залежностей, то якщо ви імпортуєте свій пакунок зі свого setup.py, він не обов’язково зможе імпортувати його деп, або правильні версії своїх деп.
Зооко

3
Чи можете ви написати файл "version.py" з "setup.py" замість розбору? Це здається простішим.
Джонатан Хартлі

3
Джонатан Хартлі: Я погоджуюся, що для вашого "setup.py" було б трохи простіше написати файл "version.py", а не аналізувати його, але це відкриє вікно для невідповідності, коли ви відредагували ваш setup.py мати нову версію, але ще не виконано setup.py для оновлення файлу version.py. Інша причина, щоб канонічна версія містилася в невеликому окремому файлі, полягає в тому, що це полегшує написання файлу версії іншими інструментами, такими як інструменти, які читають стан контролю вашої редакції.
Зооко

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__атрибуту ДОЛЖЕ бути рядком.


13
Цей ПЕП не приймається / стандартизується, а відкладається (через відсутність інтересу). Тому трохи вводити в оману констатувати, що " існує стандартний спосіб ", визначений нею.
ткач

@weaver: О, мій! Я дізнався щось нове. Я не знав, що це потрібно було перевірити.
Відмінна думка

4
Відредаговано, щоб відзначити, що це не стандарт. Зараз мені стає ніяково, тому що я піднімав запити на проекти, пропонуючи їм дотримуватися цього "стандарту".
Відмінна думка

1
Можливо, вам слід взяти на себе роботу над стандартизацією цього PEP, оскільки вам здається цікавим :)
ткаць

Це працювало б для версії окремого модуля, але я не впевнений, що це стосуватиметься версії повного проекту.
Stevoisiak

21

Хоча це, мабуть, занадто пізно, є дещо простіша альтернатива попередній відповіді:

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

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

Звичайно, із того, що я бачив, люди, як правило, використовують щось подібне до раніше згаданої версії при використанні __version_info__, і як таке зберігають це як кортеж вкладених матеріалів однак я не зовсім бачу сенсу в цьому, тому що я сумніваюся, що ви можете виконувати математичні операції, такі як додавання та віднімання на частини номерів версій з будь-якою метою, окрім цікавості чи автоматичного збільшення (і навіть тоді, int()іstr() їх можна використовувати досить легко). (З іншого боку, існує можливість, щоб хтось інший код очікував числовий кортеж, а не рядковий кортеж, і таким чином вийшов з ладу.)

Це, звичайно, моє власне бачення, і я б із задоволенням хотів би інших вкладів у використанні числового кортежу.


Як нагадав мені shezi, (лексичні) порівняння рядків чисел не обов'язково мають такий же результат, як прямі чисельні порівняння; провідні нулі були б необхідні для цього. Отже, врешті-решт, зберігання __version_info__(або як би це не називалося) як набір цілих значень дозволило б зробити більш ефективними порівняння версій.


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__))
підкоригував

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 repo. Тег відновлюється до origin.

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

  3. setup.pyкоманда читає новий рядок версії з package.__version__.

Деталі:

Makefilefile

# 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

1
Дійсно - ціла нитка забуває, що ВКС дуже важливий у цій дискусії. просто Obs: збільшення версії має залишатися ручним процесом, тому кращим способом буде 1. створити вручну та натиснути тег 2. нехай інструменти VCS відкриють цей тег і зберігають його там, де потрібно (ух - цей інтерфейс редагування SO нас дійсно калічить мене - Мені довелося це редагувати десяток разів, щоб мати справу з новими рядками. І ВІН НЕ РОБИТИ! @ # $% ^ & *)
axd

Не потрібно використовувати, versionbump.pyколи у нас є дивовижний пакет bumpversion для python.
Оран

@Oran Я подивився на versionbump. Документи не дуже зрозумілі, і це не дуже добре позначає теги. У моєму тестуванні, схоже, потрапляють у стани, які спричиняють його збій: subprocess.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / folders / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngbo '] 'повернуто ненульовий статус виходу 1.
cmcginty

1
Чому не _version.pyслід відстежувати контроль версій?
Stevoisiak

1
@StevenVascellaro Це ускладнює процес випуску. Тепер ви повинні переконатися, що ви позначаєте теги та зобов’язання на значення в _version.py. Використання єдиного тегу версії є чистішим IMO і означає, що ви можете створити реліз безпосередньо з інтерфейсу програми github.
cmcginty

10

Я використовую файл JSON в режимі пакета. Це відповідає вимогам 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?
sorin

1
Мені подобається ідея варіанту (1), вона здається простішою, ніж відповідь Zooko про аналіз номера версії з файлу. Але ви не можете переконатися, що version.py створений, коли розробник просто клонує ваш репо. Якщо ви не зареєструєтеся у version.py, що відкриває невелику зморшку, яку ви можете змінити номер версії в setup.py, здійснити, випустити, а потім доведеться (косий забути) здійснити зміну в version.py.
Джонатан Хартлі

Імовірно, ви могли б генерувати модуль, використовуючи щось на зразок "" "з відкритим (" mypackage / version.py "," w ") як fp: fp.write (" __ версія__ == '% s' \ n "% (__version__,) ) "" "
Джонатан Хартлі

1
Я думаю, що варіант 2. піддається встановленню невдачі під час встановлення, як зазначено в коментарях до відповіді JAB.
Джонатан Хартлі

Що на рахунок того? Ви поміщаєте __version__ = '0,0,1' "(де версія, звичайно, рядок) в __init__.py" основного пакета вашого програмного забезпечення. Потім перейдіть до пункту 2: У налаштуваннях, які ви робите з пакета .__ init__ імпортуйте __version__ як v, а потім ви встановлюєте змінну v як версію свого setup.py. Потім імпортуйте mypack як мій, надрукуйте мій .__ версія__ надрукує версію. Версія буде зберігатися лише в одному місці, доступному з усього коду, у файлі, який не імпортує нічого іншого, пов’язаного з програмним забезпеченням.
СеФ

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 абсолютно не слід покладатися на імпорт пакету, який він знаходиться в процесі установки, щоб визначити параметри для розгортання. Yikes.
ely

Так, я не знаю, чому стрілка вирішила повернутися до цього рішення. Крім того, повідомлення фіксації говорить «Оновлення setup.py з сучасними стандартами Python » ... 🤷
Anto

4

Я також побачив інший стиль:

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

1
Так, я теж бачив. До речі, кожна відповідь має інший стиль, тому зараз я не знаю, який стиль є "еталоном". Шукаєте згаданих
депутатів

Інший спосіб бачення; Клієнт Python Mongo використовує звичайну версію, без підкреслення. Так це працює; $ python >>> імпорт pymongo >>> pymongo.version '2.7'
AnneTheAgile

Впровадження .VERSIONне означає, що вам не доведеться реалізовувати __version__.
Acumenus

Це вимагає впровадження djangoв проект?
Стевойшак

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 repo, то деякі метадані в setup.cfgможна залишатись і автоматично генеруватися кожного разу, коли буде створено розповсюдження для вашого пакету (використовуючиsetup.py )

Поточна версія в реальному часі

setuptoolsотримає останню інформацію в режимі реального часу, використовуючи setup.py:

python setup.py --version

Це витягне останню версію або з setup.cfgфайлу, або з git repo, на основі останнього комітету, який було зроблено, і тегів, які існують у репо. Ця команда не оновлює версію в дистрибутиві.

Оновлення версії

Коли ви створюєте дистрибутив за допомогою 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 repo та оновленням локальної .egg-infoпапки, встановленням виконавчих файлів сценаріїв для будь-яких визначених вами вхідних точок та інших функцій, які ви можете бачити з виводу при виконанні цієї команди.

Зауважте, що .egg-infoпапка, як правило, виключається із збереження у самій git repo в стандартних .gitignoreфайлах Python (наприклад, з Gitignore.IO ), оскільки вона може бути створена з вашого джерела. Якщо це виключено, переконайтеся, що у вас є стандартний "процес випуску", щоб метадані були оновлені локально перед випуском, і будь-який пакет, який ви завантажуєте на PyPi.org або розповсюджуєте іншим чином, повинен містити ці дані, щоб мати правильну версію. Якщо ви хочете, щоб Git repo містив цю інформацію, ви можете виключити конкретні файли з ігнорування (тобто додати !*.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

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 , зміни, так і 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повідомить про версію безпосередньо.


-1

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


Чи можете ви надати більше деталей або приклад того, як це працювало?
Stevoisiak

Хм. У 2020 році це (це було завжди?) Призначене для FORTRAN . Пакет "numpy.distutils є частиною NumPy, що розширює стандартні дистрибутиви Python для роботи з джерелами Fortran."
ingyhere

-1
  1. Використовуйте version.pyфайл лише з __version__ = <VERSION>парам у файлі. У setup.pyфайл імпортуйте __version__парам і введіть його значення у такий setup.pyфайл: version=__version__
  2. Інший спосіб - це використовувати лише setup.pyфайл із version=<CURRENT_VERSION>- CURRENT_VERSION жорстко кодується.

Оскільки ми не хочемо вручну змінювати версію у файлі кожного разу, коли ми створюємо новий тег (готовий випустити нову версію пакета), ми можемо використовувати наступне.

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

почніть з додавання 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 Вибачте за затримку, будь ласка, перевірте мою відповідь bumpversion. Використовуйте останню версію.
Оран

Мені трохи незрозуміло, яке рішення тут пропонується. Чи рекомендуєте ви відстежувати версію вручну з version.pyабо відстежувати її bumpversion?
Stevoisiak

@StevenVascellaro Я пропоную використовувати bumpversion, ніколи не використовувати ручну версію. Що я намагався пояснити, це те, що ви можете направити bumpversion для оновлення або версії у файлі setup.py, а ще краще використовувати її для оновлення файлу version.py. Більш поширеною практикою є оновлення файлу version.py та прийняття __version__параметра param у файл 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.