Odpowiedzi:
Aby pobrać wersję z pakietu w czasie wykonywania (o co faktycznie chodzi w pytaniu), możesz użyć:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Jeśli chcesz przejść w drugą stronę (co wydaje się być tym, o czym inni autorzy odpowiedzi myśleli, że pytasz), umieść ciąg wersji w oddzielnym pliku i przeczytaj zawartość tego pliku setup.py
.
Możesz utworzyć version.py w swoim pakiecie z __version__
linią, a następnie przeczytać go z pliku setup.py za pomocą execfile('mypackage/version.py')
, aby ustawić go __version__
w przestrzeni nazw setup.py.
Jeśli potrzebujesz prostszego sposobu, który będzie działał ze wszystkimi wersjami Pythona, a nawet językami innymi niż Python, które mogą wymagać dostępu do ciągu wersji:
Zapisz wersję jako jedyną zawartość zwykłego pliku tekstowego o nazwie np. VERSION
I przeczytaj ten plik w trakcie setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
Ten sam VERSION
plik będzie działał wtedy dokładnie tak samo dobrze w każdym innym programie, nawet innym niż Python, i wystarczy zmienić ciąg znaków wersji w jednym miejscu dla wszystkich programów.
Nawiasem mówiąc, NIE importuj swojego pakietu z pliku setup.py, jak sugeruje to w innej odpowiedzi tutaj: wydaje się, że działa on dla Ciebie (ponieważ masz już zainstalowane zależności pakietu), ale sieją spustoszenie wśród nowych użytkowników Twojego pakietu , ponieważ nie będą w stanie zainstalować pakietu bez ręcznej instalacji zależności.
execfile
działa bardzo dobrze ... ale (niestety) nie działa z Pythonem 3.
with open('mypackage/version.py') as f: exec(f.read())
zamiast execfile('mypackage/version.py')
. (Od stackoverflow.com/a/437857/647002 )
mymodule
Wyobraź sobie taką konfigurację:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Następnie wyobraź sobie zwykły scenariusz, w którym masz zależności i setup.py
wygląda tak:
setup(...
install_requires=['dep1','dep2', ...]
...)
I przykład __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
I na przykład myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
podczas instalacjiJeśli setup.py
importujesz, mymodule
to podczas konfiguracji najprawdopodobniej otrzymasz plik ImportError
. Jest to bardzo częsty błąd, gdy pakiet ma zależności. Jeśli twój pakiet nie ma innych zależności niż wbudowane, możesz być bezpieczny; jednak nie jest to dobra praktyka. Powodem tego jest to, że nie jest przyszłościowe; powiedz, że jutro twój kod musi zużywać inną zależność.
__version__
?Jeśli zakodujesz __version__
na stałe setup.py
, może to nie odpowiadać wersji, którą dostarczysz w swoim module. Aby zachować spójność, umieściłbyś go w jednym miejscu i czytał z tego samego miejsca, kiedy tego potrzebujesz. Korzystanie z import
ciebie może mieć problem nr 1.
setuptools
Można by użyć kombinacji open
, exec
i zapewniają dict dla exec
zmiennych dodają:
# 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__'],
...)
I mymodule/version.py
ujawnij wersję:
__version__ = 'some.semantic.version'
W ten sposób wersja jest dostarczana z modułem i nie ma problemów podczas instalacji podczas próby zaimportowania modułu z brakującymi zależnościami (jeszcze do zainstalowania).
Najlepszą techniką jest zdefiniowanie __version__
kodu produktu, a następnie zaimportowanie go stamtąd do pliku setup.py. Daje to wartość, którą możesz odczytać w uruchomionym module i masz tylko jedno miejsce, aby ją zdefiniować.
Wartości w setup.py nie są instalowane, a setup.py nie utrzymuje się po instalacji.
Co zrobiłem (na przykład) w Cover.py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE (2017): Coverage.py nie importuje się już, aby pobrać wersję. Zaimportowanie własnego kodu może uniemożliwić jego odinstalowanie, ponieważ kod produktu będzie próbował zaimportować zależności, które nie są jeszcze zainstalowane, ponieważ instaluje je plik setup.py.
__version__
w którym się znajduje, jest w jakiś sposób uszkodzony, wówczas import również zostanie uszkodzony. Python nie wie, jak interpretować tylko te instrukcje, które chcesz. @pjeby ma rację: jeśli twój moduł musi zaimportować inne moduły, mogą nie zostać jeszcze zainstalowane i będzie chaotycznie. Ta technika działa, jeśli uważasz, że import nie powoduje długiego łańcucha innych importów.
pip install <packagename>
, pojawia się następujący błąd: ImportError: No module named <packagename>
. OSTRZEGAJ swoich czytelników, że nie możesz uruchamiać takich plików setup.py w środowiskach, w których pakiet nie jest jeszcze zainstalowany!
Nie byłem zadowolony z tych odpowiedzi ... nie chciałem wymagać narzędzi konfiguracyjnych ani tworzyć całego oddzielnego modułu dla jednej zmiennej, więc wymyśliłem je.
Gdy jesteś pewien, że główny moduł jest w stylu pep8 i taki pozostanie:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Jeśli chcesz być bardzo ostrożny i użyć prawdziwego parsera:
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 jest w pewnym sensie jednorazowym modułem, więc nie stanowi problemu, jeśli jest trochę brzydki.
Aktualizacja: dość zabawne Odszedłem od tego w ostatnich latach i zacząłem używać oddzielnego pliku w pakiecie o nazwie meta.py
. Umieściłem tam wiele metadanych, które chciałbym często zmieniać. A więc nie tylko dla jednej wartości.
ast.get_docstring()
z niektórymi .split('\n')[0].strip()
itp., Aby automatycznie wypełnić description
ze źródła. Jedna rzecz mniej do synchronizacji
Utwórz plik w swoim drzewie źródłowym, np. W yourbasedir / yourpackage / _version.py. Niech ten plik zawiera tylko jedną linię kodu, na przykład:
__version__ = "1.1.0-r4704"
Następnie w swoim setup.py otwórz ten plik i przeanalizuj numer wersji w następujący sposób:
verstr = "nieznany" próbować: verstrline = open ('yourpackage / _version.py', "rt"). read () z wyjątkiem EnvironmentError: pass # OK, brak pliku wersji. jeszcze: VSRE = r "^ __ wersja__ = ['\"] ([^' \ "] *) ['\"] " mo = re.search (VSRE, verstrline, re.M) jeśli mo: verstr = mo.group (1) jeszcze: podnieść RuntimeError ("nie można znaleźć wersji w twoim pakiecie / _version.py")
Na koniec w yourbasedir/yourpackage/__init__.py
import _version w ten sposób:
__version__ = "nieznane" próbować: z _version import __version__ oprócz ImportError: # Pracujemy w drzewie, które nie ma _version.py, więc nie wiemy, jaka jest nasza wersja. przechodzić
Przykładem kodu, który to robi, jest pakiet „pyutil”, którym się opiekuję. (Zobacz PyPI lub wyszukiwarkę Google - stackoverflow zabrania mi umieszczania hiperłącza do niego w tej odpowiedzi).
@pjeby ma rację, że nie powinieneś importować swojego pakietu z jego własnego pliku setup.py. To zadziała, gdy przetestujesz go, tworząc nowy interpreter Pythona i uruchamiając w nim setup.py: python setup.py
ale są przypadki, kiedy to nie zadziała. To dlatego, import youpackage
że nie oznacza odczytywania bieżącego katalogu roboczego dla katalogu o nazwie „twój pakiet”, oznacza to szukanie w sys.modules
nim klucza „twój pakiet”, a następnie robienie różnych rzeczy, jeśli go tam nie ma. Więc zawsze działa, gdy robisz, python setup.py
ponieważ masz świeży, pusty sys.modules
, ale to ogólnie nie działa.
Na przykład, co się stanie, jeśli py2exe wykonuje plik setup.py w ramach procesu pakowania aplikacji? Widziałem taki przypadek, w którym py2exe umieściłby nieprawidłowy numer wersji na pakiecie, ponieważ pakiet pobierał numer wersji zimport myownthing
w pliku setup.py, ale inna wersja tego pakietu została wcześniej zaimportowana podczas uruchamiania py2exe. Podobnie, co się stanie, jeśli setuptools, easy_install, dystrybucja lub distutils2 próbują zbudować Twój pakiet w ramach procesu instalowania innego pakietu, który zależy od Twojego? Następnie, czy twój pakiet jest możliwy do zaimportowania w czasie, gdy jego setup.py jest oceniany, czy też istnieje już wersja twojego pakietu, która została zaimportowana podczas życia tego interpretera Pythona, lub czy import twojego pakietu wymaga zainstalowania innych pakietów lub ma skutki uboczne, może zmienić wyniki. Miałem kilka problemów z próbą ponownego użycia pakietów Pythona, co spowodowało problemy z narzędziami takimi jak py2exe i setuptools, ponieważ ich setup.py importuje sam pakiet w celu znalezienia numeru wersji.
Nawiasem mówiąc, ta technika dobrze współgra z narzędziami do automatycznego tworzenia yourpackage/_version.py
pliku, na przykład czytając historię kontroli wersji i wypisując numer wersji w oparciu o najnowszy znacznik w historii kontroli wersji. Oto narzędzie, które robi to dla darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst, a tutaj jest fragment kodu, który robi to samo dla git: http: // github .com / warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Powinno to również zadziałać, używając wyrażeń regularnych i zależnie od pól metadanych, aby mieć taki format:
__fieldname__ = 'value'
Użyj poniższego na początku swojego setup.py:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Następnie możesz użyć metadanych w swoim skrypcie w następujący sposób:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Z taką strukturą:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
gdzie version.py zawiera:
__version__ = 'version_string'
Możesz to zrobić w setup.py :
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Nie spowoduje to żadnych problemów z zależnościami, które masz w swoim mymodule / __ init__.py
Aby uniknąć importowania pliku (a tym samym wykonywania jego kodu), można go przeanalizować i odzyskać version
atrybut z drzewa składni:
# 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
Ten kod próbuje znaleźć przypisanie __version__
lub VERSION
na najwyższym poziomie zwracanego modułu jest wartością ciągu. Prawa strona może być ciągiem lub krotką.
Istnieje tysiąc sposobów na oskórowanie kota - oto moje:
# 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.")
Czyszczenie https://stackoverflow.com/a/12413800 z @ 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
Teraz jest to obrzydliwe i wymaga pewnego dopracowania (może nawet być odkryte wezwanie członka w pkg_resources, które przegapiłem), ale po prostu nie rozumiem, dlaczego to nie działa, ani dlaczego nikt nie zasugerował tego do tej pory (wyszukiwanie w Google ma nie włączyłem tego) ... zauważ, że to jest Python 2.xi wymagałoby użycia pkg_resources (westchnienie):
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
Chcieliśmy umieścić meta informacje na temat naszego pakietu pypackagery
w __init__.py
, ale nie mógł, ponieważ ma zależności innych firm jako PJ Eby już wspomniano (patrz jego odpowiedź i ostrzeżenia dotyczące sytuacji wyścigu).
Rozwiązaliśmy to, tworząc osobny moduł, pypackagery_meta.py
który zawiera tylko metainformacje:
"""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'
następnie zaimportował metainformacje w packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
i ostatecznie użyłem go w 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'],
)
Musisz dołączyć pypackagery_meta
do swojego pakietu z py_modules
argumentem konfiguracji. W przeciwnym razie nie możesz go zaimportować podczas instalacji, ponieważ w pakiecie dystrybucyjnym byłoby go brakuje.
Prosty i prosty, utwórz plik o nazwie source/package_name/version.py
o następującej zawartości:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Następnie w swoim pliku source/package_name/__init__.py
importujesz wersję, z której mogą korzystać inne osoby:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Teraz możesz to założyć 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()
Przetestowane z Pythonem 2.7
, 3.3
, 3.4
, 3.5
, 3.6
i 3.7
na Linux, Windows i Mac OS. Użyłem na moim pakiecie, który ma testy integracji i testów jednostkowych dla wszystkich tych platform. Można zobaczyć wyniki .travis.yml
i appveyor.yml
tutaj:
Alternatywna wersja korzysta z menedżera kontekstu:
#!/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 ) )
Możesz także używać codecs
modułu do obsługi błędów Unicode zarówno w Pythonie, jak 2.7
i3.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 ) )
Jeśli piszesz moduł Pythona w 100% w C / C ++ przy użyciu Python C Extensions, możesz zrobić to samo, ale używając C / C ++ zamiast Python.
W takim przypadku utwórz 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'],
)
],
)
Który czyta wersję z pliku version.h
:
const char* __version__ = "1.0.12";
Ale nie zapomnij utworzyć pliku, MANIFEST.in
aby dołączyć version.h
plik:
include README.md
include LICENSE.txt
recursive-include source *.h
I jest zintegrowany z główną aplikacją za pomocą:
#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__ ) );
...
}
Bibliografia:
wdrożyć pakiet na serwerze i konwencję nazewnictwa plików dla pakietów indeksów:
przykład dynamicznej konwersji wersji pip:
zdobyć:
prochowiec:
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
Znajdź przykład setup.py wywołuje dynamiczne dopasowanie wersji pip z git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Używam zmiennej środowiskowej, jak poniżej
WERSJA = 0.0.0 python setup.py sdist bdist_wheel
W setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Do sprawdzenia spójności z wersją pakera używam poniższego skryptu.
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