Standardweg Version in Python-Paket einbetten?
Frage
Gibt es eine Standardmethode Version String mit einem Python-Paket in einer solchen Art und Weise zu verbinden, dass ich folgendes tun könnte?
import foo
print foo.version
Ich könnte mir vorstellen, es gibt eine Möglichkeit, dass die Daten ohne zusätzliche Hardcoding abzurufen, da Moll / Dur-Strings bereits in setup.py
angegeben sind. Alternative Lösung, die ich war gefunden import __version__
in meinem foo/__init__.py
zu haben und dann __version__.py
von setup.py
erzeugt hat.
Lösung
Nicht direkt eine Antwort auf Ihre Frage, aber man sollte es für __version__
zu nennen, nicht version
.
Das ist fast ein Quasi-Standard. Viele Module in der Standardbibliothek Verwendung __version__
, und dies auch in Los von 3rd-Party-Module, so ist es der quasi-Standard.
Normalerweise ist __version__
eine Zeichenfolge, aber manchmal auch, es ist ein Schwimmer oder Tupels.
Edit: wie erwähnt durch S.Lott (Danke), PEP 8 sagt es ausdrücklich:
Version Buchhaltung
Wenn Sie haben, Subversion, CVS oder RCS crud in der Quelldatei, tun Sie es wie folgt.
__version__ = "$Revision: 63990 $" # $Source$
Diese Zeilen sollen nach der Modul docstring aufgenommen werden, bevor ein anderer Code, durch eine Leerzeile oberhalb und unterhalb getrennt ist.
Sie sollten auch sicherstellen, dass die Versionsnummer auf das Format in PEP 440 ( PEP 386 eine frühere Version dieses Standards).
Andere Tipps
Ich verwende eine einzige _version.py
Datei als „einmal cannonical Ort“ zum Speichern von Versionsinformationen:
-
Es bietet ein
__version__
Attribut. -
Es bietet die Standard-Metadaten-Version. Daher wird es durch
pkg_resources
oder andere Werkzeuge erfasst werden, dass das Paket-Metadaten (EGG-INFO und / oder PKG-INFO, PEP 0345). parsen
-
Es importiert nicht Ihr Paket (oder etwas anderes), wenn Ihr Paket bauen, die Probleme in einigen Situationen führen kann. (Siehe die folgenden Erläuterungen darüber, welche Probleme das verursachen kann.)
-
Es gibt nur einen Ort, der die Versionsnummer wird aufgeschrieben, so gibt es nur einen Ort, um es zu ändern, wenn die Versionsnummer ändert sich, und es gibt weniger Chancen inkonsistenter Versionen.
ist, wie es funktioniert: der „ein kanonischer Ort“, um die Versionsnummer zu speichern, wird .py Datei mit dem Namen „_version.py“, die in Ihrem Python-Paket ist zum Beispiel in myniftyapp/_version.py
. Diese Datei ist ein Python-Modul, aber Ihr setup.py nicht importieren Sie es nicht! (Das würde besiegen Feature 3.) Statt Ihrer setup.py weiß, dass der Inhalt dieser Datei ist sehr einfach, so etwas wie:
__version__ = "3.6.5"
Und damit Ihre setup.py öffnet die Datei und analysiert es mit Code wie:
import re
VERSIONFILE="myniftyapp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
verstr = mo.group(1)
else:
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
Dann wird Ihr setup.py gibt diese Zeichenfolge als Wert des „Version“ Arguments setup()
, so befriedigend Merkmal 2.
Merkmal 1 zu erfüllen, Sie Ihr Paket haben kann, importieren Sie die _version Datei von myniftyapp/__init__.py
wie folgt aus (zur Laufzeit, überhaupt nicht Rüstzeit!):
from _version import __version__
Hier ist ein Beispiel für diese Technik dass ich habe seit Jahren verwendet wird.
Der Code in diesem Beispiel ist ein wenig komplizierter, aber das vereinfachte Beispiel, dass ich in diesen Kommentar schrieb sollte eine vollständige Implementierung sein.
Hier ist Beispielcode des Imports die Version .
Wenn Sie etwas falsch mit diesem Ansatz zu sehen, lassen Sie es mich wissen.
Rewritten 2017-05
Nach mehr als 10 Jahren von Python-Code zu schreiben und verschiedene Pakete zu verwalten ich zu dem Schluss gekommen, dass DIY ist vielleicht nicht der beste Ansatz.
Ich begann pbr
Paket mit in meinen Paketen mit Versionsverwaltung handelt. Wenn Sie git als SCM verwenden, wird dies wie Magie in Ihren Arbeitsablauf passen, Ihre Arbeitswochen Spar (Sie werden überrascht sein, wie komplex das Problem sein kann).
Ab heute pbr ist auf Platz # 11 am häufigsten verwendete Python-Paket und erreichen dieses Niveau didn‘ t umfassen alle schmutzigen Tricks: war nur eine:. ein gemeinsames Verpackungsproblem auf sehr einfache Art und Weise zur Festsetzung
pbr
kann mehr von dem Paketverwaltungsaufwand tun, ist nicht auf der Versionierung beschränkt, sondern es macht Dich nicht alle seine Vorteile erlassen erzwingen.
So Ihnen eine Idee zu geben, wie es pbr annehmen sieht in einem einen Blick swiching Verpackung pbr
Wahrscheinlich würden Sie beobachtet, dass die Version nicht in dem Repository gespeichert ist. PBR erkennt es aus Git Zweige und Tags.
Keine Notwendigkeit, sich Sorgen zu machen, was passiert, wenn Sie nicht über ein Git Repository, weil pbr bedeutet „kompiliert“ und die Version Cache, wenn Sie die Anwendungen verpacken oder installieren, so dass es keine Laufzeitabhängigkeit git.
Alte Lösung
Hier ist die beste Lösung, die ich bisher gesehen habe und es erklärt auch, warum:
Innerhalb yourpackage/version.py
:
# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.12'
Innerhalb yourpackage/__init__.py
:
from .version import __version__
Innerhalb setup.py
:
exec(open('yourpackage/version.py').read())
setup(
...
version=__version__,
...
Wenn Sie einen anderen Ansatz kennen, besser zu sein scheint, lassen Sie es mich wissen.
Per dem latenten PEP 396 (Modul Versionsnummern) , gibt es eine vorgeschlagene Art und Weise, dies zu tun. Es beschreibt, mit Begründung, ein (zugegebenermaßen optional) Standard für die Module zu folgen. Hier ist ein Ausschnitt:
3) Wenn ein Modul (oder Paket) eine Versionsnummer enthält, sollte die Version im
__version__
Attribute zur Verfügung.4) Für Module, die in einem Namespace-Paket leben, sollte das Modul das
__version__
Attribut enthalten. Das Namespace-Paket selbst sollte nicht sein eigenes__version__
Attribut enthalten.5) Der Wert des
__version__
Attribut sollte ein String sein.
Obwohl dies ist wahrscheinlich viel zu spät, gibt es eine etwas einfachere Alternative zur vorherige Antwort:
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(Und es wäre recht einfach mit automatischer Erhöhung Teile der Versionsnummern auf einen String str()
verwenden zu konvertieren.)
Natürlich, von dem, was ich gesehen habe, neigen die Menschen so etwas wie die zuvor genannten Version zu verwenden, wenn __version_info__
verwenden, und als solcher Speicher als ein Tupel von Ints; aber ich verstehe nicht ganz den Punkt sieht, dass man, wie ich bezweifle, gibt es Situationen, in denen Sie mathematische Operationen wie Addition und Subtraktion auf Teile der Versionsnummern für jeden Zweck neben Neugier oder Auto-Inkrementierung (und selbst dann durchführen würden, int()
und str()
kann ziemlich leicht verwendet werden). (Auf der anderen Seite gibt es die Möglichkeit, Code von jemand anderem eine numerische Tupel eher als ein Stringtupel erwartet und somit versagt.)
Das ist, natürlich, meine eigene Meinung, und ich würde gerne wie andere -Eingang auf einen numerischen Tupel verwendet wird.
Als Shezi mich daran erinnert, (lexikalische) Vergleiche der Zahlenketten müssen nicht unbedingt das gleiche Ergebnis wie direkte numerische Vergleiche; führende Nullen würden dafür zu schaffen, erforderlich. Also am Ende, __version_info__
Speichern (oder was auch immer, es würde genannt werden) als Tupel von ganzzahligen Werten würde für eine effizientere Version Vergleiche ermöglichen.
Ich verwende eine JSON-Datei im Paket Richt. Das paßt Zooko Anforderungen.
Innerhalb pkg_dir/pkg_info.json
:
{"version": "0.1.0"}
Innerhalb setup.py
:
from distutils.core import setup
import json
with open('pkg_dir/pkg_info.json') as fp:
_info = json.load(fp)
setup(
version=_info['version'],
...
)
Innerhalb pkg_dir/__init__.py
:
import json
from os.path import dirname
with open(dirname(__file__) + '/pkg_info.json') as fp:
_info = json.load(fp)
__version__ = _info['version']
Ich habe auch andere Informationen in pkg_info.json
, wie Autor. ich
wie JSON zu verwenden, weil ich Management von Metadaten automatisieren kann.
Viele dieser Lösungen ignorieren hier git
Version Tags, die immer noch bedeutet, dass Sie Version an mehreren Stellen zu verfolgen haben (sehr schlecht). Ich näherte ich dies mit den folgenden Zielen:
- leitet alle Python-Version Referenzen von einem Tag in der
git
Repo - Automatisieren
git tag
/push
undsetup.py upload
Schritte mit einem einzigen Befehl, die keine Eingaben nehmen.
Wie es funktioniert:
-
Von einem
make release
Befehl wird die letzte markierte Version im git Repo gefunden und erhöht. Der Tag zurückgeschoben zuorigin
. -
Die
Makefile
speichert die Version insrc/_version.py
, wo sie vonsetup.py
gelesen wird und auch in der Pressemitteilung. Sie_version.py
in der Quellcodeverwaltung nicht überprüfen! -
setup.py
Befehl liest die neue Version Zeichenfolge auspackage.__version__
.
Details:
Makefile
# remove optional 'v' and trailing hash "v1.0-N-HASH" -> "v1.0-N"
git_describe_ver = $(shell git describe --tags | sed -E -e 's/^v//' -e 's/(.*)-.*/\1/')
git_tag_ver = $(shell git describe --abbrev=0)
next_patch_ver = $(shell python versionbump.py --patch $(call git_tag_ver))
next_minor_ver = $(shell python versionbump.py --minor $(call git_tag_ver))
next_major_ver = $(shell python versionbump.py --major $(call git_tag_ver))
.PHONY: ${MODULE}/_version.py
${MODULE}/_version.py:
echo '__version__ = "$(call git_describe_ver)"' > $@
.PHONY: release
release: test lint mypy
git tag -a $(call next_patch_ver)
$(MAKE) ${MODULE}/_version.py
python setup.py check sdist upload # (legacy "upload" method)
# twine upload dist/* (preferred method)
git push origin master --tags
Das release
Ziel immer erhöht die dritte Version digit, aber Sie können die next_minor_ver
oder next_major_ver
verwenden, um die anderen Ziffern zu erhöhen. Die Befehle setzen auf dem versionbump.py
-Skript, das in die Wurzel des Repo geprüft
versionbump.py
"""An auto-increment tool for version strings."""
import sys
import unittest
import click
from click.testing import CliRunner # type: ignore
__version__ = '0.1'
MIN_DIGITS = 2
MAX_DIGITS = 3
@click.command()
@click.argument('version')
@click.option('--major', 'bump_idx', flag_value=0, help='Increment major number.')
@click.option('--minor', 'bump_idx', flag_value=1, help='Increment minor number.')
@click.option('--patch', 'bump_idx', flag_value=2, default=True, help='Increment patch number.')
def cli(version: str, bump_idx: int) -> None:
"""Bumps a MAJOR.MINOR.PATCH version string at the specified index location or 'patch' digit. An
optional 'v' prefix is allowed and will be included in the output if found."""
prefix = version[0] if version[0].isalpha() else ''
digits = version.lower().lstrip('v').split('.')
if len(digits) > MAX_DIGITS:
click.secho('ERROR: Too many digits', fg='red', err=True)
sys.exit(1)
digits = (digits + ['0'] * MAX_DIGITS)[:MAX_DIGITS] # Extend total digits to max.
digits[bump_idx] = str(int(digits[bump_idx]) + 1) # Increment the desired digit.
# Zero rightmost digits after bump position.
for i in range(bump_idx + 1, MAX_DIGITS):
digits[i] = '0'
digits = digits[:max(MIN_DIGITS, bump_idx + 1)] # Trim rightmost digits.
click.echo(prefix + '.'.join(digits), nl=False)
if __name__ == '__main__':
cli() # pylint: disable=no-value-for-parameter
Dies ist das Heben schwerer Lasten wie zu verarbeiten und die Versionsnummer von git
zu erhöhen.
__ init __. Py
Die my_module/_version.py
Datei wird in my_module/__init__.py
importiert. Setzen Sie eventuell vorhandene statische installieren Config hier, dass Sie mit Ihrem Modul verteilt werden sollen.
from ._version import __version__
__author__ = ''
__email__ = ''
setup.py
Der letzte Schritt ist die Versionsinfo von einem my_module
Modul zu lesen.
from setuptools import setup, find_packages
pkg_vars = {}
with open("{MODULE}/_version.py") as fp:
exec(fp.read(), pkg_vars)
setup(
version=pkg_vars['__version__'],
...
...
)
Natürlich, für all dies funktioniert, müssen Sie mindestens eine Version Tag in Ihrem Repo haben müssen würden starten.
git tag -a v0.0.1
Es scheint nicht ein Standard-Weg, um eine Versionszeichenfolge in einem Python-Paket einbetten. Die meisten Pakete Ich habe verwenden eine Variante Ihrer Lösung gesehen, das heißt Eitner
-
einbetten Version in
setup.py
und habensetup.py
ein Modul erzeugen (zversion.py
) nur Versionsinformationen enthalten, die von Ihrem Paket importiert wird, oder -
Die Rückseite: legen Sie die Versionsinformationen in Ihrem Paket selbst und Import , die die Version in
setup.py
einstellen
Erwähnenswert ist auch, dass ebenso wie __version__
ein halb std zu sein. so in Python ist __version_info__
die ein Tupel, in den einfachen Fällen einfach etwas tun können, wie:
__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])
... und Sie können den __version__
String aus einer Datei erhalten, oder was auch immer.
Ich sah auch einen anderen Stil:
>>> django.VERSION
(1, 1, 0, 'final', 0)
Pfeil behandelt sie auf eine interessante Weise.
In arrow/__init__.py
:
__version__ = '0.8.0'
VERSION = __version__
In setup.py
:
def grep(attrname):
pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
strval, = re.findall(pattern, file_text)
return strval
setup(
name='arrow',
version=grep('__version__'),
# [...]
)
Für das, was es wert ist, wenn Sie NumPy distutils verwenden, numpy.distutils.misc_util.Configuration
hat eine make_svn_version_py()
Verfahren, das die Revisionsnummer innerhalb package.__svn_version__
in der variablen version
einbettet.
- Verwenden Sie eine
version.py
Datei nur mit__version__ = <VERSION>
param in der Datei. In dersetup.py
Datei des__version__
param importieren und setzt es Wert in dersetup.py
-Datei wie folgt:version=__version__
- Eine andere Möglichkeit ist nur eine
setup.py
Datei mitversion=<CURRENT_VERSION>
zu verwenden - das CURRENT_VERSION hartcodiert ist .
Da wir nicht wollen, manuell die Version in der Datei, die wir jedes Mal ändern, erstellen Sie einen neuen Tag (bereit, eine Version neue Paket veröffentlichen), können wir verwenden die folgende ..
Ich empfehle bumpversion Paket. Ich habe es seit Jahren mit einer Version stoßen.
Start von version=<VERSION>
auf Ihre setup.py
Datei hinzufügen, wenn Sie es nicht bereits haben.
Sie sollten ein kurzes Skript wie folgt verwenden, jedesmal wenn Sie stoßen eine Version:
bumpversion (patch|minor|major) - choose only one option
git push
git push --tags
Dann fügen Sie eine Datei pro repo genannt: .bumpversion.cfg
:
[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]
Hinweis:
- Sie können
__version__
Parameter unterversion.py
-Datei verwenden, wie es in anderen Beiträgen vorgeschlagen wurde und aktualisieren Sie die bumpversion-Datei wie folgt:[bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
- Sie muss
git commit
oder alles in Ihrem Repogit reset
, sonst werden Sie einen schmutzigen Repo-Fehler. - Stellen Sie sicher, dass Ihre virtuelle Umgebung das Paket von bumpversion enthält, ohne sie wird es nicht funktionieren.
Wenn Sie CVS (oder RCS) und wollen eine schnelle Lösung, können Sie:
__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])
(Natürlich wird die Revisionsnummer für Sie durch CVS ersetzt werden.)
Dies gibt Ihnen eine druckfreundliche Version und eine Version Info, die Sie überprüfen können, ob das Modul, das Sie importieren, zumindest die erwartete Version:
import my_module
assert my_module.__version_info__ >= (1, 1)