Как я могу получить версию, определенную в setup.py (setuptools) в моем пакете? - PullRequest
134 голосов
/ 13 января 2010

Как я могу получить версию, определенную в setup.py, из моего пакета (для --version или других целей)?

Ответы [ 14 ]

223 голосов
/ 15 января 2010

Запрос строки версии уже установленного дистрибутива

Чтобы получить версию из вашего пакета во время выполнения (что, по-видимому, действительно задает ваш вопрос), вы можете использовать:

import pkg_resources  # part of setuptools
version = pkg_resources.require("MyProject")[0].version

Сохранить строку версии для использования во время установки

Если вы хотите пойти по другому пути (что похоже на то, что другие авторы ответов здесь, похоже, думали, что вы спрашивали), поместите строку версии в отдельный файл и прочитайте содержимое этого файла в setup.py.

Вы можете сделать version.py в своем пакете со строкой __version__, а затем прочитать его из setup.py, используя execfile('mypackage/version.py'), чтобы он устанавливал __version__ в пространстве имен setup.py.

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

Сохранение строки версии как единственного содержимого простого текстового файла с именем, например, VERSION и прочитайте этот файл во время setup.py.

version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()

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

Предупреждение о состоянии гонки во время установки

Кстати, НЕ импортируйте ваш пакет из вашего setup.py, как это предлагается в другом ответе здесь: он будет работать для вас (потому что у вас уже установлены зависимости вашего пакета), но он нанесет ущерб новым пользователям вашего пакета, поскольку они не смогут установить ваш пакет без предварительной ручной установки зависимостей.

29 голосов
/ 01 июля 2014

пример исследования: mymodule

Представьте себе эту конфигурацию:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py

Тогда представьте себе обычный сценарий, в котором у вас есть зависимости и setup.py выглядит так:

setup(...
    install_requires=['dep1','dep2', ...]
    ...)

И пример __init__.py:

from mymodule.myclasses import *
from mymodule.version import __version__

А например myclasses.py:

# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2

проблема # 1: импорт mymodule во время установки

Если ваш setup.py импортирует mymodule, то во время установки , скорее всего, вы получите ImportError. Это очень распространенная ошибка, когда ваш пакет имеет зависимости. Если ваш пакет не имеет других зависимостей, кроме встроенных, вы можете быть в безопасности; Однако это не очень хорошая практика. Причина этого заключается в том, что это не будущее; скажем, завтра ваш код должен использовать какую-то другую зависимость.

проблема № 2: где мой __version__?

Если вы жестко закодировали __version__ в setup.py, тогда он может не совпадать с версией, которую вы поставили бы в своем модуле. Чтобы быть последовательным, вы должны поместить его в одно место и прочитать его из того же места, когда вам это нужно. Используя import, вы можете получить проблему # 1.

решение: а-ля setuptools

Вы бы использовали комбинацию open, exec и указали бы exec для добавления переменных:

# setup.py
from setuptools import setup, find_packages
from distutils.util import convert_path

main_ns = {}
ver_path = convert_path('mymodule/version.py')
with open(ver_path) as ver_file:
    exec(ver_file.read(), main_ns)

setup(...,
    version=main_ns['__version__'],
    ...)

А в mymodule/version.py выставь версию:

__version__ = 'some.semantic.version'

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

14 голосов
/ 13 января 2010

Лучший способ - определить __version__ в коде вашего продукта, а затем импортировать его в setup.py оттуда. Это дает вам значение, которое вы можете прочитать в своем работающем модуле, и у вас есть только одно место для его определения.

Значения в setup.py не установлены, и setup.py не остается после установки.

Что я сделал (например) в cover.py:

# coverage/__init__.py
__version__ = "3.2"


# setup.py
from coverage import __version__

setup(
    name = 'coverage',
    version = __version__,
    ...
    )

ОБНОВЛЕНИЕ (2017): cover.py больше не импортирует себя для получения версии. Импорт собственного кода может сделать его неустранимым, потому что код вашего продукта будет пытаться импортировать зависимости, которые еще не установлены, потому что их устанавливает setup.py.

12 голосов
/ 13 января 2010

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

Вам нужно определить __version__ примерно так:

__version__ = '1.4.4'

И затем вы можете подтвердить, что setup.py знает о только что указанной вами версии:

% ./setup.py --version
1.4.4
7 голосов
/ 13 сентября 2012

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

Если вы уверены, что основной модуль выполнен в стиле pep8 и останется таким:

version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            _, _, version = line.replace("'", '').split()
            break

Если вы хотите быть очень осторожным и использовать настоящий парсер:

import ast
version = '0.30.unknown2'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            version = ast.parse(line).body[0].value.s
            break

setup.py - своего рода одноразовый модуль, поэтому не проблема, если он немного уродлив.

4 голосов
/ 01 сентября 2010

Создайте файл в вашем исходном дереве, например, в yourbasedir / yourpackage / _version.py. Пусть этот файл содержит только одну строку кода, например:

__version__ = "1.1.0-r4704"

Затем в вашем файле setup.py откройте этот файл и найдите номер версии следующим образом:

verstr = "unknown"
try:
    verstrline = open('yourpackage/_version.py', "rt").read()
except EnvironmentError:
    pass # Okay, there is no version file.
else:
    VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
    mo = re.search(VSRE, verstrline, re.M)
    if mo:
        verstr = mo.group(1)
    else:
        raise RuntimeError("unable to find version in yourpackage/_version.py")

Наконец, в yourbasedir/yourpackage/__init__.py import _version вот так:

__version__ = "unknown"
try:
    from _version import __version__
except ImportError:
    # We're running in a tree that doesn't have a _version.py, so we don't know what our version is.
    pass

Примером кода, который делает это, является пакет "pyutil", который я поддерживаю. (См. PyPI или поиск в Google - stackoverflow запрещает мне включать гиперссылку на него в этом ответе.)

@ pjeby прав, что вы не должны импортировать свой пакет из его собственного setup.py. Это сработает, когда вы протестируете его, создав новый интерпретатор Python и выполнив в нем файл setup.py: python setup.py, но есть случаи, когда он не работает. Это потому, что import youpackage не означает чтение текущего рабочего каталога для каталога с именем «yourpackage», это означает поиск в текущем sys.modules ключа «yourpackage» и затем выполнение различных действий, если это не так. там. Так что это всегда работает, когда вы делаете python setup.py, потому что у вас есть свежий, пустой sys.modules, но это не работает в целом.

Например, что если py2exe выполняет ваш setup.py как часть процесса упаковки приложения? Я видел такой случай, когда py2exe помещал неправильный номер версии в пакет, потому что пакет получал свой номер версии из import myownthing в своем файле setup.py, но другая версия этого пакета ранее была импортирована во время py2exe запускается. Аналогичным образом, что если setuptools, easy_install, distribution или distutils2 пытаются собрать ваш пакет как часть процесса установки другого пакета, который зависит от вашего? Затем, будет ли ваш пакет импортируемым во время оценки его setup.py, или уже существует версия вашего пакета, которая была импортирована в течение жизни этого интерпретатора Python, или для импорта вашего пакета сначала должны быть установлены другие пакеты. или имеет побочные эффекты, может изменить результаты. У меня было несколько попыток повторно использовать пакеты Python, что вызывало проблемы для таких инструментов, как py2exe и setuptools, потому что их setup.py импортирует сам пакет, чтобы найти его номер версии.

Кстати, эта техника прекрасно работает с инструментами для автоматического создания файла yourpackage/_version.py для вас, например, путем чтения вашей истории контроля версий и записи номера версии на основе самого последнего тега в истории контроля версий. Вот инструмент, который делает это для darcs: http://tahoe -lafs.org / trac / darcsver / browser / trunk / README.rst и вот фрагмент кода, который делает то же самое для git: http://github.com/warner/python-ecdsa/blob/0ed702a9d4057ecf33eea969b8cf280eaccd89a1/setup.py#L34

3 голосов
/ 21 сентября 2018

Со структурой, подобной этой:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py

, где version.py содержит:

__version__ = 'version_string'

Вы можете сделать это в setup.py :

import sys

sys.path[0:0] = ['mymodule']

from version import __version__

Это не вызовет никаких проблем с какими-либо зависимостями, которые есть у вас в вашем модуле / __ init __. Py

3 голосов
/ 03 мая 2011

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

__fieldname__ = 'value'

Используйте следующее в начале вашего setup.py:

import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))

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

print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
2 голосов
/ 18 сентября 2018

Мы хотели поместить метаинформацию о нашем пакете pypackagery в __init__.py, но не смогли, поскольку он имеет сторонние зависимости, как уже указывал PJ Eby (см. Его ответ и предупреждение относительно состояния гонки).

Мы решили эту проблему, создав отдельный модуль pypackagery_meta.py, содержащий только метаинформацию:

"""Define meta information about pypackagery package."""

__title__ = 'pypackagery'
__description__ = ('Package a subset of a monorepo and '
                   'determine the dependent packages.')
__url__ = 'https://github.com/Parquery/pypackagery'
__version__ = '1.0.0'
__author__ = 'Marko Ristin'
__author_email__ = 'marko.ristin@gmail.com'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'

затем импортировал метаинформацию в packagery/__init__.py:

# ...

from pypackagery_meta import __title__, __description__, __url__, \
    __version__, __author__, __author_email__, \
    __license__, __copyright__

# ...

и наконец использовал его в setup.py:

import pypackagery_meta

setup(
    name=pypackagery_meta.__title__,
    version=pypackagery_meta.__version__,
    description=pypackagery_meta.__description__,
    long_description=long_description,
    url=pypackagery_meta.__url__,
    author=pypackagery_meta.__author__,
    author_email=pypackagery_meta.__author_email__,
    # ...
    py_modules=['packagery', 'pypackagery_meta'],
 )

Вы должны включить pypackagery_meta в свой пакет с py_modules аргументом установки. В противном случае вы не сможете импортировать его после установки, поскольку в упакованном дистрибутиве его не будет.

2 голосов
/ 27 мая 2015

Очистка https://stackoverflow.com/a/12413800 от @ gringo-suave:

from itertools import ifilter
from os import path
from ast import parse

with open(path.join('package_name', '__init__.py')) as f:
    __version__ = parse(next(ifilter(lambda line: line.startswith('__version__'),
                                     f))).body[0].value.s
...