Antworten:
Um die Version zur Laufzeit aus Ihrem Paket abzurufen (was Ihre Frage tatsächlich zu stellen scheint), können Sie Folgendes verwenden:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Wenn Sie in die andere Richtung gehen möchten (was andere Antwortautoren hier anscheinend gedacht haben), fügen Sie die Versionszeichenfolge in eine separate Datei ein und lesen Sie den Inhalt dieser Datei ein setup.py
.
Sie können eine version.py in Ihrem Paket mit einer __version__
Zeile erstellen und diese dann aus setup.py mit lesen execfile('mypackage/version.py')
, sodass sie __version__
im Namespace setup.py festgelegt wird.
Wenn Sie eine viel einfachere Methode wünschen, die mit allen Python-Versionen und sogar Nicht-Python-Sprachen funktioniert, die möglicherweise Zugriff auf die Versionszeichenfolge benötigen:
Speichern Sie die Versionszeichenfolge als einzigen Inhalt einer VERSION
Nur- Text-Datei mit dem Namen z. B. und lesen Sie diese Datei während setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
Dieselbe VERSION
Datei funktioniert dann genauso gut in jedem anderen Programm, auch in Nicht-Python-Programmen, und Sie müssen die Versionszeichenfolge für alle Programme nur an einer Stelle ändern.
Übrigens, importieren Sie Ihr Paket NICHT aus Ihrer setup.py, wie in einer anderen Antwort hier vorgeschlagen: Es scheint für Sie zu funktionieren (da Sie bereits die Abhängigkeiten Ihres Pakets installiert haben), aber es wird neuen Benutzern Ihres Pakets Schaden zufügen , da sie Ihr Paket nicht installieren können, ohne zuerst die Abhängigkeiten manuell zu installieren.
execfile
funktioniert wirklich gut ... aber (leider) funktioniert nicht mit Python 3.
with open('mypackage/version.py') as f: exec(f.read())
anstelle von verwenden execfile('mypackage/version.py')
. (Von stackoverflow.com/a/437857/647002 )
mymodule
Stellen Sie sich diese Konfiguration vor:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Stellen Sie sich dann ein übliches Szenario vor, in dem Sie Abhängigkeiten haben und setup.py
wie folgt aussehen:
setup(...
install_requires=['dep1','dep2', ...]
...)
Und ein Beispiel __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
Und zum Beispiel myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
während des SetupsWenn Sie dann während des Setupssetup.py
importieren, erhalten Sie höchstwahrscheinlich eine . Dies ist ein sehr häufiger Fehler, wenn Ihr Paket Abhängigkeiten aufweist. Wenn Ihr Paket keine anderen Abhängigkeiten als die integrierten enthält, sind Sie möglicherweise sicher. Dies ist jedoch keine gute Praxis. Der Grund dafür ist, dass es nicht zukunftssicher ist; Sagen Sie morgen, Ihr Code muss eine andere Abhängigkeit verbrauchen.mymodule
ImportError
__version__
?Wenn Sie codieren __version__
in setup.py
dann kann es nicht mit der Version übereinstimmen , dass Sie in Ihrem Modul versenden würden. Um konsistent zu sein, würden Sie es an einer Stelle ablegen und an derselben Stelle lesen, wenn Sie es benötigen. Bei Verwendung import
kann das Problem Nr. 1 auftreten.
setuptools
Sie würden eine Kombination aus verwenden open
, exec
und ein dict für bieten exec
hinzuzufügen Variablen:
# 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__'],
...)
Und in mymodule/version.py
der Version aussetzen:
__version__ = 'some.semantic.version'
Auf diese Weise wird die Version mit dem Modul geliefert, und Sie haben keine Probleme beim Einrichten eines Moduls mit fehlenden Abhängigkeiten (das noch installiert werden muss).
Die beste Technik besteht darin, __version__
in Ihrem Produktcode zu definieren und ihn dann von dort in setup.py zu importieren. Dies gibt Ihnen einen Wert, den Sie in Ihrem laufenden Modul lesen können und der nur an einer Stelle definiert werden kann.
Die Werte in setup.py werden nicht installiert, und setup.py bleibt nach der Installation nicht erhalten.
Was ich (zum Beispiel) in Coverage.py gemacht habe:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE (2017): Coverage.py importiert sich nicht mehr selbst, um die Version zu erhalten. Durch das Importieren Ihres eigenen Codes kann es deinstalliert werden, da Ihr Produktcode versucht, Abhängigkeiten zu importieren, die noch nicht installiert sind, da sie durch setup.py installiert werden.
__version__
auf irgendeine Weise beschädigt ist, wird auch Ihr Import unterbrochen. Python kann nicht nur die gewünschten Anweisungen interpretieren. @pjeby ist richtig: Wenn Ihr Modul andere Module importieren muss, sind diese möglicherweise noch nicht installiert und es wird chaotisch. Diese Technik funktioniert, wenn Sie darauf achten, dass der Import keine lange Kette anderer Importe verursacht.
pip install <packagename>
die folgende Fehlermeldung erhalte : ImportError: No module named <packagename>
. Bitte warnen Sie Ihre Leser, dass Sie solche setup.py-Dateien in Umgebungen, in denen das Paket noch nicht installiert ist, nicht ausführen können!
Ihre Frage ist etwas vage, aber ich denke, Sie fragen, wie Sie sie spezifizieren sollen.
Sie müssen wie folgt definieren __version__
:
__version__ = '1.4.4'
Und dann können Sie bestätigen, dass setup.py die gerade angegebene Version kennt:
% ./setup.py --version
1.4.4
Ich war mit diesen Antworten nicht zufrieden ... wollte weder Setuptools benötigen noch ein ganzes separates Modul für eine einzelne Variable erstellen, also habe ich mir diese ausgedacht.
Wenn Sie sicher sind, dass das Hauptmodul im pep8-Stil vorliegt und dies auch bleibt:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Wenn Sie besonders vorsichtig sein und einen echten Parser verwenden möchten:
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 ist so etwas wie ein Wegwerfmodul, also kein Problem, wenn es etwas hässlich ist.
Update: Lustigerweise habe ich mich in den letzten Jahren davon entfernt und angefangen, eine separate Datei im Paket namens zu verwenden meta.py
. Ich habe dort viele Metadaten eingefügt, die ich möglicherweise häufig ändern möchte. Also nicht nur für einen Wert.
ast.get_docstring()
mit einigen .split('\n')[0].strip()
usw. verwenden, um automatisch description
aus der Quelle auszufüllen . Eine Sache weniger, um synchron zu bleiben
Erstellen Sie eine Datei in Ihrem Quellbaum, z. B. in yourbasedir / yourpackage / _version.py. Lassen Sie diese Datei nur eine einzige Codezeile enthalten, wie folgt:
__version__ = "1.1.0-r4704"
Öffnen Sie dann in Ihrer setup.py diese Datei und analysieren Sie die Versionsnummer wie folgt:
verstr = "unbekannt" Versuchen: verstrline = open ('yourpackage / _version.py', "rt"). read () außer EnvironmentError: pass # Okay, es gibt keine Versionsdatei. sonst: VSRE = r "^ __ version__ = ['\"] ([^' \ "] *) ['\"] " mo = re.search (VSRE, verstrline, re.M) wenn mo: verstr = mo.group (1) sonst: RuntimeError auslösen ("Version in yourpackage / _version.py konnte nicht gefunden werden")
Schließlich in yourbasedir/yourpackage/__init__.py
import _version wie folgt:
__version__ = "unbekannt" Versuchen: von _version import __version__ außer ImportError: # Wir laufen in einem Baum ohne _version.py, daher wissen wir nicht, was unsere Version ist. bestehen
Ein Beispiel für Code, der dies tut, ist das von mir verwaltete "pyutil" -Paket. (Siehe PyPI oder Google-Suche - Stackoverflow verbietet mir, einen Hyperlink in diese Antwort aufzunehmen.)
@pjeby ist richtig, dass Sie Ihr Paket nicht aus seiner eigenen setup.py importieren sollten. Das funktioniert, wenn Sie es testen, indem Sie einen neuen Python-Interpreter erstellen und als erstes setup.py ausführen: python setup.py
aber es gibt Fälle, in denen es nicht funktioniert. Das liegt daran, import youpackage
dass Sie nicht das aktuelle Arbeitsverzeichnis für ein Verzeichnis mit dem Namen "yourpackage" lesen müssen, sondern im aktuellen sys.modules
nach einem Schlüssel "yourpackage" suchen und dann verschiedene Dinge tun müssen, wenn es nicht vorhanden ist. Es funktioniert also immer, wenn Sie es tun, python setup.py
weil Sie ein frisches, leeres haben sys.modules
, aber das funktioniert im Allgemeinen nicht.
Was ist zum Beispiel, wenn py2exe Ihre setup.py im Rahmen des Packens einer Anwendung ausführt? Ich habe einen Fall wie diesen gesehen, in dem py2exe die falsche Versionsnummer auf ein Paket gesetzt hat, weil das Paket seine Versionsnummer von erhalten hatimport myownthing
in seiner setup.py, aber eine andere Version dieses Pakets wurde zuvor während des py2exe-Laufs importiert. Was ist, wenn setuptools, easy_install, Distribute oder distutils2 versuchen, Ihr Paket als Teil eines Installationsprozesses für ein anderes Paket zu erstellen, das von Ihrem abhängt? Dann, ob Ihr Paket zum Zeitpunkt der Auswertung von setup.py importierbar ist oder ob bereits eine Version Ihres Pakets vorhanden ist, die während des Lebens dieses Python-Interpreters importiert wurde, oder ob für den Import Ihres Pakets zuerst andere Pakete installiert werden müssen oder hat Nebenwirkungen, kann die Ergebnisse ändern. Ich hatte mehrere Probleme mit dem Versuch, Python-Pakete wiederzuverwenden, was Probleme für Tools wie py2exe und setuptools verursachte, da deren setup.py das Paket selbst importiert, um seine Versionsnummer zu finden.
Übrigens spielt diese Technik gut mit Tools, mit denen die yourpackage/_version.py
Datei automatisch für Sie erstellt werden kann, z. B. indem Sie Ihren Revisionskontrollverlauf lesen und eine Versionsnummer schreiben, die auf dem neuesten Tag im Revisionskontrollverlauf basiert. Hier ist ein Tool, das das für darcs macht: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst und hier ist ein Code-Snippet, das dasselbe für git macht: http: // github .com / warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Dies sollte auch funktionieren, indem reguläre Ausdrücke verwendet werden und abhängig von den Metadatenfeldern ein Format wie das folgende verwendet wird:
__fieldname__ = 'value'
Verwenden Sie zu Beginn Ihrer setup.py Folgendes:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Danach können Sie die Metadaten in Ihrem Skript folgendermaßen verwenden:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Mit einer Struktur wie dieser:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
wo version.py enthält:
__version__ = 'version_string'
Sie können dies in setup.py tun :
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Dies verursacht keine Probleme mit den Abhängigkeiten, die Sie in Ihrem mymodule / __ init__.py haben
Um zu vermeiden, dass eine Datei importiert wird (und somit ihr Code ausgeführt wird), können Sie sie analysieren und das version
Attribut aus dem Syntaxbaum wiederherstellen :
# 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
Dieser Code versucht, die __version__
oder VERSION
-Zuweisung auf der obersten Ebene der Modulrückgabe als Zeichenfolgenwert zu finden. Die rechte Seite kann entweder eine Zeichenfolge oder ein Tupel sein.
Es gibt tausend Möglichkeiten, eine Katze zu häuten - hier ist meine:
# 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.")
Bereinigen von https://stackoverflow.com/a/12413800 von @ 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
Das ist eklig und muss verfeinert werden (es kann sogar einen ungedeckten Mitgliederaufruf in pkg_resources geben, den ich verpasst habe), aber ich verstehe einfach nicht, warum dies nicht funktioniert oder warum es bisher niemand vorgeschlagen hat (Googeln hat es getan) nicht aufgedreht) ... beachte, dass dies Python 2.x ist und pkg_resources erfordern würde (seufz):
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
Wir wollten die Meta - Informationen über unser Paket setzen pypackagery
in __init__.py
, konnte aber nicht , da sie von Drittanbietern Abhängigkeiten wie PJ Eby hat bereits (siehe seine Antwort und die Warnung in Bezug auf die Race - Bedingung) hingewiesen.
Wir haben es gelöst, indem wir ein separates Modul erstellt haben pypackagery_meta.py
, das nur die Metainformationen enthält:
"""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'
importierte dann die Metainformationen in packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
und schließlich verwendet in 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'],
)
Sie müssen das Setup-Argument pypackagery_meta
in Ihr Paket aufnehmen py_modules
. Andernfalls können Sie es bei der Installation nicht importieren, da es der gepackten Distribution fehlen würde.
Erstellen Sie einfach und direkt eine Datei source/package_name/version.py
mit dem folgenden Inhalt:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Anschließend source/package_name/__init__.py
importieren Sie in Ihre Datei die Version, die andere Benutzer verwenden können:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Jetzt können Sie dies anziehen 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()
Getestet dies mit Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
und 3.7
auf Linux, Windows und Mac OS. Ich habe mein Paket verwendet, das Integrations- und Unit-Tests für alle diese Plattformen enthält. Sie können die Ergebnisse von .travis.yml
und appveyor.yml
hier sehen:
Eine alternative Version verwendet den Kontextmanager:
#!/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 ) )
Sie können das codecs
Modul auch verwenden, um Unicode-Fehler sowohl in Python 2.7
als auch in Python zu behandeln3.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 ) )
Wenn Sie ein Python-Modul zu 100% in C / C ++ mit Python C-Erweiterungen schreiben, können Sie dasselbe tun, jedoch C / C ++ anstelle von Python verwenden.
Erstellen Sie in diesem Fall Folgendes 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'],
)
],
)
Welches liest die Version aus der Datei version.h
:
const char* __version__ = "1.0.12";
Vergessen Sie jedoch nicht, das zu erstellen MANIFEST.in
, um die version.h
Datei einzuschließen:
include README.md
include LICENSE.txt
recursive-include source *.h
Und es ist in die Hauptanwendung integriert mit:
#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__ ) );
...
}
Verweise:
Bereitstellen der Konvention zur Paket- und Dateinamenkonvention für Indexpakete:
Beispiel für die Konvertierung der dynamischen Pip-Version:
Sieg:
Mac:
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
Suchen Sie das Beispiel setup.py ruft die dynamische Pip-Version auf, die mit git commit übereinstimmt
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Ich verwende eine Umgebungsvariable wie unten
VERSION = 0.0.0 python setup.py sdist bdist_wheel
In setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Zur Konsistenzprüfung mit der Packer-Version verwende ich das folgende Skript.
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