Ответы:
Чтобы получить версию из вашего пакета во время выполнения (что, по-видимому, действительно задает ваш вопрос), вы можете использовать:
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 языками, которым может потребоваться доступ к строке версии:
Сохраните строку версии как единственное содержимое простого текстового файла с именем eg VERSION
и прочитайте этот файл во время setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
Этот же VERSION
файл будет работать точно так же, как и в любой другой программе, даже не в Python, и вам нужно всего лишь изменить строку версии в одном месте для всех программ.
Между прочим, НЕ импортируйте ваш пакет из вашего setup.py, как это предлагается в другом ответе здесь: он будет работать для вас (потому что у вас уже установлены зависимости вашего пакета), но он нанесет ущерб новым пользователям вашего пакета , так как они не смогут установить ваш пакет без предварительной ручной установки зависимостей.
execfile
работает очень хорошо ... но (к сожалению) не работает с Python 3.
with open('mypackage/version.py') as f: exec(f.read())
вместо execfile('mypackage/version.py')
. (Из stackoverflow.com/a/437857/647002 )
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
mymodule
во время установкиЕсли ваш setup.py
импорт mymodule
то во время настройки вы, скорее всего, получите ImportError
. Это очень распространенная ошибка, когда ваш пакет имеет зависимости. Если ваш пакет не имеет других зависимостей, кроме встроенных, вы можете быть в безопасности; Однако это не очень хорошая практика. Причина этого заключается в том, что это не будущее; скажем завтра ваш код должен потреблять некоторые другие зависимости.
__version__
?Если вы жестко __version__
вsetup.py
то оно может не совпадать с версией , что вы грузили бы в вашем модуле. Чтобы быть последовательным, вы должны поместить его в одном месте и читать его из того же места, когда вам это нужно. Использование import
вы можете получить проблему # 1.
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'
Таким образом, версия поставляется вместе с модулем, и у вас не возникает проблем во время установки, когда вы пытаетесь импортировать модуль с отсутствующими зависимостями (еще не установлен).
Лучше всего определить __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.
__version__
в нем, каким-то образом поврежден, то ваш импорт также будет поврежден. Python не знает, как интерпретировать только те выражения, которые вы хотите. @pjeby прав: если вашему модулю нужно импортировать другие модули, они могут быть еще не установлены, и это будет хаотично. Этот метод работает, если вы осторожны, что при импорте не возникает длинная цепочка других импортов.
pip install <packagename>
я получаю следующую ошибку:ImportError: No module named <packagename>
. Пожалуйста, ПРЕДУПРЕЖДАЙТЕ своим читателям, что вы не можете запускать файлы setup.py, подобные этим, в тех средах, где пакет еще не установлен!
Я не был доволен этими ответами ... не хотел требовать 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 является своего рода одноразовым модулем, поэтому не проблема, если он немного уродлив.
Обновление: достаточно забавно, я отошел от этого в последние годы и начал использовать отдельный файл в пакете под названием meta.py
. Я поместил там много метаданных, которые я мог бы захотеть часто менять. Таким образом, не только для одного значения.
ast.get_docstring()
с некоторыми и .split('\n')[0].strip()
т. Д. Для автоматического заполнения из description
источника. Одна вещь меньше , чтобы держать в синхронизации
Создайте файл в вашем исходном дереве, например, в yourbasedir / yourpackage / _version.py. Пусть этот файл содержит только одну строку кода, например:
__version__ = "1.1.0-r4704"
Затем в вашем файле setup.py откройте этот файл и проанализируйте номер версии следующим образом:
verstr = "неизвестно" пытаться: verstrline = open ('yourpackage / _version.py', "rt"). read () кроме EnvironmentError: pass # Хорошо, нет файла версии. еще: VSRE = r "^ __ version__ = ['\"] ([^' \ "] *) ['\"] " mo = re.search (VSRE, verstrline, re.M) если мо: verstr = mo.group (1) еще: поднять RuntimeError («невозможно найти версию в вашем пакете / _version.py»)
Наконец, в yourbasedir/yourpackage/__init__.py
_version импорта вот так:
__version__ = "неизвестно" пытаться: из _version import __version__ кроме ImportError: # Мы работаем в дереве, у которого нет _version.py, поэтому мы не знаем, какая у нас версия. проходить
Примером кода, который делает это, является пакет "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
файла для вас, например, читая историю контроля версий и записывая номер версии на основе самого последнего тега в истории контроля версий. Вот инструмент, который делает это для дарков: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst, а также фрагмент кода, который делает то же самое для git: http: // github .com / Уорнер / питон-ECDSA / BLOB / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Это также должно работать, используя регулярные выражения и в зависимости от полей метаданных иметь такой формат:
__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']
С такой структурой:
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
Чтобы избежать импорта файла (и, следовательно, выполнения его кода), можно проанализировать его и восстановить 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__
or VERSION
на верхнем уровне модуля, возвращающее строковое значение. Правая сторона может быть либо строкой, либо кортежем.
Есть тысяча способов снять шкуру с кошки - вот мой:
# 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.")
Очистка 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
Теперь это грубо и нуждается в некоторой доработке (возможно, в pkg_resources я пропустил непокрытый вызов члена, но я просто не понимаю, почему это не работает, и почему никто не предложил его на сегодняшний день (поиск в Google не включил это) ... обратите внимание, что это 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
Мы хотели поставить мета информацию о нашем пакете pypackagery
в __init__.py
, но не мог , так как он имеет зависимости сторонних , как PJ Эби уже отмечали (см его ответ и предупреждение относительно состояния гонки).
Мы решили это, создав отдельный модуль, 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
аргументом настройки. В противном случае вы не сможете импортировать его после установки, так как в упакованном дистрибутиве его не будет.
Просто и понятно, создайте файл 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
здесь:
Альтернативная версия использует менеджер контекста:
#!/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
и на Python.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__ ) );
...
}
Ссылки:
развернуть пакет на сервере и соглашение об именах файлов для пакетов индексов:
пример преобразования динамической версии в пипсах:
выиграть:
макинтош:
from setuptools_scm import get_version
def _get_version():
dev_version = str(".".join(map(str, str(get_version()).split("+")[0]\
.split('.')[:-1])))
return dev_version
Найдите образец setup.py вызывает динамическое соответствие версии пипа из git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Я использую переменную среды, как показано ниже
ВЕРСИЯ = 0.0.0 python setup.py sdist bdist_wheel
В setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Для проверки согласованности с версией упаковщика я использую приведенный ниже скрипт.
PKG_VERSION=`python -c "import pkg; print(pkg.__version__)"`
if [ $PKG_VERSION == $VERSION ]; then
python setup.py sdist bdist_wheel
else
echo "Package version differs from set env variable"
fi