Відповіді:
Щоб отримати версію зсередини вашого пакета під час виконання (про що, як видається, насправді задає ваше запитання), ви можете використовувати:
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, як це запропоновано в іншій відповіді тут: він, здається, спрацює для вас (адже у вас вже встановлені залежності вашого пакета), але він спричинить хаос для нових користувачів вашого пакету. , оскільки вони не зможуть встановити ваш пакет, не спершу вручну встановивши залежності.
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): покриття.py більше не імпортує себе, щоб отримати версію. Імпорт власного коду може видалити його, оскільки код продукту намагатиметься імпортувати залежності, які ще не встановлені, тому що setup.py - це те, що їх встановлює.
__version__ним розміщений якимось чином, то імпорт також буде порушений. Python не знає, як лише інтерпретувати потрібні твердження. @pjeby має рацію: якщо вашому модулю потрібно імпортувати інші модулі, вони, можливо, ще не встановлені, і це буде хаотично. Ця методика працює, якщо ви обережні, що імпорт не заважає довгому ланцюгу іншого імпорту.
pip install <packagename>, я отримую наступне повідомлення про помилку: ImportError: No module named <packagename>. Будь ласка, ЗАПЕРЕДУЙТЕ своїх читачів про те, що ви не можете запускати такі файли setup.py у середовищах, де пакет ще не встановлений!
Я не був задоволений цими відповідями ... не хотів вимагати налаштувань, а також не робити цілого окремого модуля для однієї змінної, тому я придумав ці.
Бо коли ви впевнені, що основний модуль виконаний у стилі 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 "^ __ версія__ = ['\"] ([^' \ "] *) ['\"] "
mo = повторний пошук (VSRE, verstrline, re.M)
якщо мо:
verstr = mo.group (1)
ще:
підняти RuntimeError ("не вдається знайти версію у yourpackage / _version.py")
Нарешті, в yourbasedir/yourpackage/__init__.pyімпорті _версії, як це:
__version__ = "невідомо"
спробуйте:
з імпорту _version __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, розповсюджувати або 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
Це також має працювати, використовуючи регулярні вирази та залежно від полів метаданих мати такий формат:
__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__або 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, який я пропустив), але я просто не бачу, чому це не працює, і чому ніхто не запропонував цього на сьогодні (Googling around has не підключило це) ... зауважте, що це Python 2.x, і для нього знадобиться pkg_resources (зітхання):
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і на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__ ) );
...
}
Список літератури:
розгортати пакет на сервері та назви файлів для іменних пакетів:
приклад перетворення динамічної версії в pip:
виграти:
Мак:
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, який викликає динамічну версію pip, що відповідає git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Я використовую змінну середовища, як показано нижче
VERSION = 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