Как я могу получить версию, определенную в setup.py
из моего пакета (для --version
или других целей)?
Как я могу получить версию, определенную в setup.py(setuptools) в моем пакете?
Ответ 1
Опросить строку версии уже установленного дистрибутива
Чтобы получить версию изнутри вашего пакета во время выполнения (что ваш вопрос, кажется, действительно запрашивается), вы можете использовать:
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, как предлагается в другом ответе здесь: он, похоже, сработает для вас (потому что у вас уже установлены ваши зависимости в пакетах), но это навлечет на себя новых пользователей вашего пакета, так как они не смогут установить пакет без ручной установки зависимостей.
Ответ 2
пример исследования: 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: где my __version__
?
Если вы жестко обозначили __version__
в setup.py
, то он может не соответствовать версии, которую вы отправили бы в своем модуле. Чтобы быть последовательным, вы бы поставили его в одном месте и прочитали его с того же места, когда вам это нужно. Используя import
, вы можете получить проблему №1.
решение: à la setuptools
Вы должны использовать комбинацию open
, exec
и предоставить dict для 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'
Таким образом, версия поставляется вместе с модулем, и у вас нет проблем во время установки, пытаясь импортировать модуль с отсутствующими зависимостями (еще не установлен).
Ответ 3
Лучший способ - определить __version__
в вашем коде продукта, а затем импортировать его в setup.py. Это дает вам значение, которое вы можете прочитать в своем рабочем модуле, и иметь только одно место для его определения.
Значения в setup.py не установлены, и setup.py не встает после установки.
Что я сделал (например) в сфере охвата .py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE (2017): cover.py больше не импортирует себя, чтобы получить версию. Импорт собственного кода может привести к его удалению, поскольку код продукта будет пытаться импортировать зависимости, которые еще не установлены, поскольку setup.py - это то, что их устанавливает.
Ответ 4
Ваш вопрос немного расплывчатый, но я думаю, что вы спрашиваете, как его указать.
Вам нужно определить __version__
следующим образом:
__version__ = '1.4.4'
И затем вы можете подтвердить, что setup.py знает о только что указанной версии:
% ./setup.py --version
1.4.4
Ответ 5
Я не был доволен этими ответами... не хотел требовать 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 в некотором роде является одноразовым модулем, поэтому не проблема, если он немного уродлив.
Ответ 6
Создайте файл в исходном дереве, например. в 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 search - 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 run. Точно так же, если setuptools, easy_install, distribute или 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
Ответ 7
Это также должно работать, используя регулярные выражения и в зависимости от полей метаданных иметь такой формат:
__fieldname__ = 'value'
Используйте следующее в начале настройки .py:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
После этого вы можете использовать метаданные в script следующим образом:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Ответ 8
С такой структурой:
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__
Это не вызовет никаких проблем с зависимостями, которые у вас есть в вашем mymodule/__ init__.py
Ответ 9
Чтобы избежать импорта файла (и, таким образом, его код), можно проанализировать его и восстановить атрибут version
из дерева синтаксиса:
# assuming 'path' holds the path to the file
import ast
with open(path, 'rU') as file:
t = compile(file.read(), path, 'exec', ast.PyCF_ONLY_AST)
for node in (n for n in t.body if isinstance(n, ast.Assign)):
if len(node.targets) == 1:
name = node.targets[0]
if isinstance(name, ast.Name) and \
name.id in ('__version__', '__version_info__', 'VERSION'):
v = node.value
if isinstance(v, ast.Str):
version = v.s
break
if isinstance(v, ast.Tuple):
r = []
for e in v.elts:
if isinstance(e, ast.Str):
r.append(e.s)
elif isinstance(e, ast.Num):
r.append(str(e.n))
version = '.'.join(r)
break
Этот код пытается найти назначение __version__
или version
на верхнем уровне возврата модуля - это строковое значение. Правая сторона может быть либо строкой, либо кортежем.
Ответ 10
Там тысячи способов кожи кошки - здесь моя:
# Copied from (and hacked):
# https://github.com/pypa/virtualenv/blob/develop/setup.py#L42
def get_version(filename):
import os
import re
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, filename))
version_file = f.read()
f.close()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
Ответ 11
Очистка fooobar.com/questions/24509/... от @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
Ответ 12
Мы хотели поместить метаинформацию о пакете 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__ = '[email protected]'
__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
установки py_modules
. В противном случае вы не сможете импортировать его после установки, так как в упакованном дистрибутиве его не будет.
Ответ 13
Теперь это грубо и нуждается в некоторой доработке (может быть, даже непокрытый вызов участника в pkg_resources, который я пропустил), но я просто не понимаю, почему это не работает, и почему никто не предложил его на сегодняшний день ( Googling вокруг не изменил это)... обратите внимание, что это Python 2.x и потребует pkg_resources (sigh):
import pkg_resources
version_string = None
try:
if pkg_resources.working_set is not None:
disto_obj = pkg_resources.working_set.by_key.get('<my pkg name>', None)
# (I like adding ", None" to gets)
if disto_obj is not None:
version_string = disto_obj.version
except Exception:
# Do something
pass
Ответ 14
Просто и понятно, создайте файл с именем source/package_name/version.py
со следующим содержимым:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Затем в файле source/package_name/__init__.py
вы импортируете версию для использования другими людьми:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Теперь вы можете положить это на setup.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
version_file = open( filepath )
__version__ ,= re.findall( '__version__ = "(.*)"', version_file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
finally:
version_file.close()
Протестировано это с Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
и 3.7
на Linux, Windows и Mac OS. Я использовал пакет, в котором есть Интеграция и Модульные тесты для всех этих платформ. Вы можете увидеть результаты .travis.yml
и appveyor.yml
здесь:
- https://travis-ci.org/evandrocoan/debugtools/builds/527110800
- https://ci.appveyor.com/project/evandrocoan/pythondebugtools/builds/24245446
Альтернативная версия использует менеджер контекста:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
with open( filepath ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
Вы также можете использовать модуль codecs
для обработки ошибок Unicode как в Python 2.7
и в 3.6
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
try:
filepath = 'source/package_name/version.py'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
Если вы пишете модуль Python на 100% на C/C++ с использованием расширений Python C, вы можете сделать то же самое, но с использованием C/C++ вместо Python.
В этом случае создайте следующий setup.py
:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
from setuptools import setup, Extension
try:
filepath = 'source/version.h'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
setup(
name = 'package_name',
version = __version__,
package_data = {
'': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
},
ext_modules = [
Extension(
name = 'package_name',
sources = [
'source/file.cpp',
],
include_dirs = ['source'],
)
],
)
Который читает версию из файла version.h
:
const char* __version__ = "1.0.12";
Но не забудьте создать файл MANIFEST.in
для включения файла version.h
:
include README.md
include LICENSE.txt
recursive-include source *.h
И он интегрирован в основное приложение с:
#include <Python.h>
#include "version.h"
// create the module
PyMODINIT_FUNC PyInit_package_name(void)
{
PyObject* thismodule;
...
// https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue
PyObject_SetAttrString( thismodule, "__version__", Py_BuildValue( "s", __version__ ) );
...
}
Рекомендации:
- Ошибка открытого файла Python
- Определить глобальный в модуле Python из C API
- Как включить данные пакета с помощью setuptools/distribution?
- https://github.com/lark-parser/lark/blob/master/setup.py#L4
- Как использовать пакеты setuptools и ext_modules с одинаковыми именами?
- Можно ли включить подкаталоги, используя dist utils (setup.py) как часть данных пакета?