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

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

import foo
print foo.version

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

К вашему сведению, есть очень хороший обзор по адресу: package.python.org/en/latest/…

ionelmc 08.04.2015 17:52

Версию установленного пакета можно получить из метаданных с помощью setuptools, поэтому во многих случаях достаточно поместить версию только в setup.py. См. этот вопрос.

saaj 21.07.2015 17:57

К вашему сведению, в основном есть 5 распространенных паттернов для поддержания единого источника истины (как во время установки, так и во время выполнения) для номера версии.

KF Lin 22.06.2016 12:19

В документации @ionelmc Python перечислены 7 различных вариантов однократной закваски. Разве это не противоречит концепции «единственный источник истины»?

Stevoisiak 10.04.2018 19:20

@StevenVascellaro не уверен, о чем вы спрашиваете. Там перечислено так много способов, потому что руководство по упаковке не хочет быть самоуверенным.

ionelmc 11.04.2018 12:41
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
290
5
153 821
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

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

Не могли бы вы предоставить более подробную информацию или пример того, как это будет работать?

Stevoisiak 10.04.2018 19:10

Хм. В 2020 году это (всегда ли?) Предназначалось для FORTRAN. Пакет numpy.distutils является частью NumPy, расширяющей стандартные дистрибутивы Python для работы с исходными кодами Fortran.

ingyhere 08.06.2020 17:11

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

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

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

Мне нравится ваша рекомендация, но как сгенерировать этот модуль из setup.py?

sorin 22.06.2011 23:51

Мне нравится идея варианта (1), он кажется проще, чем ответ Zooko о разборе номера версии из файла. Но вы не можете гарантировать, что version.py создается, когда разработчик просто клонирует ваше репо. Если вы не зарегистрируетесь в version.py, который открывает небольшую складку, вы можете изменить номер версии в setup.py, зафиксировать, выпустить, а затем вам придется (косая черта забыть) зафиксировать изменение в version.py.

Jonathan Hartley 14.02.2012 16:44

Предположительно, вы могли бы сгенерировать модуль, используя что-то вроде "" "с open (" mypackage / version.py "," w ") как fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "

Jonathan Hartley 14.02.2012 16:49

Я думаю, что вариант 2. может потерпеть неудачу во время установки, как указано в комментариях к ответу JAB.

Jonathan Hartley 14.02.2012 16:52

Что об этом? Вы помещаете __version__ = '0.0.1' "(где версия, конечно, является строкой) в __init__.py" основного пакета вашего программного обеспечения. Затем перейдите к пункту 2: в настройке, которую вы выполняете из package .__ init__ import __version__ как v, а затем вы устанавливаете переменную v как версию вашего setup.py. Затем импортируйте mypack как my, print my .__ version__ распечатает версию. Версия будет храниться только в одном месте, доступном для всего кода, в файле, который не импортирует ничего другого, связанного с программным обеспечением.

SeF 29.07.2017 20:21
Ответ принят как подходящий

Это не прямой ответ на ваш вопрос, но вам следует подумать о том, чтобы назвать его __version__, а не version.

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

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

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

Module Level Dunder Names

Module level "dunders" (i.e. names with two leading and two trailing underscores) such as __all__, __author__, __version__, etc. should be placed after the module docstring but before any import statements except from __future__ imports.

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

Это должна быть строка с информация о версии для версии кортежа.

James Antill 21.01.2009 22:47

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

Roger Pate 30.12.2009 04:32

Вы можете увидеть кое-что о том, что версия должен сказать на packages.python.org/distribute/…. Эта страница посвящена распространению, но значение номера версии становится стандартом де-факто.

sienkiew 18.08.2010 22:48

Несмотря на то, что говорит PEP 8, крайне желательно, чтобы содержимое __version__ соответствовало правилам PEP 386, которые позволяют надежно сравнивать номера версий.

Daira Hopwood 21.02.2012 21:54

Это возвращает нас к вопросу: если версия должен быть сопоставимым и совместимым с PEP-386/440, то это исключает использование упомянутого в PEP-8 использования символов раскрытия ключевых слов VCS. Так где же мой git hash или RCS $ Revision $? В версия? Если да, то где моя локальная версия PEP-440 "0: 0.0.0.0.0.0rc0.post0.dev0-0.0.0.0"?

Austin Hastings 10.06.2014 08:06

Верно. Кажется, что эти PEP противоречат друг другу. Что ж, PEP 8 говорит «if» и «crud», поэтому на самом деле он не поддерживает использование расширения ключевых слов VCS. Кроме того, если вы когда-нибудь переключитесь на другую VCS, вы потеряете информацию о версии. Поэтому я бы предложил использовать информацию о версии, совместимую с PEP 386/440, встроенную в один исходный файл, по крайней мере, для более крупных проектов.

oefe 11.06.2014 00:37

Потрясающие. А затем сделайте еще один шаг и установите флаг командной строки (-v, --version) для печати версии. Используйте optparse: docs.python.org/3/library/optparse.html#module-optparse

BoomShadow 28.07.2016 04:47

Куда бы вы положили этот версия. Учитывая, что это принятая версия, мне бы хотелось увидеть здесь дополнительную информацию.

darkgaze 15.09.2016 17:22

PEP 8 больше этого не говорит.

Boris 18.12.2019 19:37

@Boris спасибо за подсказку! PEP8 по-прежнему упоминает «версия», но, к счастью, они избавились от этой грязи VCS. Обновлен соответствующим образом

oefe 19.12.2019 01:26

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

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

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

Есть ли у вас какие-либо ссылки / ссылки относительно происхождения такого использования __version_info__?

Craig McQueen 14.12.2009 10:11

Это то же самое сопоставление, что и sys.version с sys.version_info. Итак: docs.python.org/library/sys.html#sys.version_info

James Antill 23.12.2009 00:33

Проще провести сопоставление в другом направлении (__version_info__ = (1, 2, 3) и __version__ = '.'.join(map(str, __version_info__))).

Eric O Lebigot 17.04.2013 12:23

@EOL - __version__ = '.'.join(str(i) for i in __version_info__) - немного длиннее, но более питонично.

ArtOfWarfare 30.01.2016 00:52

Я не уверен, что то, что вы предлагаете, явно более питонично, поскольку оно содержит фиктивную переменную, которая на самом деле не нужна и значение которой немного сложно выразить (i не имеет значения, version_num немного длинен и неоднозначен…). Я даже воспринимаю наличие map() в Python как убедительный намек на то, что его следует использовать здесь, поскольку здесь нам нужно сделать типичный вариант использования map() (использовать с существующей функцией) - я не вижу многих других разумных использует.

Eric O Lebigot 30.01.2016 14:31

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

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

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

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

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


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

приятно (+1), но разве вы не предпочли бы числа вместо строк? например __version_info__ = (1,2,3)

orip 24.11.2009 17:34

Сравнение строк может стать опасным, если номер версии превышает 9, например, «10» <«2».

D Coetzee 11.05.2011 02:59

В этом случае вы можете просто использовать int() для правильного сравнения значений или просто сделать то, что предлагает orip, и сохранить значения как целые числа.

JAB 18.05.2011 19:07

Я тоже так делаю, но немного подправил для адресации ints .. __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))

rh0dium 26.07.2011 17:44

Проблема с __version__ = '.'.join(__version_info__) в том, что __version_info__ = ('1', '2', 'beta') станет 1.2.beta, а не 1.2beta или 1.2 beta.

nagisa 22.08.2011 17:36

@nagisa: Я не вижу в этом проблем. Это не то, чего ожидает большинство людей, но это все еще очень легко разбирается.

JAB 22.08.2011 18:17

Я думаю, что проблема этого подхода в том, где разместить строки кода, объявляющие __version__. Если они находятся в setup.py, ваша программа не может импортировать их из своего пакета. Возможно, для вас это не проблема, в таком случае ничего страшного. Если они входят в вашу программу, ваш setup.py может их импортировать, но не должен, поскольку setup.py запускается во время установки, когда зависимости вашей программы еще не установлены (setup.py используется для определения зависимостей. .) Отсюда ответ Zooko: вручную проанализировать значение из исходного файла продукта, не импортируя пакет продукта.

Jonathan Hartley 14.02.2012 16:40

Независимо от того, устанавливаете ли вы <tt> __version__ </tt> непосредственно как строку или используя этот метод (и парсинг <tt> __version_info__ </tt> в setup.py), ортогонален другим преимуществам ответа Zooko.

Daira Hopwood 21.02.2012 21:19

Использование чисел может помочь в сравнении: у Opera была большая проблема, когда они были первым браузером, достигшим версии 10. Прочтите полный отчет здесь: dev.opera.com/articles/view/opera-ua-string-changes

shezi 15.02.2013 15:00

@shezi: Это проблема с анализом подстроки, а не с какой-либо проблемой сравнения, но вы поднимаете хороший момент: лексическое сравнение целых чисел, хранящихся в виде строк, не обязательно будет таким же, как численное сравнение двух значений. (например, 2 < 10 оценивается как True, но '2' < '10' оценивается как False).

JAB 28.02.2013 01:46

Этот трюк информация о версии, по крайней мере, не синхронизирован с соответствующим PEP. У него также есть проблемы, о которых упоминал @nagisa.

lpapp 18.11.2013 14:29

Это не указывает где, версия должна быть сохранена. В каком файле это должно быть? Должен ли он быть в файле, импортированном setup.py?

Stevoisiak 10.04.2018 19:14

Еще я увидел другой стиль:

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

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

kbec 14.02.2013 22:59

Другой путь виден; Клиент Mongo Python использует простую версию без подчеркивания. Итак, это работает; $ python >>> import pymongo >>> pymongo.version '2.7'

AnneTheAgile 10.11.2014 18:14

Внедрение .VERSION не означает, что вам не нужно внедрять __version__.

Acumenus 21.02.2017 00:48

Требуется ли для этого внедрение django в проект?

Stevoisiak 10.04.2018 19:13

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

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

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

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

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

Не могли бы вы описать проблемы, которые мотивируют №3? Глиф сказал, что это как-то связано с «setuptools любит делать вид, что вашего кода нет в системе при запуске setup.py», но детали помогут убедить меня и других.

Ivan Kozik 23.09.2011 17:03

@IvanKozik: если ваш setup.py выполняется в только что созданном процессе Python, а ваш текущий рабочий каталог - это каталог, содержащий ваш проект, тогда он будет работать. Это обычный способ, которым программисты запускают setup.py, поэтому они думают, что это единственный способ. Но есть и другие способы! Такие инструменты, как py2exe, запускают один процесс Python, а затем загружают и выполняют сценарии setup.py из одного проекта за другим (при этом упаковывая их все вместе). Предположим, что часть кода Python, запускающего перед вашего сценария setup.py, импортировала другую версию вашего модуля.

Zooko 30.10.2011 19:02

@IvanKozik Я думаю, что с импортом вашего пакета могут быть и другие проблемы. Учтите, что такой инструмент, как setuptools или pip, должен делать две вещи для пакетов, объявляющих зависимости: 1. строить этот пакет, 2. устанавливать (первое построение, если необходимо) его зависимости.

Zooko 30.10.2011 19:14

@Iva А теперь, в каком порядке инструмент должен это делать? Он не может (в сегодняшней системе setuptools / pip / virtualenv) даже знать, что такое deps находятся, пока не оценит ваш setup.py. Кроме того, если бы он попытался сначала выполнить полную глубину и сделать все углубления до этого, он бы застрял, если бы были круговые углубления. Но если он попытается собрать этот пакет перед установкой зависимостей, то, если вы импортируете свой пакет со своего setup.py, он не обязательно сможет импортировать свои deps или правильные версии своих deps.

Zooko 30.10.2011 19:27

Выше я написал: «если ваш setup.py выполняется в только что созданном процессе Python, а ваш текущий рабочий каталог - это каталог, содержащий ваш проект, тогда он будет работать» «Я должен был написать:», если ваш setup.py выполняется выполняется в только что созданном процессе Python, и ваш текущий рабочий каталог - это каталог, содержащий ваш проект, и все его зависимости, которые ему нужны при импорте, уже установлены, тогда он будет работать "

Zooko 31.10.2011 19:31

Могли бы вы написать файл "version.py" из "setup.py" вместо его синтаксического анализа? Это кажется проще.

Jonathan Hartley 14.02.2012 16:53

Отличный ответ. Небольшая морщинка для читателей: тип выпуска (альфа, бета, кандидат, окончательный, публикация и т. д.) Дублируется в версии, как указано выше, и в классификаторах сокровищ, если вы их укажете.

Jonathan Hartley 14.02.2012 18:12

Джонатан Хартли: Я согласен, что для вашего «setup.py» было бы немного проще написать файл «version.py», а не разбирать его, но при редактировании файла setup.py откроется окно несогласованности. чтобы иметь новую версию, но еще не запустил setup.py для обновления файла version.py. Еще одна причина, по которой каноническая версия должна быть в небольшом отдельном файле, заключается в том, что это упрощает для инструментов Другие, таких как инструменты, которые читают ваше состояние управления версиями, запись файла версии.

Zooko 21.02.2012 18:45

Аналогичный подход применяется к execfile("myniftyapp/_version.py") из setup.py, вместо того, чтобы пытаться анализировать код версии вручную. Предлагается в stackoverflow.com/a/2073599/647002 - обсуждение там тоже может быть полезно.

medmunds 03.03.2013 23:19

Вот инструмент, который в основном дополняет мой давний партнер по программированию Брайан Уорнер: blog.mozilla.org/warner/2012/01/31/… Versioneer - это инструмент для генерации номера версии из истории git. В идеале я хотел бы использовать Versioneer для генерации номера версии, а затем использовать технику, описанную в моем ответе здесь, чтобы сделать этот номер версии доступным во всех местах, где он нужен. Я делаю что-то подобное в этом проекте: github.com/zooko/pycryptopp

Zooko 14.03.2013 23:46

+1. Есть ли причина не иметь файл ´__version__´, который содержит только версию (1.2.3), а затем прочитать его в setup.py, а также в вашем пакете? (Такой синтаксический анализ не требуется ...)

Ajasja 15.04.2014 12:43

Я добавляю к этому __version_info__ = tuple(map(int, __version__.split('.'))).

letmaik 25.10.2014 00:14

Должен быть from ._version import __version__, чтобы вытащить модуль _version из _version.py в локальный проект, а не еще один, плавающий в вашей среде.

Max Hutchinson 20.03.2015 21:09

Ваша первая ссылка не работает.

Jonathan Wheeler 12.12.2016 21:03

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

3) When a module (or package) includes a version number, the version SHOULD be available in the __version__ attribute.

4) For modules which live inside a namespace package, the module SHOULD include the __version__ attribute. The namespace package itself SHOULD NOT include its own __version__ attribute.

5) The __version__ attribute's value SHOULD be a string.

Этот PEP не принимается / не стандартизируется, а откладывается (из-за отсутствия интереса). Поэтому утверждение, что "есть стандартный способ" определено им, немного сбивает с толку.

weaver 14.04.2014 19:41

@weaver: Боже мой! Я узнал кое-что новое. Я не знал, что мне нужно это проверить.

Oddthinking 14.04.2014 20:01

Отредактировано, чтобы отметить, что это не стандарт. Теперь мне неловко, потому что я поднял запросы функций в проектах, прося их следовать этому «стандарту».

Oddthinking 14.04.2014 20:04

Возможно, вам стоит взять на себя работу по стандартизации этого PEP, раз уж вы, кажется, заинтересованы :)

weaver 15.04.2014 06:38

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

Stevoisiak 10.04.2018 19:16

@StevenVascellaro: "Полный проект" немного расплывчатый. Это вопрос о пакетах. Если ваш проект представляет собой веб-сайт, встроенную систему, приложение для телефона, настольное приложение, командную строку и т. д., Вам понадобится другая система - и другой вопрос.

Oddthinking 10.04.2018 19:34

«версия ДОЛЖНА быть доступна в атрибуте версия» (и т. д.) не означает, что этот атрибут жестко запрограммирован. Он тоже мог получить это динамически. Он просто должен быть «доступен». Что такое GoodThing (TM)

nerdoc 25.04.2020 18:24

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

Oddthinking 25.04.2020 18:55

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

После 13 с лишним лет написания кода 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__,
    ...

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

Вам следует заменить exec (open ('yourpackage / version.py'). Read ()) на execfile ('yourpackage / version.py').

Christophe Vu-Brugier 28.08.2013 16:16

Эээ нет. execfile () не существует в Python 3, поэтому лучше использовать exec (open (). read ()).

Christophe Vu-Brugier 19.10.2013 11:48

почему бы не поставить "version = '0.12' прямо в setup.py?

アレックス 22.03.2015 09:14

почему не from .version import __version__ в setup.py?

Aprillion 10.06.2015 13:12

@Aprillion Потому что пакет не загружается, когда setup.py запущен - попробуйте, вы получите сообщение об ошибке (по крайней мере, я :-))

darthbith 10.06.2015 22:38

@darthbith Я думаю, что в Python 2.7+ . находится в sys.path по умолчанию, так что, пока модуль version не имеет зависимостей, это способ.

saaj 21.07.2015 17:46

@saaj Да, но каталог . - это каталог с setup.py, а не каталог с модулем version ...

darthbith 21.07.2015 18:40

Если вы запустите python setup.py install из каталога, отличного от корня пакета, относительный поиск version.py завершится ошибкой.

BMW 14.12.2016 18:37

работает отлично, но для большей надежности добавлен абсолютный путь: here = os.path.abspath(os.path.dirname(__file__)), затем exec(open(os.path.join(here, 'yourpackage/version.py')).read()). Также не обнаружил необходимости в относительном импорте.

slightlynybbled 25.05.2017 04:23

Ссылка на pbr приводит к плохому шлюзу.

MERose 27.07.2017 11:45

Спасибо за помощь в переходе на pbr (Разумность сборки Python)! Выглядит очень многообещающе! Это поможет нам сделать экосистему более безопасной и надежной с помощью текстовых файлов, таких как setup.cfg, а не кода DIY, выполняемого в setup.py, что значительно усложнило работу с PyPI, установкой pip и управлением требованиями.

nealmcb 14.10.2017 18:07

Не могли бы вы привести пример реализации pbr для управления версиями?

Stevoisiak 10.04.2018 19:05
pbr, несомненно, отличный инструмент, но вы не смогли ответить на этот вопрос. Как получить доступ к текущей версии или установленному пакету через bpr.
nad2000 30.05.2018 02:26

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

Arne 09.01.2019 18:18

Вам не нужен pbr для доступа к информации версия, просто получите ее с module.__version__ - на самом деле pbr автоматизирует ее создание во время сборки и ее обслуживание при запуске из git. Если вы используете git и теги для версии, это было бы пустой тратой времени. Верно, что pbr возник на openstack, но в нем нет ничего особенного (его популярность не имеет ничего общего с openstack). Я надеюсь, что упаковка на Python будет развиваться, поэтому pbr не понадобится.

sorin 12.01.2019 13:39

Я использую файл 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 24.05.2019 11:41

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

Сейчас (с 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 19.08.2019 15:49

обновленное решение на самом деле вредно. Когда запущен setup.py, он не обязательно увидит версию пакета по пути к локальному файлу. Он может вернуться к ранее установленной версии, например от запуска pip install -e . в ветке разработки или чего-то еще при тестировании. setup.py абсолютно не должен полагаться на импорт пакета, который находится в процессе установки, чтобы определить параметры для развертывания. Ой.

ely 22.08.2019 20:01

Да, я не знаю, почему стрелка решила вернуться к этому решению. Кроме того, в сообщении фиксации говорится: «Обновлен setup.py с помощью современные стандарты Python» ... ?

Anto 26.08.2019 16:13

Если вы используете 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 10.04.2018 19:10

Многие из этих решений здесь игнорируют теги версий 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

действительно - вся эта ветка забывает, что VCS очень важна в этом обсуждении. просто obs: приращение версии должно оставаться ручным процессом, поэтому предпочтительным способом будет 1. вручную создать и вставить тег 2. позволить инструментам VCS обнаруживать этот тег и сохранять его там, где это необходимо (вау, этот интерфейс редактирования SO действительно наносит мне вред - Мне пришлось отредактировать это дюжину раз только для того, чтобы иметь дело с символами новой строки, И ЭТО ПО-прежнему НЕ РАБОТАЕТ! @ # $% ^ & *)

axd 13.12.2017 16:31

Нет необходимости использовать versionbump.py, когда у нас есть отличный пакет bumpversion для python.

Oran 29.01.2018 14:43

@Oran Я посмотрел на версию. Документы не очень понятны, и он не очень хорошо обрабатывает теги. В моем тестировании он, кажется, попадает в состояния, которые вызывают сбой: subprocess.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / folder / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngb‌ o ']' вернул ненулевой статус выхода 1.

cmcginty 30.01.2018 01:58

Еще одна хитрость - использовать PBR для управления некоторыми версиями. Вы можете использовать тот же процесс, чтобы пометить репо, и тогда PBR правильно вставит новую версию в команду setup.py.

cmcginty 30.01.2018 02:02

@cmcginty Извините за задержку с ответом, проверьте мой ответ: stackoverflow.com/a/48664839/2656748

Oran 07.02.2018 16:24

Почему нельзя отслеживать _version.py с помощью контроля версий?

Stevoisiak 10.04.2018 19:12

@StevenVascellaro Это усложняет процесс выпуска. Теперь вы должны убедиться, что ваши теги и коммиты соответствуют значению в _version.py. Использование одного тега версии более чистое IMO и означает, что вы можете создать выпуск прямо из пользовательского интерфейса github.

cmcginty 11.04.2018 23:14

похоже, что bumpversion не поддерживается. Вы можете использовать более новый bump2version

Amnon 20.02.2020 14:29
  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 Извините за задержку, пожалуйста, проверьте мой ответ ^^^ - обратите внимание, что вы должны git commit или git reset все в вашем репо и убедиться, что ваша виртуальная среда включает пакет bumpversion, без него он не будет работать. Используйте последнюю версию.

Oran 07.02.2018 16:21

Я немного не понимаю, какое решение здесь предлагается. Вы рекомендуете отслеживать версию вручную с помощью version.py или отслеживать ее с помощью bumpversion?

Stevoisiak 10.04.2018 19:09

@StevenVascellaro Я предлагаю использовать bumpversion, никогда не используйте ручное управление версиями. Я попытался объяснить, что вы можете направить bumpversion либо для обновления версии в файле setup.py, либо, что еще лучше, использовать ее для обновления файла version.py. Чаще всего обновляют файл version.py и переносят значение параметра __version__ в файл setup.py. Мое решение используется в производстве, и это обычная практика. Примечание: для ясности: использование bumpversion как части скрипта - лучшее решение, поместите его в свой CI, и это будет автоматическая операция.

Oran 11.04.2018 14:20

Использование 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 в наборе файлов, в которых хранятся метаданные распространения.

Note on process to update version meta-data:

If you are not using pbr to pull version data from git, then just update your setup.cfg directly with new version info (easy enough, but make sure this is a standard part of your release process).

If you are using git, and you don't need to create a source or binary distribution (using python setup.py sdist or one of the python setup.py bdist_xxx commands) the simplest way to update the git repo info into your <mypackage>.egg-info metadata folder is to just run the python setup.py install command. This will run all the PBR functions related to pulling metadata from the git repo and update your local .egg-info folder, install script executables for any entry-points you have defined, and other functions you can see from the output when you run this command.

Note that the .egg-info folder is generally excluded from being stored in the git repo itself in standard Python .gitignore files (such as from Gitignore.IO), as it can be generated from your source. If it is excluded, make sure you have a standard "release process" to get the metadata updated locally before release, and any package you upload to PyPi.org or otherwise distribute must include this data to have the correct version. If you want the Git repo to contain this info, you can exclude specific files from being ignored (i.e. add !*.egg-info/PKG_INFO to .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 07.01.2020 20:45

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

создайте файл 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__,

в основной папке в этом.py:

from .version import __version__

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

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

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

  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 сообщит версию напрямую.

Я предпочитаю читать версию пакета из среды установки. Это мой src/foo/_version.py:

from pkg_resources import get_distribution                                        
                                                                                  
__version__ = get_distribution('osbc').version

Убедитесь, что foo всегда уже установлен, поэтому требуется слой src/ для предотвращения импорта foo без установки.

В setup.py я использую setuptools-scm для автоматического создания версии.

pbr с bump2version

Это решение было получено из эта статья

Вариант использования - пакет графического интерфейса Python, распространяемый через PyInstaller. Требуется показать информацию о версии.

Вот структура проекта packagex

packagex
├── packagex
│   ├── __init__.py
│   ├── main.py
│   └── _version.py
├── packagex.spec
├── LICENSE
├── README.md
├── .bumpversion.cfg
├── requirements.txt
├── setup.cfg
└── setup.py

где setup.py - это

# setup.py
import os

import setuptools

about = {}
with open("packagex/_version.py") as f:
    exec(f.read(), about)

os.environ["PBR_VERSION"] = about["__version__"]

setuptools.setup(
    setup_requires=["pbr"],
    pbr=True,
    version=about["__version__"],
)

packagex/_version.py содержит только

__version__ = "0.0.1"

и packagex/__init__.py

from ._version import __version__

и для .bumpversion.cfg

[bumpversion]
current_version = 0.0.1
commit = False
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+)(?P<build>\d+))?
serialize = 
    {major}.{minor}.{patch}-{release}{build}
    {major}.{minor}.{patch}

[bumpversion:part:release]
optional_value = prod
first_value = dev
values = 
    dev
    prod

[bumpversion:file:packagex/_version.py]
  1. Создайте файл с именем _version.txt в той же папке, что и __init__.py, и запишите версию в виде одной строки:
0.8.2
  1. Прочтите эту информацию из файла _version.txt в __init__.py:
    import os 
    def get_version():
        with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), "_version.txt")) as f:
            return f.read().strip() 
    __version__ = get_version()

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