Standardmethode zum Einbetten der Version in das Python-Paket?

263

Gibt es eine Standardmethode, um Versionszeichenfolgen einem Python-Paket so zuzuordnen, dass ich Folgendes tun kann?

import foo
print foo.version

Ich würde mir vorstellen, dass es eine Möglichkeit gibt, diese Daten ohne zusätzliche Hardcodierung abzurufen, da bereits kleinere / größere Zeichenfolgen angegeben sind setup.py. Alternative Lösung, die ich gefunden habe, war import __version__in meinem zu haben foo/__init__.pyund dann von __version__.pygeneriert zu haben setup.py.

Dimitri Tcaciuc
quelle
7
FYI, gibt es eine sehr gute Übersicht bei: packaging.python.org/en/latest/...
ionelmc
1
Die Version eines installierten Pakets kann mit setuptools aus Metadaten abgerufen werden. In vielen Fällen reicht es also aus, nur die Version einzutragensetup.py . Siehe diese Frage .
Saaj
2
Zu Ihrer Information, es gibt grundsätzlich 5 gängige Muster , um die einzige Quelle der Wahrheit (sowohl zur Einrichtung als auch zur Laufzeit) für die Versionsnummer beizubehalten.
KF Lin
In der Dokumentation von @ionelmc Python sind 7 verschiedene Optionen für Single-Souring aufgeführt . Widerspricht das nicht dem Konzept einer " einzigen Quelle der Wahrheit "?
Stevoisiak
@StevenVascellaro nicht sicher, was Sie fragen. Dort sind so viele Möglichkeiten aufgeführt, weil der Verpackungsleitfaden keine Meinung abgeben möchte.
Ionelmc

Antworten:

135

Keine direkte Antwort auf Ihre Frage, aber Sie sollten in Betracht ziehen, sie zu benennen __version__, nicht version.

Dies ist fast ein Quasi-Standard. Viele Module in der Standardbibliothek werden verwendet __version__, und dies wird auch in Losen verwendet Modulen von Drittanbietern verwendet, sodass dies der Quasi-Standard ist.

Normalerweise __version__ist es eine Zeichenfolge, aber manchmal ist es auch ein Float oder Tupel.

Edit: Wie von S.Lott erwähnt (Danke!), Sagt PEP 8 es explizit:

Modul Level Dunder Namen

Modulebene „dunders“ (dh Namen mit zwei führenden und zwei hinteren Unterstrichen) wie __all__, __author__, __version__usw. sollte nach dem Modul docstring gestellt werden , sondern vor all Import - Anweisungen mit Ausnahme von __future__Importen.

Sie sollten auch sicherstellen, dass die Versionsnummer dem in PEP 440 beschriebenen Format entspricht ( PEP 386 eine frühere Version dieses Standards).

oefe
quelle
9
Es sollte eine Zeichenfolge sein und eine version_info für die Tupelversion haben .
James Antill
James: Warum __version_info__speziell? (Was Ihr eigenes doppeltes Unterstrichwort "erfindet".) [Als James kommentierte, taten Unterstriche nichts in Kommentaren, jetzt zeigen sie Betonung an, also schrieb James auch wirklich __version_info__. --- ed.]
Unter packages.python.org/distribute/… können Sie etwas darüber sehen, was die Version sagen soll. Auf dieser Seite geht es um das Verteilen, aber die Bedeutung der Versionsnummer wird zum De-facto-Standard.
Sienkiew
2
Recht. Scheint, dass diese PEPs sich widersprechen. Nun, PEP 8 sagt "wenn" und "grob", daher unterstützt es die Verwendung der VCS-Schlüsselworterweiterung nicht wirklich. Wenn Sie jemals zu einem anderen VCS wechseln, gehen die Revisionsinformationen verloren. Daher würde ich empfehlen, eine PEP 386/440-kompatible Versionsinformation zu verwenden, die in eine einzelne Quelldatei eingebettet ist, zumindest für größere Projekte.
Oefe
2
Wo würden Sie diese setzen Version . Da dies die akzeptierte Version ist, würde ich diese zusätzlichen Informationen gerne hier sehen.
Darkgaze
119

Ich verwende eine einzelne _version.pyDatei als "einmal kanonischen Ort", um Versionsinformationen zu speichern:

  1. Es bietet ein __version__Attribut.

  2. Es bietet die Standard-Metadatenversion. Daher wird es von pkg_resourcesoder anderen Tools erkannt , die die Paketmetadaten analysieren (EGG-INFO und / oder PKG-INFO, PEP 0345).

  3. Beim Erstellen Ihres Pakets wird Ihr Paket (oder etwas anderes) nicht importiert, was in einigen Situationen zu Problemen führen kann. (In den Kommentaren unten erfahren Sie, welche Probleme dies verursachen kann.)

  4. Es gibt nur einen Ort, an dem die Versionsnummer notiert wird. Es gibt also nur einen Ort, an dem sie geändert werden kann, wenn sich die Versionsnummer ändert, und es besteht eine geringere Wahrscheinlichkeit für inkonsistente Versionen.

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

__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 Zeichenfolge als Wert des Arguments "version" an setup()und erfüllt damit Merkmal 2.

Um Funktion 1 zu erfüllen, können Sie Ihr Paket (zur Laufzeit, nicht zur Setup-Zeit!) Die _Versionsdatei myniftyapp/__init__.pywie folgt importieren lassen :

from _version import __version__

Hier ist ein Beispiel für diese Technik , die ich seit Jahren verwende.

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 ein Beispielcode zum Importieren der Version .

Wenn Sie etwas falsch mit diesem Ansatz sehen, lassen Sie es mich bitte wissen.

Zooko
quelle
8
Könnten Sie bitte die Probleme beschreiben, die # 3 motivieren? Glyph sagte, es habe etwas mit "setuptools tut gerne so, als ob Ihr Code nicht irgendwo auf dem System ist, wenn Ihre setup.py ausgeführt wird" zu tun, aber die Details würden helfen, mich und andere zu überzeugen.
Ivan Kozik
2
@Iva Nun, in welcher Reihenfolge sollte das Tool dies tun? Es kann (im heutigen setuptools / pip / virtualenv-System) nicht einmal wissen, was die Deps sind, bis es Ihre bewertet setup.py. Wenn es versucht, zuerst die volle Tiefe zu machen und alle Deps zu machen, bevor es dieses macht, würde es stecken bleiben, wenn es kreisförmige Deps gäbe. Wenn es jedoch versucht, dieses Paket vor der Installation der Abhängigkeiten zu erstellen setup.py, kann es beim Importieren Ihres Pakets nicht unbedingt seine Deps oder die richtigen Versionen seiner Deps importieren.
Zooko
3
Könnten Sie die Datei "version.py" aus "setup.py" schreiben, anstatt sie zu analysieren? Das scheint einfacher.
Jonathan Hartley
3
Jonathan Hartley: Ich bin damit einverstanden, dass es für Ihre "setup.py" etwas einfacher wäre, die "version.py" -Datei zu schreiben, anstatt sie zu analysieren, aber es würde ein Fenster für Inkonsistenzen öffnen, wenn Sie Ihre setup.py bearbeitet haben um die neue Version zu haben, aber setup.py noch nicht ausgeführt zu haben, um die Datei version.py zu aktualisieren. Ein weiterer Grund dafür, dass sich die kanonische Version in einer kleinen separaten Datei befindet, besteht darin, dass andere Tools, z. B. Tools, die Ihren Revisionskontrollstatus lesen, die Versionsdatei problemlos schreiben können.
Zooko
3
Ein ähnlicher Ansatz besteht darin, execfile("myniftyapp/_version.py")in setup.py zu arbeiten, anstatt zu versuchen, den Versionscode manuell zu analysieren. Vorgeschlagen in stackoverflow.com/a/2073599/647002 - eine Diskussion dort kann ebenfalls hilfreich sein.
Medmunds
96

Umgeschrieben 2017-05

Nachdem ich mehr als zehn Jahre Python-Code geschrieben und verschiedene Pakete verwaltet hatte, kam ich zu dem Schluss, dass DIY möglicherweise nicht der beste Ansatz ist.

Ich habe angefangen, pbrpackage für die Versionsverwaltung in meinen Paketen zu verwenden. Wenn Sie git als SCM verwenden, passt dies wie von Zauberhand in Ihren Workflow und spart Ihnen wochenlange Arbeit (Sie werden überrascht sein, wie komplex das Problem sein kann).

Bis heute ist pbr die Nummer 11 der am häufigsten verwendeten Python-Pakete, und das Erreichen dieses Levels beinhaltete keine schmutzigen Tricks: war nur eine: Beheben eines häufigen Verpackungsproblems auf sehr einfache Weise.

pbr kann mehr von der Paketwartungslast übernehmen, ist nicht auf die Versionierung beschränkt, zwingt Sie jedoch nicht dazu, alle Vorteile zu nutzen.

Um Ihnen eine Vorstellung davon zu geben, wie es aussieht, pbr in einem Commit zu übernehmen, werfen Sie einen Blick auf pbr

Wahrscheinlich haben Sie festgestellt, dass die Version überhaupt nicht im Repository gespeichert ist. PBR erkennt es an Git-Zweigen und -Tags.

Sie müssen sich keine Gedanken darüber machen, was passiert, wenn Sie kein Git-Repository haben, da pbr die Version beim Packen oder Installieren der Anwendungen "kompiliert" und zwischenspeichert, sodass keine Laufzeitabhängigkeit von Git besteht.

Alte Lösung

Hier ist die beste Lösung, die ich bisher gesehen habe, und sie erklärt auch, warum:

Innen 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'

Innen yourpackage/__init__.py:

from .version import __version__

Innen 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.

Sorin
quelle
12
Äh, nein. execfile () ist in Python 3 nicht vorhanden, daher ist es besser, exec (open (). read ()) zu verwenden.
Christophe Vu-Brugier
4
warum nicht auch from .version import __version__in setup.py?
Aprillion
4
@Aprillion Da das Paket nicht geladen wird, wenn setup.pyes ausgeführt wird - versuchen Sie es, Sie erhalten eine Fehlermeldung (oder zumindest habe ich :-))
Darthbith
3
Die Verknüpfung zu pbr führt zu einem schlechten Gateway.
MERose
4
pbr ist zweifellos ein großartiges Werkzeug, aber Sie haben die Frage nicht beantwortet. Wie können Sie die aktuelle Version zugreifen oder das installierte Paket über BPR .
nad2000
29

Gemäß dem zurückgestellten PEP 396 (Modulversionsnummern) wird ein Weg vorgeschlagen, dies zu tun. Es beschreibt mit Begründung einen (zugegebenermaßen optionalen) Standard für Module, die folgen sollen. Hier ist ein Ausschnitt:

3) Wenn ein Modul (oder Paket) eine Versionsnummer enthält, MUSS die Version im __version__Attribut verfügbar sein .

4) Für Module, die sich in einem Namespace-Paket befinden, sollte das Modul das __version__Attribut enthalten. Das Namespace-Paket selbst sollte kein eigenes __version__Attribut enthalten.

5) Der __version__Wert des Attributs sollte eine Zeichenfolge sein.

Seltsames Denken
quelle
13
Dieses PEP wird nicht akzeptiert / standardisiert, sondern zurückgestellt (aufgrund mangelnden Interesses). Daher ist es etwas irreführend zu behaupten, dass " es einen Standardweg gibt", der von ihm angegeben wird.
Weber
@ Weaver: Oh mein! Ich habe etwas Neues gelernt. Ich wusste nicht, dass ich das überprüfen musste.
Oddthinking
4
Bearbeitet, um zu beachten, dass es kein Standard ist. Jetzt schäme ich mich, weil ich bei Projekten Feature-Anfragen gestellt habe, in denen sie gebeten wurden, diesem "Standard" zu folgen.
Oddthinking
1
Vielleicht sollten Sie die Standardisierungsarbeit an diesem PEP übernehmen, da Sie interessiert zu sein scheinen :)
Weber
Dies würde für die Versionierung eines einzelnen Moduls funktionieren, aber ich bin nicht sicher, ob dies für die Versionierung eines vollständigen Projekts gelten würde.
Stevoisiak
21

Obwohl 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 mit in eine Zeichenfolge zu konvertieren str() .)

Nach allem, was ich gesehen habe, neigen die Leute dazu, bei der Verwendung so etwas wie die zuvor erwähnte Version zu verwenden __version_info__und es als solches als Tupel von Ints zu speichern. Ich verstehe dies jedoch nicht ganz, da ich bezweifle, dass es Situationen gibt, in denen Sie mathematische Operationen wie Addition und Subtraktion von Teilen von Versionsnummern für einen anderen Zweck als Neugier oder automatische Inkrementierung ausführen würden (und selbst dann) int()und str()kann ziemlich einfach verwendet werden). (Andererseits besteht die Möglichkeit, dass der Code eines anderen ein numerisches Tupel anstelle eines Zeichenfolgentupels erwartet und somit fehlschlägt.)

Dies ist natürlich meine eigene Ansicht, und ich würde gerne die Beiträge anderer zur Verwendung eines numerischen Tupels begrüßen.


Wie Shezi mich erinnerte, haben (lexikalische) Vergleiche von Zahlenfolgen nicht unbedingt das gleiche Ergebnis wie direkte numerische Vergleiche. Dazu wären führende Nullen erforderlich. Am Ende __version_info__würde das Speichern (oder wie auch immer es genannt werden würde) als Tupel ganzzahliger Werte effizientere Versionsvergleiche ermöglichen.

JAB
quelle
12
schön (+1), aber würden Sie nicht Zahlen anstelle von Zeichenfolgen bevorzugen? zB__version_info__ = (1,2,3)
Orip
3
Der Vergleich von Zeichenfolgen kann gefährlich werden, wenn die Versionsnummern 9 überschreiten, z. B. '10' <'2'.
D Coetzee
13
Ich mache das auch, aber leicht angepasst, um Ints zu adressieren. __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
Rh0dium
2
Problem mit __version__ = '.'.join(__version_info__)ist, dass __version_info__ = ('1', '2', 'beta')wird 1.2.beta, nicht 1.2betaoder1.2 beta
Nagisa
4
Ich denke, das Problem bei diesem Ansatz ist, wo die Codezeilen platziert werden, die die __version__ deklarieren. Wenn sie sich in setup.py befinden, kann Ihr Programm sie nicht aus seinem Paket importieren. Vielleicht ist dies kein Problem für Sie. In diesem Fall ist das in Ordnung. Wenn sie in Ihrem Programm enthalten sind, kann Ihre setup.py sie importieren, sollte dies jedoch nicht, da setup.py während der Installation ausgeführt wird, wenn die Abhängigkeiten Ihres Programms noch nicht installiert sind (setup.py wird verwendet, um die Abhängigkeiten zu bestimmen .) Daher Zookos Antwort: Analysieren Sie den Wert manuell aus einer Produktquelldatei, ohne das Produktpaket zu importieren
Jonathan Hartley
11

Viele dieser Lösungen hier ignorieren gitVersions-Tags, was bedeutet, dass Sie die Version an mehreren Stellen verfolgen müssen (schlecht). Ich ging dies mit folgenden Zielen an:

  • Leiten Sie alle Python-Versionsreferenzen von einem Tag in der ab git Repo ab
  • Automatisieren Sie git tag/ pushund setup.py uploadSchritte mit einem einzigen Befehl, der keine Eingaben akzeptiert.

Wie es funktioniert:

  1. Aus einem make releaseBefehl heraus wird die zuletzt getaggte Version im Git-Repo gefunden und erhöht. Das Tag wird zurückgeschoben origin.

  2. Das Makefilespeichert die Version dort, src/_version.pywo sie gelesen wird setup.pyund auch in der Version enthalten ist. Nicht _version.pyin die Quellcodeverwaltung einchecken !

  3. setup.pyBefehl liest die neue Versionszeichenfolge aus package.__version__.

Einzelheiten:

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 releaseZiel erhöht immer die Ziffer der 3. Version, aber Sie können das next_minor_veroder verwenden, next_major_verum die anderen Ziffern zu erhöhen. Die Befehle basieren auf dem versionbump.pySkript, das im Stammverzeichnis des Repos eingecheckt ist

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 macht das schwere Heben, wie die Versionsnummer von verarbeitet und erhöht wird git.

__init__.py

Die my_module/_version.pyDatei wird in importiert my_module/__init__.py. Fügen Sie hier eine statische Installationskonfiguration ein, die Sie mit Ihrem Modul verteilen möchten.

from ._version import __version__
__author__ = ''
__email__ = ''

setup.py

Der letzte Schritt besteht darin, die Versionsinformationen aus dem my_moduleModul 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__'],
    ...
    ...
)

Damit dies alles funktioniert, muss Ihr Repo mindestens ein Versions-Tag enthalten, um zu starten.

git tag -a v0.0.1
cmcginty
quelle
1
in der Tat - dieser ganze Thread vergisst, dass ein VCS in dieser Diskussion sehr wichtig ist. Nur eine obs: Versionsinkrementierung sollte ein manueller Prozess bleiben, daher wäre der bevorzugte Weg 1. manuelles Erstellen und Verschieben eines Tags 2. Lassen Sie VCS-Tools dieses Tag erkennen und bei Bedarf speichern (wow - diese SO-Bearbeitungsoberfläche lähmt mich wirklich - Ich musste dies ein Dutzend Mal bearbeiten, nur um mit Zeilenumbrüchen fertig zu werden, und es funktioniert immer noch nicht! @ # $% ^ & *)
Axd
Keine Notwendigkeit zu verwenden, versionbump.pywenn wir ein fantastisches Bumpversion- Paket für Python haben.
Oran
@Oran Ich habe mir versionbump angesehen. Die Dokumente sind nicht sehr klar und das Taggen ist nicht sehr gut. In meinen Tests scheint es in Zustände zu geraten, die zum Absturz führen: subprocess.CalledProcessError: Befehl '[' git ',' commit ',' -F ',' / var / folders / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngbo '] 'Rückgabestatus ungleich Null zurückgegeben 1.
cmcginty
1
Warum sollte nicht _version.pymit Versionskontrolle verfolgt werden?
Stevoisiak
1
@StevenVascellaro Dies erschwert den Freigabeprozess. Jetzt müssen Sie sicherstellen, dass Ihre Tags und Commits mit dem Wert in _version.py übereinstimmen. Die Verwendung eines einzelnen Versions-Tags ist IMO sauberer und bedeutet, dass Sie eine Version direkt über die Github-Benutzeroberfläche erstellen können.
cmcginty
10

Ich verwende eine JSON-Datei im Paketverzeichnis. Dies entspricht den Anforderungen von Zooko.

Innen pkg_dir/pkg_info.json:

{"version": "0.1.0"}

Innen 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'],
    ...
    )

Innen 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 pkg_info.jsonwie den Autor eingegeben. Ich verwende gerne JSON, weil ich die Verwaltung von Metadaten automatisieren kann.

Andy Lee
quelle
Können Sie erläutern, wie Sie den json zur Automatisierung der Metadatenverwaltung verwenden können? Vielen Dank!
Ryanjdillon
6

Erwähnenswert ist auch, dass dies __version__nicht nur ein Semi-Standard ist. In Python ist __version_info__das also ein Tupel. In den einfachen Fällen können Sie einfach so etwas tun:

__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])

... und Sie können die __version__Zeichenfolge aus einer Datei oder was auch immer erhalten.

James Antill
quelle
1
Haben Sie Verweise / Links zum Ursprung dieser Nutzung von __version_info__?
Craig McQueen
3
Nun, es ist die gleiche Zuordnung wie sys.version zu sys.version_info. Also: docs.python.org/library/sys.html#sys.version_info
James Antill
7
Es ist einfacher, das Mapping in die andere Richtung ( __version_info__ = (1, 2, 3)und __version__ = '.'.join(map(str, __version_info__))) durchzuführen.
Eric O Lebigot
2
@EOL - __version__ = '.'.join(str(i) for i in __version_info__)- etwas länger, aber pythonischer.
ArtOfWarfare
2
Ich bin mir nicht sicher, ob das, was Sie vorschlagen, eindeutig pythonischer ist, da es eine Dummy-Variable enthält, die nicht wirklich benötigt wird und deren Bedeutung etwas schwer auszudrücken ist ( ikeine Bedeutung hat, version_numetwas lang und mehrdeutig ist…). Ich nehme sogar die Existenz von map()in Python als starken Hinweis darauf, dass es hier verwendet werden sollte, da wir hier den typischen Anwendungsfall von map()(Verwendung mit einer vorhandenen Funktion) tun müssen - ich sehe nicht viele andere vernünftige Verwendungen.
Eric O Lebigot
5

Es scheint keine Standardmethode zum Einbetten einer Versionszeichenfolge in ein Python-Paket zu geben. Die meisten Pakete, die ich gesehen habe, verwenden eine Variante Ihrer Lösung, z. B. eitner

  1. Betten Sie die Version ein setup.pyund setup.pygenerieren Sie ein Modul (z. B. version.py), das nur Versionsinformationen enthält, die von Ihrem Paket importiert wurden, oder

  2. Das Gegenteil: Fügen Sie die Versionsinformationen in Ihr Paket selbst ein und importieren Sie diese , um die Version festzulegen setup.py

dF.
quelle
Ich mag Ihre Empfehlung, aber wie kann man dieses Modul aus setup.py generieren?
Sorin
1
Ich mag die Idee von Option (1), sie scheint einfacher zu sein als Zookos Antwort, die Versionsnummer aus einer Datei zu analysieren. Sie können jedoch nicht sicherstellen, dass version.py erstellt wird, wenn ein Entwickler nur Ihr Repo klont. Es sei denn, Sie checken version.py ein, wodurch die kleine Falte entsteht, dass Sie möglicherweise die Versionsnummer in setup.py ändern, festschreiben, freigeben und dann die Änderung an version.py festschreiben müssen.
Jonathan Hartley
Vermutlich könnten Sie das Modul mit so etwas wie "" "mit open (" mypackage / version.py "," w ") als fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) generieren. ) ""“
Jonathan Hartley
1
Ich denke, Option 2. kann während der Installation fehlschlagen, wie in den Kommentaren zur Antwort von JAB angegeben.
Jonathan Hartley
Was ist damit? Sie fügen __version__ = '0.0.1' "(wobei die Version natürlich eine Zeichenfolge ist) in die Datei __init__.py" des Hauptpakets Ihrer Software ein. Gehen Sie dann zu Punkt 2 über: Im Setup, das Sie aus dem Paket .__ init__ import __version__ als v ausführen, setzen Sie die Variable v als Version Ihrer setup.py. Importieren Sie dann mypack als my, drucken Sie meine .__-Version__, um die Version zu drucken. Die Version wird nur an einer Stelle gespeichert, auf die über den gesamten Code zugegriffen werden kann, und zwar in einer Datei, die nichts anderes im Zusammenhang mit der Software importiert.
SeF
5

Pfeil behandelt es auf interessante Weise.

Jetzt (seit 2e5031b )

In arrow/__init__.py:

__version__ = 'x.y.z'

In setup.py:

from arrow import __version__

setup(
    name='arrow',
    version=__version__,
    # [...]
)

Vor

In arrow/__init__.py:

__version__ = 'x.y.z'
VERSION = __version__

In setup.py:

def grep(attrname):
    pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
    strval, = re.findall(pattern, file_text)
    return strval

file_text = read(fpath('arrow/__init__.py'))

setup(
    name='arrow',
    version=grep('__version__'),
    # [...]
)
Anto
quelle
was ist file_text?
Ely
2
Die aktualisierte Lösung ist tatsächlich schädlich. Wenn setup.py ausgeführt wird, wird die Version des Pakets nicht unbedingt aus dem lokalen Dateipfad angezeigt. Es kann zu einer zuvor installierten Version zurückkehren, z. B. pip install -e .wenn es in einem Entwicklungszweig ausgeführt wird oder etwas beim Testen. setup.py sollte sich unbedingt nicht auf den Import des Pakets verlassen, das gerade installiert wird, um die Parameter für die Bereitstellung zu ermitteln. Huch.
Ely
Ja, ich weiß nicht, warum Pfeil beschlossen hat, auf diese Lösung zurückzugreifen. Außerdem heißt es in der Commit-Nachricht "Updated setup.py mit modernen Python-Standards " ... 🤷
Anto
4

Ich habe auch einen anderen Stil gesehen:

>>> django.VERSION
(1, 1, 0, 'final', 0)
L1ker
quelle
1
Ja, ich habe auch gesehen. Übrigens hat jede Antwort einen anderen Stil, daher weiß ich jetzt nicht, welcher Stil ein "Standard" ist. Auf der Suche nach erwähnten PEPs ...
kbec
Ein anderer Weg gesehen; Der Python-Client von Mongo verwendet eine einfache Version ohne Unterstriche. Das funktioniert also; $ python >>> importiere pymongo >>> pymongo.version '2.7'
AnneTheAgile
Das Implementieren .VERSIONbedeutet nicht, dass Sie nicht implementieren müssen __version__.
Acumenus
Erfordert dies die Implementierung djangoim Projekt?
Stevoisiak
3

Verwenden von setuptoolsundpbr

Es gibt keine Standardmethode zum Verwalten der Version, aber die Standardmethode zum Verwalten Ihrer Pakete ist setuptools .

Die beste Lösung, die ich insgesamt für die Verwaltung der Version gefunden habe, ist die Verwendung setuptoolsmit dempbr Erweiterung. Dies ist jetzt meine Standardmethode zum Verwalten der Version.

Das Einrichten Ihres Projekts für die vollständige Verpackung kann für einfache Projekte übertrieben sein. Wenn Sie jedoch die Version verwalten müssen, sind Sie wahrscheinlich auf der richtigen Ebene, um einfach alles einzurichten. Dadurch wird Ihr Paket auch bei PyPi freigegeben kann sodass jeder es herunterladen und mit Pip verwenden kann.

PBR verschiebt die meisten Metadaten aus den setup.pyTools in eine setup.cfgDatei, die dann als Quelle für die meisten Metadaten verwendet wird, einschließlich der Version. Auf diese Weise können die Metadaten bei Bedarf in eine ausführbare Datei gepackt pyinstallerwerden (falls ja, benötigen Sie wahrscheinlich diese Informationen ), und die Metadaten werden von den anderen Paketverwaltungs- / Setup-Skripten getrennt. Sie können die Versionszeichenfolge direkt setup.cfgmanuell aktualisieren , und sie wird in die Datei gezogen*.egg-info Erstellen Ihrer Paketversionen Ordner . Ihre Skripte können dann mit verschiedenen Methoden aus den Metadaten auf die Version zugreifen (diese Prozesse werden in den folgenden Abschnitten beschrieben).

Wenn Sie Git für VCS / SCM verwenden, ist dieses Setup sogar noch besser, da viele Metadaten von Git abgerufen werden, sodass Ihr Repo Ihre primäre Wahrheitsquelle für einige der Metadaten sein kann, einschließlich Version, Autoren, Änderungsprotokolle, usw. Für die spezifische Version wird eine Versionszeichenfolge für das aktuelle Commit basierend auf den Git-Tags im Repo erstellt.

Da PBR Version, Autor, Änderungsprotokoll und andere Informationen direkt aus Ihrem Git-Repo abruft, sind einige der Metadaten in setup.cfg und automatisch generiert werden, wenn eine Distribution für Ihr Paket erstellt wird (mithilfe von setup.py).

Aktuelle Echtzeitversion

setuptools wird die neuesten Informationen in Echtzeit mit abrufen setup.py :

python setup.py --version

Dadurch wird die neueste Version entweder aus dem setup.cfg Datei oder aus dem Git-Repo abgerufen, basierend auf dem zuletzt vorgenommenen Commit und den im Repo vorhandenen Tags. Dieser Befehl aktualisiert die Version in einer Distribution jedoch nicht.

Aktualisierung der Version

Wenn Sie eine Distribution mit setup.py( py setup.py sdistz. B.) erstellen , werden alle aktuellen Informationen extrahiert und in der Distribution gespeichert. Dies führt im Wesentlichen den setup.py --versionBefehl aus und speichert diese Versionsinformationen in dem package.egg-infoOrdner in einer Reihe von Dateien, in denen Verteilungsmetadaten gespeichert sind.

Hinweis zum Aktualisieren der Versionsmetadaten:

Wenn Sie pbr nicht zum Abrufen von Versionsdaten aus git verwenden, aktualisieren Sie einfach Ihre setup.cfg direkt mit neuen Versionsinformationen (einfach genug, aber stellen Sie sicher, dass dies ein Standardbestandteil Ihres Veröffentlichungsprozesses ist).

Wenn Sie git verwenden und keine Quell- oder Binärdistribution (mit python setup.py sdistoder einem der python setup.py bdist_xxxBefehle) erstellen müssen, können Sie die git-Repo-Informationen am einfachsten in Ihrem <mypackage>.egg-infoMetadatenordner aktualisieren, indem Sie einfach den python setup.py installBefehl ausführen . Dadurch werden alle PBR-Funktionen ausgeführt, die sich auf das Abrufen von Metadaten aus dem Git-Repo beziehen, Ihren lokalen .egg-infoOrdner aktualisieren , ausführbare Skriptdateien für alle von Ihnen definierten Einstiegspunkte installieren und andere Funktionen, die Sie in der Ausgabe sehen können, wenn Sie diesen Befehl ausführen.

Beachten Sie, dass der .egg-infoOrdner im Allgemeinen nicht im Git-Repo selbst in Standard-Python- .gitignoreDateien (z. B. von Gitignore.IO ) gespeichert werden kann , da er aus Ihrer Quelle generiert werden kann. Wenn dies ausgeschlossen ist, stellen Sie sicher, dass Sie über einen Standard- "Freigabeprozess" verfügen, um die Metadaten vor der Veröffentlichung lokal zu aktualisieren. Jedes Paket, das Sie auf PyPi.org hochladen oder auf andere Weise verteilen, muss diese Daten enthalten, um die richtige Version zu haben. Wenn das Git-Repo diese Informationen enthalten soll, können Sie ausschließen, dass bestimmte Dateien ignoriert werden (dh hinzufügen !*.egg-info/PKG_INFOzu .gitignore).

Zugriff auf die Version über ein Skript

Sie können auf die Metadaten aus dem aktuellen Build in Python-Skripten im Paket selbst zugreifen. Für die Version gibt es zum Beispiel verschiedene Möglichkeiten, dies zu tun, die ich bisher gefunden habe:

## This one is a new built-in as of Python 3.8.0 should become the standard
from importlib-metadata import version

v0 = version("mypackage")
print('v0 {}'.format(v0))

## I don't like this one because the version method is hidden
import pkg_resources  # part of setuptools

v1 = pkg_resources.require("mypackage")[0].version
print('v1 {}'.format(v1))

# Probably best for pre v3.8.0 - the output without .version is just a longer string with
# both the package name, a space, and the version string
import pkg_resources  # part of setuptools

v2 = pkg_resources.get_distribution('mypackage').version
print('v2 {}'.format(v2))

## This one seems to be slower, and with pyinstaller makes the exe a lot bigger
from pbr.version import VersionInfo

v3 = VersionInfo('mypackage').release_string()
print('v3 {}'.format(v3))

Sie können eine davon direkt in Ihr __init__.pyPaket einfügen, um die Versionsinformationen wie folgt zu extrahieren, ähnlich wie bei einigen anderen Antworten:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version
LightCC
quelle
Per Down-Abstimmung neu formatiert, um die Frage jetzt direkt zu beantworten.
LightCC
1

Nach mehreren Stunden des Versuchs, die einfachste zuverlässige Lösung zu finden, sind hier die Teile:

Erstellen Sie eine version.py-Datei im Ordner Ihres Pakets "/ mypackage":

# 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__ = '1.2.7'

in setup.py:

exec(open('mypackage/version.py').read())
setup(
    name='mypackage',
    version=__version__,

im Hauptordner init .py:

from .version import __version__

Die exec()Funktion führt das Skript außerhalb von Importen aus, da setup.py ausgeführt wird, bevor das Modul importiert werden kann. Sie müssen die Versionsnummer immer noch nur in einer Datei an einem Ort verwalten, aber leider befindet sie sich nicht in setup.py. (Das ist der Nachteil, aber keine Importfehler zu haben, ist der Vorteil)

Marc Maxmeister
quelle
1

Seit diese Frage zum ersten Mal gestellt wurde, wurden viele Arbeiten zur einheitlichen Versionierung und zur Unterstützung von Konventionen abgeschlossen . Schmackhafte Optionen werden nun detailliert in der Python Verpackung Benutzerhandbuch . Bemerkenswert ist auch, dass die Versionsnummernschemata in Python gemäß PEP 440 relativ streng sind. Daher ist es wichtig , dass die Dinge gesund bleiben, wenn Ihr Paket im Cheese Shop veröffentlicht wird .

Hier ist eine verkürzte Aufschlüsselung der Versionsoptionen:

  1. Lesen Sie die Datei ein setup.py( setuptools ) und erhalten Sie die Version.
  2. Verwenden Sie ein externes Build-Tool (um sowohl __init__.pydie Quellcodeverwaltung als auch die Quellcodeverwaltung zu aktualisieren ), z. B. Stoß2version , Änderungen oder zest.releaser .
  3. Setzen Sie den Wert __version__in einem bestimmten Modul auf eine globale Variable.
  4. Platzieren Sie den Wert in einer einfachen VERSION-Textdatei, damit sowohl setup.py als auch Code gelesen werden können.
  5. Legen Sie den Wert über ein setup.pyRelease fest und verwenden Sie importlib.metadata , um ihn zur Laufzeit abzurufen . (Achtung, es gibt Versionen vor 3.8 und nach 3.8.)
  6. Setzen Sie den Wert auf __version__in sample/__init__.pyund importieren Sie das Beispiel in setup.py.
  7. Verwenden Sie setuptools_scm , um die Versionierung aus der Quellcodeverwaltung zu extrahieren, sodass es sich um die kanonische Referenz und nicht um Code handelt.

HINWEIS : (7) ist möglicherweise der modernste Ansatz (Build-Metadaten sind unabhängig von Code, der durch Automatisierung veröffentlicht wird). Auch HINWEIS dass , wenn Setup für Paket Freigabe verwendet wird , dass ein einfacher python3 setup.py --versiondie Version direkt berichten.

ingyhere
quelle
-1

Wenn Sie NumPy distutils verwenden, numpy.distutils.misc_util.Configurationgibt es eine make_svn_version_py()Methode, mit der die Revisionsnummer package.__svn_version__in die Variable eingebettet wird version.

Matt
quelle
Können Sie weitere Details oder ein Beispiel dafür angeben, wie dies funktionieren würde?
Stevoisiak
Hmm. Im Jahr 2020 ist dies (war es immer?) Für FORTRAN gedacht . Paket "numpy.distutils ist Teil von NumPy, das Standard-Python-Distutils für Fortran-Quellen erweitert."
Ingyhere
-1
  1. Verwenden Sie eine version.pyDatei nur mit __version__ = <VERSION>param in der Datei. In der setup.pyDatei , um den Import __version__param und seinen Wert in der Put - setup.pyDatei wie folgt: version=__version__
  2. Eine andere Möglichkeit besteht darin, nur eine setup.pyDatei mit zu verwenden version=<CURRENT_VERSION>- die CURRENT_VERSION ist fest codiert.

Da wir die Version in der Datei nicht jedes Mal manuell ändern möchten, wenn wir ein neues Tag erstellen (bereit, eine neue Paketversion freizugeben), können wir Folgendes verwenden:

Ich kann das Bumpversion- Paket nur empfehlen . Ich benutze es seit Jahren, um eine Version zu stoßen.

Beginnen Sie mit dem Hinzufügen version=<VERSION>zu Ihrer setup.pyDatei, falls Sie diese noch nicht haben.

Sie sollten jedes Mal, wenn Sie eine Version stoßen, ein kurzes Skript wie dieses verwenden:

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 unter version.pyDatei verwenden, wie es in anderen Beiträgen vorgeschlagen wurde, und die Bumpversion-Datei wie folgt aktualisieren: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • Sie müssen git commit oder git resetalles in Ihrem Repo, sonst erhalten Sie einen schmutzigen Repo-Fehler.
  • Stellen Sie sicher, dass Ihre virtuelle Umgebung das Bumpversion-Paket enthält, ohne das es nicht funktioniert.
Oran
quelle
@cmcginty Entschuldigung für die Verzögerung, bitte überprüfen Sie meine Antwort ^^^ - Beachten Sie, dass Sie git commit oder git reset alles in Ihrem Repo und sicherstellen müssen, dass Ihre virtuelle Umgebung das Paket von enthält bumpversion, ohne es wird es nicht funktionieren. Verwenden Sie die neueste Version.
Oran
Ich bin mir nicht sicher, welche Lösung hier vorgeschlagen wird. Empfehlen Sie, die Version manuell zu verfolgen version.pyoder mit zu verfolgen bumpversion?
Stevoisiak
@StevenVascellaro Ich schlage vor, Bumpversion zu verwenden, niemals manuelle Versionierung. Ich habe versucht zu erklären, dass Sie die Bumpversion anweisen können, um entweder die Version in der Datei setup.py zu aktualisieren, oder besser noch, um die Datei version.py zu aktualisieren. Es ist üblicher, die Datei __version__version.py zu aktualisieren und den Parameterwert in die Datei setup.py zu übernehmen. Meine Lösung wird in der Produktion verwendet und ist eine gängige Praxis. Hinweis: Um ganz klar zu sein, ist die Verwendung von Bumpversion als Teil eines Skripts die beste Lösung. Fügen Sie sie in Ihr CI ein und es wird automatisch ausgeführt.
Oran
-3

Wenn Sie CVS (oder RCS) verwenden und eine schnelle Lösung wünschen, können Sie Folgendes verwenden:

__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])

(Natürlich wird die Revisionsnummer durch CVS ersetzt.)

Auf diese Weise erhalten Sie eine druckfreundliche Version und Versionsinformationen, mit denen Sie überprüfen können, ob das zu importierende Modul mindestens die erwartete Version aufweist:

import my_module
assert my_module.__version_info__ >= (1, 1)
Martin Ibert
quelle
In welcher Datei empfehlen Sie das Speichern __version__? Wie würde man die Versionsnummer mit dieser Lösung erhöhen?
Stevoisiak