Відповіді:
Щоб отримати версію зсередини вашого пакета під час виконання (про що, як видається, насправді задає ваше запитання), ви можете використовувати:
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