402 Stimmen

Standardmethode zum Einbetten von Versionen in Python-Pakete?

Gibt es einen Standardweg zu assoziieren Version String mit einem Python-Paket in einer solchen Weise, dass ich das folgende tun könnte?

import foo
print(foo.version)

Ich könnte mir vorstellen, dass es eine Möglichkeit gibt, diese Daten ohne zusätzliche Hardcodierung abzurufen, da Minor/Hauptstrings in setup.py bereits. Eine alternative Lösung, die ich gefunden habe, war, dass ich import __version__ in meinem foo/__init__.py und haben dann __version__.py erzeugt durch setup.py .

203voto

oefe Punkte 18208

Das ist zwar keine direkte Antwort auf Ihre Frage, aber Sie sollten in Erwägung ziehen, dem Ganzen einen Namen zu geben __version__ , nicht version .

Dies ist fast schon ein Quasi-Standard. Viele Module in der Standardbibliothek verwenden __version__ und dies wird auch verwendet in Lose von Modulen von Drittanbietern, es ist also der Quasi-Standard.

Normalerweise, __version__ ist eine Zeichenkette, aber manchmal auch ein Float oder Tupel.

Edit: wie von S.Lott erwähnt (Danke!), PEP 8 sagt es ausdrücklich:

Modul-Ebene Dunder-Namen

"Dunders" auf Modulebene (d. h. Namen mit zwei führenden und zwei nachgestellten Unterstrichen) wie zum Beispiel __all__ , __author__ , __version__ , sollte nach dem Modul-Docstring, aber vor allen import Anweisungen stehen, außer bei __future__ Einfuhren.

Sie sollten auch darauf achten, dass die Versionsnummer dem Format entspricht, das in PEP 440 ( PEP 386 einer früheren Version dieser Norm).

171voto

Zooko Punkte 2172

Ich verwende eine einzelne _version.py Datei als den "einzigen kanonischen Ort" für die Speicherung von Versionsinformationen:

  1. Sie bietet eine __version__ Attribut.

  2. Sie liefert die Standardversion der Metadaten. Daher wird sie erkannt von pkg_resources oder andere Werkzeuge, die die Metadaten des Pakets auswerten (EGG-INFO und/oder PKG-INFO, PEP 0345).

  3. Es importiert Ihr Paket (oder irgendetwas anderes) nicht, wenn es Ihr Paket erstellt, was in manchen Situationen Probleme verursachen kann. (Siehe die Kommentare unten über die Probleme, die dies verursachen kann).

  4. Es gibt nur eine Stelle, an der die Versionsnummer notiert wird, so dass es nur eine Stelle gibt, an der sie geändert werden muss, wenn sich die Versionsnummer ändert, und die Wahrscheinlichkeit inkonsistenter Versionen ist geringer.

So funktioniert es: der "einzige kanonische Ort", um die Versionsnummer zu speichern, ist eine .py-Datei mit dem Namen "_version.py", die sich in Ihrem Python-Paket befindet, zum Beispiel in myniftyapp/_version.py . Diese Datei ist ein Python-Modul, aber Ihre setup.py importiert es nicht! (Das würde Feature 3 vereiteln.) Stattdessen weiß Ihr setup.py, dass der Inhalt dieser Datei sehr einfach ist, etwa so:

__version__ = "3.6.5"

Und so öffnet Ihre setup.py die Datei und analysiert sie, 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 übergibt Ihre setup.py diese Zeichenkette als Wert des Arguments "version" an setup() und erfüllt damit das Merkmal 2.

Um Merkmal 1 zu erfüllen, können Sie Ihr Paket (zur Laufzeit, nicht zur Setup-Zeit!) die _version-Datei von myniftyapp/__init__.py wie diese:

from _version import __version__

Hier ist ein Beispiel für diese Technik die ich schon seit Jahren benutze.

Der Code in diesem Beispiel ist etwas komplizierter, aber das vereinfachte Beispiel, das ich in diesen Kommentar geschrieben habe, sollte eine vollständige Implementierung sein.

Hier ist Beispielcode zum Importieren der Version .

Wenn Sie etwas an diesem Ansatz auszusetzen haben, lassen Sie es mich bitte wissen.

141voto

sorin Punkte 147675

Umgeschrieben 2017-05

Nach mehr als 13 Jahren, in denen ich Python-Code geschrieben und verschiedene Pakete verwaltet habe, bin ich zu dem Schluss gekommen, dass DIY vielleicht nicht der beste Ansatz ist.

Ich habe mit der pbr Paket für den Umgang mit der Versionierung in meinen Paketen. Wenn Sie Git als SCM verwenden, wird sich dies wie von Zauberhand in Ihren Arbeitsablauf einfügen und Ihnen wochenlange Arbeit ersparen (Sie werden überrascht sein, wie komplex das Thema sein kann).

Von heute an, pbr hat 12 Mio. monatliche Downloads Und um diese Stufe zu erreichen, bedurfte es keiner schmutzigen Tricks. Es ging nur um eine Sache - die Lösung eines allgemeinen Verpackungsproblems auf sehr einfache Weise.

pbr kann einen größeren Teil der Paketpflege übernehmen und ist nicht auf die Versionierung beschränkt, aber es zwingt Sie nicht dazu, alle Vorteile zu übernehmen.

Damit Sie sich ein Bild davon machen können, wie es aussieht, wenn Sie pbr in einem Commit übernehmen, sehen Sie sich das an Umstellung der Verpackung auf pbr

Wahrscheinlich würden Sie feststellen, dass die Version überhaupt nicht im Repository gespeichert ist. Das Züchterrecht erkennt sie anhand von Git-Zweigen und Tags.

Sie brauchen sich keine Gedanken darüber zu machen, was passiert, wenn Sie kein Git-Repository haben, da pbr die Version "kompiliert" und zwischenspeichert, wenn Sie die Anwendungen verpacken oder installieren, so dass es keine Laufzeitabhängigkeit von Git gibt.

Alte Lösung

Hier ist die beste Lösung, die ich bis jetzt gesehen habe, und sie 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, der besser zu sein scheint, lassen Sie es mich wissen.

33voto

Oddthinking Punkte 22694

Gemäß der aufgeschobenen PEP 396 (Nummern der Modulversionen) gibt es einen Vorschlag, wie dies geschehen kann. Darin wird ein (zugegebenermaßen optionaler) Standard beschrieben, dem Module folgen sollen. Hier ist ein Auszug:

3) Wenn ein Modul (oder Paket) eine Versionsnummer enthält, SOLLTE die Version in der __version__ Attribut.

4 Bei Modulen, die sich innerhalb eines Namespace-Pakets befinden, SOLLTE das Modul die __version__ Attribut. Das Namespace-Paket selbst SOLLTE NICHT sein eigenes __version__ Attribut.

5 Die __version__ Der Wert des Attributs SOLLTE eine Zeichenkette sein.

31voto

JAB Punkte 19937

Auch wenn dies wahrscheinlich viel zu spät ist, gibt es eine etwas einfachere Alternative zur vorherigen Antwort:

__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)

(Und es wäre ziemlich einfach, automatisch inkrementierende Teile von Versionsnummern in einen String zu konvertieren, indem man str() .)

Soweit ich weiß, neigen die Leute natürlich dazu, so etwas wie die oben erwähnte Version zu verwenden, wenn sie __version_info__ und als solches als Tupel von Ints speichern; ich sehe jedoch nicht ganz den Sinn darin, da ich bezweifle, dass es Situationen gibt, in denen man mathematische Operationen wie Addition und Subtraktion auf Teilen von Versionsnummern zu irgendeinem Zweck außer Neugier oder automatischer Inkrementierung durchführen würde (und selbst dann, int() y str() kann relativ einfach verwendet werden). (Andererseits besteht die Möglichkeit, dass der Code eines anderen Benutzers ein numerisches Tupel anstelle eines String-Tupels erwartet und daher fehlschlägt).

Dies ist natürlich nur meine eigene Meinung, und ich würde mich freuen, wenn andere sich zur Verwendung eines numerischen Tupels äußern würden.


Wie Shezi mich daran erinnerte, haben (lexikalische) Vergleiche von Zahlenketten nicht notwendigerweise das gleiche Ergebnis wie direkte numerische Vergleiche; führende Nullen wären erforderlich, um dies zu gewährleisten. Letztendlich wird also das Speichern von __version_info__ (oder wie auch immer es genannt werden würde) als Tupel von Ganzzahlwerten würde einen effizienteren Versionsvergleich ermöglichen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X