Есть ли стандартный способ связать строку версии с пакетом python таким образом, чтобы я мог сделать следующее?
import foo
print foo.version
Я бы предположил, что есть какой-то способ получить эти данные без какого-либо дополнительного жесткого кодирования, поскольку второстепенные / основные строки уже указаны в setup.py. Альтернативное решение, которое я нашел, заключалось в том, чтобы иметь import __version__ в моем foo/__init__.py, а затем иметь __version__.py, сгенерированный setup.py.
Версию установленного пакета можно получить из метаданных с помощью setuptools, поэтому во многих случаях достаточно поместить версию только в setup.py. См. этот вопрос.
К вашему сведению, в основном есть 5 распространенных паттернов для поддержания единого источника истины (как во время установки, так и во время выполнения) для номера версии.
В документации @ionelmc Python перечислены 7 различных вариантов однократной закваски. Разве это не противоречит концепции «единственный источник истины»?
@StevenVascellaro не уверен, о чем вы спрашиваете. Там перечислено так много способов, потому что руководство по упаковке не хочет быть самоуверенным.






Как бы то ни было, если вы используете NumPy distutils, numpy.distutils.misc_util.Configuration имеет метод make_svn_version_py(), который вставляет номер версии внутри package.__svn_version__ в переменную version.
Не могли бы вы предоставить более подробную информацию или пример того, как это будет работать?
Хм. В 2020 году это (всегда ли?) Предназначалось для FORTRAN. Пакет numpy.distutils является частью NumPy, расширяющей стандартные дистрибутивы Python для работы с исходными кодами Fortran.
Кажется, не существует стандартного способа встроить строку версии в пакет python. Большинство пакетов, которые я видел, используют какой-либо вариант вашего решения, например eitner
Вставьте версию в setup.py и попросите setup.py сгенерировать модуль (например, version.py), содержащий только информацию о версии, импортированную вашим пакетом, или
Обратное: поместите информацию о версии в сам пакет и импортируйте что, чтобы установить версию в setup.py.
Мне нравится ваша рекомендация, но как сгенерировать этот модуль из setup.py?
Мне нравится идея варианта (1), он кажется проще, чем ответ Zooko о разборе номера версии из файла. Но вы не можете гарантировать, что version.py создается, когда разработчик просто клонирует ваше репо. Если вы не зарегистрируетесь в version.py, который открывает небольшую складку, вы можете изменить номер версии в setup.py, зафиксировать, выпустить, а затем вам придется (косая черта забыть) зафиксировать изменение в version.py.
Предположительно, вы могли бы сгенерировать модуль, используя что-то вроде "" "с open (" mypackage / version.py "," w ") как fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "
Я думаю, что вариант 2. может потерпеть неудачу во время установки, как указано в комментариях к ответу JAB.
Что об этом? Вы помещаете __version__ = '0.0.1' "(где версия, конечно, является строкой) в __init__.py" основного пакета вашего программного обеспечения. Затем перейдите к пункту 2: в настройке, которую вы выполняете из package .__ init__ import __version__ как v, а затем вы устанавливаете переменную v как версию вашего setup.py. Затем импортируйте mypack как my, print my .__ version__ распечатает версию. Версия будет храниться только в одном месте, доступном для всего кода, в файле, который не импортирует ничего другого, связанного с программным обеспечением.
Это не прямой ответ на ваш вопрос, но вам следует подумать о том, чтобы назвать его __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 предыдущая версия этого стандарта).
Это должна быть строка с информация о версии для версии кортежа.
Джеймс: Почему именно __version_info__? (Что «изобретает» ваше собственное двойное подчеркивание.) [Когда Джеймс прокомментировал, подчеркивания ничего не сделали в комментариях, теперь они указывают на выделение, так что Джеймс действительно написал __version_info__. --- ред.]
Вы можете увидеть кое-что о том, что версия должен сказать на packages.python.org/distribute/…. Эта страница посвящена распространению, но значение номера версии становится стандартом де-факто.
Несмотря на то, что говорит PEP 8, крайне желательно, чтобы содержимое __version__ соответствовало правилам PEP 386, которые позволяют надежно сравнивать номера версий.
Это возвращает нас к вопросу: если версия должен быть сопоставимым и совместимым с 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"?
Верно. Кажется, что эти PEP противоречат друг другу. Что ж, PEP 8 говорит «if» и «crud», поэтому на самом деле он не поддерживает использование расширения ключевых слов VCS. Кроме того, если вы когда-нибудь переключитесь на другую VCS, вы потеряете информацию о версии. Поэтому я бы предложил использовать информацию о версии, совместимую с PEP 386/440, встроенную в один исходный файл, по крайней мере, для более крупных проектов.
Потрясающие. А затем сделайте еще один шаг и установите флаг командной строки (-v, --version) для печати версии. Используйте optparse: docs.python.org/3/library/optparse.html#module-optparse
Куда бы вы положили этот версия. Учитывая, что это принятая версия, мне бы хотелось увидеть здесь дополнительную информацию.
PEP 8 больше этого не говорит.
@Boris спасибо за подсказку! PEP8 по-прежнему упоминает «версия», но, к счастью, они избавились от этой грязи VCS. Обновлен соответствующим образом
Также стоит отметить, что __version__ является полустандартным. в python так и __version_info__, который является кортежем, в простых случаях вы можете просто сделать что-то вроде:
__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])
... и вы можете получить строку __version__ из файла или что-то еще.
Есть ли у вас какие-либо ссылки / ссылки относительно происхождения такого использования __version_info__?
Это то же самое сопоставление, что и sys.version с sys.version_info. Итак: docs.python.org/library/sys.html#sys.version_info
Проще провести сопоставление в другом направлении (__version_info__ = (1, 2, 3) и __version__ = '.'.join(map(str, __version_info__))).
@EOL - __version__ = '.'.join(str(i) for i in __version_info__) - немного длиннее, но более питонично.
Я не уверен, что то, что вы предлагаете, явно более питонично, поскольку оно содержит фиктивную переменную, которая на самом деле не нужна и значение которой немного сложно выразить (i не имеет значения, version_num немного длинен и неоднозначен…). Я даже воспринимаю наличие map() в Python как убедительный намек на то, что его следует использовать здесь, поскольку здесь нам нужно сделать типичный вариант использования map() (использовать с существующей функцией) - я не вижу многих других разумных использует.
Хотя это, вероятно, слишком поздно, есть немного более простая альтернатива предыдущему ответу:
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(И было бы довольно просто преобразовать автоматически увеличивающиеся части номеров версий в строку с помощью str().)
Конечно, из того, что я видел, люди склонны использовать что-то вроде ранее упомянутой версии при использовании __version_info__ и, таким образом, хранят ее как кортеж целых чисел; однако я не совсем вижу в этом смысл, так как сомневаюсь, что есть ситуации, когда вы будете выполнять математические операции, такие как сложение и вычитание частей номеров версий, для любых целей, кроме любопытства или автоинкремента (и даже тогда, int() и str() можно использовать довольно легко). (С другой стороны, существует вероятность того, что чей-то другой код ожидает числовой кортеж, а не строковый кортеж, и, следовательно, потерпит неудачу.)
Это, конечно, мое собственное мнение, и я с радостью хотел бы узнать мнение других об использовании числового кортежа.
Как напомнил мне Шези, (лексические) сравнения числовых строк не обязательно дают тот же результат, что и прямые числовые сравнения; Для этого потребуются начальные нули. Таким образом, в конце концов, сохранение __version_info__ (или как бы он там ни назывался) в виде кортежа целочисленных значений позволило бы более эффективно сравнивать версии.
приятно (+1), но разве вы не предпочли бы числа вместо строк? например __version_info__ = (1,2,3)
Сравнение строк может стать опасным, если номер версии превышает 9, например, «10» <«2».
В этом случае вы можете просто использовать int() для правильного сравнения значений или просто сделать то, что предлагает orip, и сохранить значения как целые числа.
Я тоже так делаю, но немного подправил для адресации ints .. __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
Проблема с __version__ = '.'.join(__version_info__) в том, что __version_info__ = ('1', '2', 'beta') станет 1.2.beta, а не 1.2beta или 1.2 beta.
@nagisa: Я не вижу в этом проблем. Это не то, чего ожидает большинство людей, но это все еще очень легко разбирается.
Я думаю, что проблема этого подхода в том, где разместить строки кода, объявляющие __version__. Если они находятся в setup.py, ваша программа не может импортировать их из своего пакета. Возможно, для вас это не проблема, в таком случае ничего страшного. Если они входят в вашу программу, ваш setup.py может их импортировать, но не должен, поскольку setup.py запускается во время установки, когда зависимости вашей программы еще не установлены (setup.py используется для определения зависимостей. .) Отсюда ответ Zooko: вручную проанализировать значение из исходного файла продукта, не импортируя пакет продукта.
Независимо от того, устанавливаете ли вы <tt> __version__ </tt> непосредственно как строку или используя этот метод (и парсинг <tt> __version_info__ </tt> в setup.py), ортогонален другим преимуществам ответа Zooko.
Использование чисел может помочь в сравнении: у Opera была большая проблема, когда они были первым браузером, достигшим версии 10. Прочтите полный отчет здесь: dev.opera.com/articles/view/opera-ua-string-changes
@shezi: Это проблема с анализом подстроки, а не с какой-либо проблемой сравнения, но вы поднимаете хороший момент: лексическое сравнение целых чисел, хранящихся в виде строк, не обязательно будет таким же, как численное сравнение двух значений. (например, 2 < 10 оценивается как True, но '2' < '10' оценивается как False).
Этот трюк информация о версии, по крайней мере, не синхронизирован с соответствующим PEP. У него также есть проблемы, о которых упоминал @nagisa.
Это не указывает где, версия должна быть сохранена. В каком файле это должно быть? Должен ли он быть в файле, импортированном setup.py?
Еще я увидел другой стиль:
>>> django.VERSION
(1, 1, 0, 'final', 0)
Да, я тоже видел. Кстати, каждый ответ требует другого стиля, поэтому теперь я не знаю, какой стиль является «стандартом». Ищем упомянутых политически значимых лиц ...
Другой путь виден; Клиент Mongo Python использует простую версию без подчеркивания. Итак, это работает; $ python >>> import pymongo >>> pymongo.version '2.7'
Внедрение .VERSION не означает, что вам не нужно внедрять __version__.
Требуется ли для этого внедрение django в проект?
Я использую один файл _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 передает эту строку в качестве значения аргумента "версия" в setup(), таким образом удовлетворяя функцию 2.
Чтобы удовлетворить функцию 1, вы можете заставить свой пакет (во время выполнения, а не во время установки!) Импортировать файл _version из myniftyapp/__init__.py следующим образом:
from _version import __version__
Вот пример этой техники, который я использую годами.
Код в этом примере немного сложнее, но упрощенный пример, который я написал в этом комментарии, должен быть полной реализацией.
Вот пример кода импорта версии.
Если вы видите что-то неправильное в этом подходе, дайте мне знать.
Не могли бы вы описать проблемы, которые мотивируют №3? Глиф сказал, что это как-то связано с «setuptools любит делать вид, что вашего кода нет в системе при запуске setup.py», но детали помогут убедить меня и других.
@IvanKozik: если ваш setup.py выполняется в только что созданном процессе Python, а ваш текущий рабочий каталог - это каталог, содержащий ваш проект, тогда он будет работать. Это обычный способ, которым программисты запускают setup.py, поэтому они думают, что это единственный способ. Но есть и другие способы! Такие инструменты, как py2exe, запускают один процесс Python, а затем загружают и выполняют сценарии setup.py из одного проекта за другим (при этом упаковывая их все вместе). Предположим, что часть кода Python, запускающего перед вашего сценария setup.py, импортировала другую версию вашего модуля.
@IvanKozik Я думаю, что с импортом вашего пакета могут быть и другие проблемы. Учтите, что такой инструмент, как setuptools или pip, должен делать две вещи для пакетов, объявляющих зависимости: 1. строить этот пакет, 2. устанавливать (первое построение, если необходимо) его зависимости.
@Iva А теперь, в каком порядке инструмент должен это делать? Он не может (в сегодняшней системе setuptools / pip / virtualenv) даже знать, что такое deps находятся, пока не оценит ваш setup.py. Кроме того, если бы он попытался сначала выполнить полную глубину и сделать все углубления до этого, он бы застрял, если бы были круговые углубления. Но если он попытается собрать этот пакет перед установкой зависимостей, то, если вы импортируете свой пакет со своего setup.py, он не обязательно сможет импортировать свои deps или правильные версии своих deps.
Выше я написал: «если ваш setup.py выполняется в только что созданном процессе Python, а ваш текущий рабочий каталог - это каталог, содержащий ваш проект, тогда он будет работать» «Я должен был написать:», если ваш setup.py выполняется выполняется в только что созданном процессе Python, и ваш текущий рабочий каталог - это каталог, содержащий ваш проект, и все его зависимости, которые ему нужны при импорте, уже установлены, тогда он будет работать "
Могли бы вы написать файл "version.py" из "setup.py" вместо его синтаксического анализа? Это кажется проще.
Отличный ответ. Небольшая морщинка для читателей: тип выпуска (альфа, бета, кандидат, окончательный, публикация и т. д.) Дублируется в версии, как указано выше, и в классификаторах сокровищ, если вы их укажете.
Джонатан Хартли: Я согласен, что для вашего «setup.py» было бы немного проще написать файл «version.py», а не разбирать его, но при редактировании файла setup.py откроется окно несогласованности. чтобы иметь новую версию, но еще не запустил setup.py для обновления файла version.py. Еще одна причина, по которой каноническая версия должна быть в небольшом отдельном файле, заключается в том, что это упрощает для инструментов Другие, таких как инструменты, которые читают ваше состояние управления версиями, запись файла версии.
Аналогичный подход применяется к execfile("myniftyapp/_version.py") из setup.py, вместо того, чтобы пытаться анализировать код версии вручную. Предлагается в stackoverflow.com/a/2073599/647002 - обсуждение там тоже может быть полезно.
Вот инструмент, который в основном дополняет мой давний партнер по программированию Брайан Уорнер: blog.mozilla.org/warner/2012/01/31/… Versioneer - это инструмент для генерации номера версии из истории git. В идеале я хотел бы использовать Versioneer для генерации номера версии, а затем использовать технику, описанную в моем ответе здесь, чтобы сделать этот номер версии доступным во всех местах, где он нужен. Я делаю что-то подобное в этом проекте: github.com/zooko/pycryptopp
+1. Есть ли причина не иметь файл ´__version__´, который содержит только версию (1.2.3), а затем прочитать его в setup.py, а также в вашем пакете? (Такой синтаксический анализ не требуется ...)
Я добавляю к этому __version_info__ = tuple(map(int, __version__.split('.'))).
Должен быть from ._version import __version__, чтобы вытащить модуль _version из _version.py в локальный проект, а не еще один, плавающий в вашей среде.
Ваша первая ссылка не работает.
Для отложенного 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: Боже мой! Я узнал кое-что новое. Я не знал, что мне нужно это проверить.
Отредактировано, чтобы отметить, что это не стандарт. Теперь мне неловко, потому что я поднял запросы функций в проектах, прося их следовать этому «стандарту».
Возможно, вам стоит взять на себя работу по стандартизации этого PEP, раз уж вы, кажется, заинтересованы :)
Это сработает для управления версиями отдельного модуля, но я не уверен, что это применимо к управлению версиями всего проекта.
@StevenVascellaro: "Полный проект" немного расплывчатый. Это вопрос о пакетах. Если ваш проект представляет собой веб-сайт, встроенную систему, приложение для телефона, настольное приложение, командную строку и т. д., Вам понадобится другая система - и другой вопрос.
«версия ДОЛЖНА быть доступна в атрибуте версия» (и т. д.) не означает, что этот атрибут жестко запрограммирован. Он тоже мог получить это динамически. Он просто должен быть «доступен». Что такое GoodThing (TM)
@nerdoc: Это похоже на то, что вы возражаете против этого ответа, но я обращался к вопросу о том, как узнать версию пакета, который вы используете, а не о том, как она должна быть рассчитана.
После 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').
Эээ нет. execfile () не существует в Python 3, поэтому лучше использовать exec (open (). read ()).
почему бы не поставить "version = '0.12' прямо в setup.py?
почему не from .version import __version__ в setup.py?
@Aprillion Потому что пакет не загружается, когда setup.py запущен - попробуйте, вы получите сообщение об ошибке (по крайней мере, я :-))
@darthbith Я думаю, что в Python 2.7+ . находится в sys.path по умолчанию, так что, пока модуль version не имеет зависимостей, это способ.
@saaj Да, но каталог . - это каталог с setup.py, а не каталог с модулем version ...
Если вы запустите python setup.py install из каталога, отличного от корня пакета, относительный поиск version.py завершится ошибкой.
работает отлично, но для большей надежности добавлен абсолютный путь: here = os.path.abspath(os.path.dirname(__file__)), затем exec(open(os.path.join(here, 'yourpackage/version.py')).read()). Также не обнаружил необходимости в относительном импорте.
Ссылка на pbr приводит к плохому шлюзу.
Спасибо за помощь в переходе на pbr (Разумность сборки Python)! Выглядит очень многообещающе! Это поможет нам сделать экосистему более безопасной и надежной с помощью текстовых файлов, таких как setup.cfg, а не кода DIY, выполняемого в setup.py, что значительно усложнило работу с PyPI, установкой pip и управлением требованиями.
Не могли бы вы привести пример реализации pbr для управления версиями?
pbr - ужасное решение. Это пакет, написанный сообществом openstack для решения проблем openstack, и он вводит целый ряд мнений и предположений, которые могут сильно облажаться, если вы о них не знаете. Если вы не пишете код для openstack, велика вероятность, что он создает больше проблем, чем решает.
Вам не нужен pbr для доступа к информации версия, просто получите ее с module.__version__ - на самом деле pbr автоматизирует ее создание во время сборки и ее обслуживание при запуске из git. Если вы используете git и теги для версии, это было бы пустой тратой времени. Верно, что pbr возник на openstack, но в нем нет ничего особенного (его популярность не имеет ничего общего с openstack). Я надеюсь, что упаковка на Python будет развиваться, поэтому pbr не понадобится.
Я использую файл 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 для автоматизации управления метаданными? Спасибо!
стрела справляется с этим интересным образом.
Сейчас (с 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?
обновленное решение на самом деле вредно. Когда запущен setup.py, он не обязательно увидит версию пакета по пути к локальному файлу. Он может вернуться к ранее установленной версии, например от запуска pip install -e . в ветке разработки или чего-то еще при тестировании. setup.py абсолютно не должен полагаться на импорт пакета, который находится в процессе установки, чтобы определить параметры для развертывания. Ой.
Да, я не знаю, почему стрелка решила вернуться к этому решению. Кроме того, в сообщении фиксации говорится: «Обновлен setup.py с помощью современные стандарты Python» ... ?
Если вы используете 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__? Как увеличить номер версии с помощью этого решения?
Многие из этих решений здесь игнорируют теги версий git, что по-прежнему означает, что вам нужно отслеживать версию в нескольких местах (плохо). Я подошел к этому со следующими целями:
gitgit tag / push и setup.py upload с помощью одной команды, которая не требует ввода.С помощью команды make release будет найдена и увеличена последняя версия с тегами в репозитории git. Тег возвращается в origin.
Makefile хранит версию в src/_version.py, где она будет прочитана setup.py и также включена в релиз. Не проверяйте _version.py в системе контроля версий!
Команда setup.py считывает строку новой версии из package.__version__.
# 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, который проверяется в корне репо.
"""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.
Файл my_module/_version.py импортируется в my_module/__init__.py. Поместите сюда любую статическую конфигурацию установки, которую вы хотите распространить с вашим модулем.
from ._version import __version__
__author__ = ''
__email__ = ''
Последний шаг - прочитать информацию о версии из модуля 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 действительно наносит мне вред - Мне пришлось отредактировать это дюжину раз только для того, чтобы иметь дело с символами новой строки, И ЭТО ПО-прежнему НЕ РАБОТАЕТ! @ # $% ^ & *)
Нет необходимости использовать versionbump.py, когда у нас есть отличный пакет bumpversion для python.
@Oran Я посмотрел на версию. Документы не очень понятны, и он не очень хорошо обрабатывает теги. В моем тестировании он, кажется, попадает в состояния, которые вызывают сбой: subprocess.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / folder / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngb o ']' вернул ненулевой статус выхода 1.
Еще одна хитрость - использовать PBR для управления некоторыми версиями. Вы можете использовать тот же процесс, чтобы пометить репо, и тогда PBR правильно вставит новую версию в команду setup.py.
@cmcginty Извините за задержку с ответом, проверьте мой ответ: stackoverflow.com/a/48664839/2656748
Почему нельзя отслеживать _version.py с помощью контроля версий?
@StevenVascellaro Это усложняет процесс выпуска. Теперь вы должны убедиться, что ваши теги и коммиты соответствуют значению в _version.py. Использование одного тега версии более чистое IMO и означает, что вы можете создать выпуск прямо из пользовательского интерфейса github.
похоже, что bumpversion не поддерживается. Вы можете использовать более новый bump2version
version.py только с параметром __version__ = <VERSION> в файле. В файле setup.py импортируйте параметр __version__ и поместите его значение в файл setup.py следующим образом:
version=__version__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 все в своем репо, иначе вы получите грязную ошибку репо.@cmcginty Извините за задержку, пожалуйста, проверьте мой ответ ^^^ - обратите внимание, что вы должны git commit или git reset все в вашем репо и убедиться, что ваша виртуальная среда включает пакет bumpversion, без него он не будет работать. Используйте последнюю версию.
Я немного не понимаю, какое решение здесь предлагается. Вы рекомендуете отслеживать версию вручную с помощью version.py или отслеживать ее с помощью bumpversion?
@StevenVascellaro Я предлагаю использовать bumpversion, никогда не используйте ручное управление версиями. Я попытался объяснить, что вы можете направить bumpversion либо для обновления версии в файле setup.py, либо, что еще лучше, использовать ее для обновления файла version.py. Чаще всего обновляют файл version.py и переносят значение параметра __version__ в файл setup.py. Мое решение используется в производстве, и это обычная практика. Примечание: для ясности: использование bumpversion как части скрипта - лучшее решение, поместите его в свой CI, и это будет автоматическая операция.
setuptools и pbrНе существует стандартного способа управления версией, но стандартным способом управления вашими пакетами является setuptools.
Лучшее решение, которое я нашел в целом для управления версией, - это использовать setuptools с расширением pbr. Теперь это мой стандартный способ управления версиями.
Настройка вашего проекта для полной упаковки может быть излишней для простых проектов, но если вам нужно управлять версией, вы, вероятно, находитесь на правильном уровне, чтобы просто все настроить. Это также делает ваш пакет доступным для выпуска по адресу PyPi, так что каждый может загрузить и использовать его с Pip.
PBR перемещает большую часть метаданных из инструментов setup.py в файл setup.cfg, который затем используется в качестве источника для большинства метаданных, которые могут включать версию. Это позволяет упаковать метаданные в исполняемый файл, используя что-то вроде pyinstaller, если это необходимо (если это так, вы, вероятно, будете использовать нужна эта информация), и отделяет метаданные от других сценариев управления / настройки пакетов. Вы можете напрямую обновить строку версии в setup.cfg вручную, и она будет помещена в папку *.egg-info при сборке выпусков вашего пакета. Затем ваши сценарии могут получить доступ к версии из метаданных с помощью различных методов (эти процессы описаны в разделах ниже).
При использовании Git для VCS / SCM эта настройка даже лучше, поскольку она будет извлекать много метаданных из Git, так что ваше репо может быть вашим основным источником правды для некоторых метаданных, включая версию, авторов, журналы изменений, и т. д. В частности, для версии он создаст строку версии для текущего коммита на основе тегов git в репозитории.
setup.py и файл setup.cfg с метаданными.Поскольку 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 sdistor one of thepython setup.py bdist_xxxcommands) the simplest way to update the git repo info into your<mypackage>.egg-infometadata folder is to just run thepython setup.py installcommand. This will run all the PBR functions related to pulling metadata from the git repo and update your local.egg-infofolder, 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-infofolder is generally excluded from being stored in the git repo itself in standard Python.gitignorefiles (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_INFOto.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
Переформатировано за голос "против", чтобы прямо сейчас ответить на вопрос.
После нескольких часов попыток найти самое простое и надежное решение, вот детали:
создайте файл 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, и поэтому сохранение рассудка имеет решающее значение, если ваш пакет будет выпущен в Сырная лавка.
Вот сокращенная разбивка вариантов управления версиями:
setup.py (setuptools) и получите версию.__init__.py, так и системы управления версиями), например bump2version, изменения или zest.releaser.__version__ в конкретном модуле.setup.py и используйте importlib.metadata, чтобы поднять его во время выполнения. (Внимание! Существуют версии до 3.8 и после 3.8.)__version__ в sample/__init__.py и импортируйте семпл в setup.py.ПРИМЕЧАНИЕ, который (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 для автоматического создания версии.
Это решение было получено из эта статья
Вариант использования - пакет графического интерфейса 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]
_version.txt в той же папке, что и __init__.py, и запишите версию в виде одной строки:0.8.2
_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()
К вашему сведению, есть очень хороший обзор по адресу: package.python.org/en/latest/…