it-swarm.com.de

Standardmethode zum Einbetten der Version in python package?

Gibt es eine Standardmethode zum Verknüpfen von Versionszeichenfolgen mit einem python - Paket, sodass ich Folgendes ausführen 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 in setup.py Bereits kleinere/größere Zeichenfolgen angegeben sind. Als alternative Lösung habe ich import __version__ In meinem foo/__init__.py Und dann __version__.py Generiert durch setup.py.

228
Dimitri Tcaciuc

Keine direkte Antwort auf Ihre Frage, aber Sie sollten erwägen, sie __version__ Zu nennen, nicht version.

Dies ist fast ein Quasi-Standard. Viele Module in der Standardbibliothek verwenden __version__, Und dies wird auch in Lots von Modulen von Drittanbietern verwendet, sodass es sich um den Quasi-Standard handelt.

Normalerweise ist __version__ Ein String, aber manchmal ist es auch ein Float oder Tuple.

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

Version Buchhaltung

Wenn Sie Subversion, CVS oder RCS Crud in Ihrer Quelldatei haben müssen, gehen Sie wie folgt vor.

    __version__ = "$Revision: 63990 $"
    # $Source$

Diese Zeilen sollten nach der Dokumentzeichenfolge des Moduls eingefügt werden, vor jedem anderen Code, getrennt durch eine Leerzeile darüber und darunter.

Sie sollten auch sicherstellen, dass die Versionsnummer dem in PEP 44 ( PEP 386 einer früheren Version dieses Standards beschriebenen Format entspricht.

115
oefe

Ich verwende eine einzelne _version.py - Datei als "einst kanonischen Ort" zum Speichern von Versionsinformationen:

  1. Es bietet ein Attribut __version__.

  2. Es bietet die Standard-Metadatenversion. Daher wird es von pkg_resources Oder 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 wird erläutert, welche Probleme dies verursachen kann.)

  4. Es gibt nur eine Stelle, an der die Versionsnummer notiert wird. Daher gibt es nur eine Stelle, an der 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 es nicht! (Das würde Feature 3 zunichte machen.) Stattdessen weiß Ihre setup.py, dass der Inhalt von Diese Datei ist sehr einfach und sieht ungefähr so ​​aus:

__version__ = "3.6.5"

Und so öffnet Ihre setup.py die Datei und analysiert sie mit folgendem Code:

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 "version" -Arguments an setup(), wodurch Merkmal 2 erfüllt wird.

Um Feature 1 zu erfüllen, können Sie Ihr Paket (zur Laufzeit, nicht zur Setup-Zeit!) Die _version-Datei aus myniftyapp/__init__.py Wie folgt importieren lassen:

from _version import __version__

Hier ist ein Beispiel für diese Technik , das 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 Beispielcode zum Importieren der Version .

Wenn Sie irgendetwas falsch an diesem Ansatz sehen, lassen Sie es mich bitte wissen.

101
Zooko

2017-05 umgeschrieben

Nach mehr als zehnjährigem Schreiben von Python Code und Verwalten verschiedener Pakete bin ich zu dem Schluss gekommen, dass DIY vielleicht nicht der beste Ansatz ist.

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

Bis heute pbr ist die Nummer 11 der am häufigsten verwendeten python package und das Erreichen dieses Levels beinhaltete keine schmutzigen Tricks Ein sehr einfacher Weg.

pbr kann mehr für die Paketwartung tun, ist nicht auf die Versionierung beschränkt, zwingt Sie jedoch nicht dazu, alle seine Vorteile zu nutzen.

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

Wahrscheinlich würden Sie feststellen, 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 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, der besser zu sein scheint, lassen Sie es mich wissen.

93
sorin

Gemäß dem aufgeschobenen PEP 396 (Module Version Numbers) gibt es einen vorgeschlagenen Weg, dies zu tun. Es beschreibt aus Gründen einen (zugegebenermaßen optionalen) Standard, dem Module folgen müssen. Hier ist ein Ausschnitt:

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

4) Für Module, die sich in einem Namespace-Paket befinden, SOLLTE das Modul __version__ Attribut. Das Namespace-Paket selbst sollte NICHT sein eigenes __version__ Attribut.

5) Die __version__ Attribut Wert sollte eine Zeichenfolge sein.

26
Oddthinking

Obwohl dies wahrscheinlich viel zu spät ist, gibt es eine etwas einfachere Alternative zu der vorherigen Antwort:

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

(Und es wäre ziemlich einfach, automatisch inkrementierende Teile von Versionsnummern mit str() in eine Zeichenfolge umzuwandeln.)

Nach allem, was ich gesehen habe, neigen die Leute natürlich dazu, etwas wie die zuvor erwähnte Version zu verwenden, wenn sie __version_info__ Verwenden, und sie als Tupel von Ints zu speichern. Ich verstehe den Sinn 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 Neugierde oder automatische Inkrementierung ausführen würden (und selbst dann, int() und str() können ziemlich einfach verwendet werden). (Andererseits besteht die Möglichkeit, dass der Code einer anderen Person einen numerischen Tupel anstelle eines String-Tupels erwartet und damit fehlschlägt.)

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


Wie Shezi mich erinnerte, haben (lexikalische) Vergleiche von Zahlenfolgen nicht unbedingt das gleiche Ergebnis wie direkte numerische Vergleiche. Hierfür wären führende Nullen erforderlich. Das Speichern von __version_info__ (Oder wie auch immer es heißen würde) als Tupel mit ganzzahligen Werten würde letztendlich effizientere Versionsvergleiche ermöglichen.

22
JAB

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

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 der Autor. Ich benutze gerne JSON, weil ich die Verwaltung von Metadaten automatisieren kann.

11
Andy Lee

Viele dieser Lösungen hier ignorieren git Versions-Tags, was immer noch bedeutet, dass Sie die Version an mehreren Stellen verfolgen müssen (schlecht). Ich näherte mich dem mit den folgenden Zielen:

  • Leiten Sie alle python Versionsreferenzen von einem Tag im Repo git ab
  • Automatisieren Sie die Schritte git tag/Push und setup.py upload Mit einem einzigen Befehl, für den keine Eingaben erforderlich sind.

Wie es funktioniert:

  1. Mit einem Befehl make release Wird die zuletzt getaggte Version im Git-Repo gefunden und erhöht. Das Tag wird auf Origin zurückgeschoben.

  2. Das Makefile speichert die Version in src/_version.py, Wo sie von setup.py Gelesen und auch in der Version enthalten wird. Checken Sie _version.py Nicht in die Quellcodeverwaltung ein!

  3. Der Befehl setup.py 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)"' > [email protected]

.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 Ziel release erhöht immer die Ziffer der 3. Version, aber Sie können die anderen Ziffern mit next_minor_ver Oder next_major_ver Erhöhen. Die Befehle basieren auf dem Skript versionbump.py, 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 __== '__main__':
    cli()  # pylint: disable=no-value-for-parameter

Dies macht das schwere Heben, wie die Versionsnummer von git verarbeitet und inkrementiert wird.

__init__.py

Die Datei my_module/_version.py Wird in my_module/__init__.py Importiert. Fügen Sie hier eine statische Installationskonfiguration ein, die mit Ihrem Modul verteilt werden soll.

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

setup.py

Der letzte Schritt ist das Lesen der Versionsinformationen aus dem Modul my_module.

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, müssen Sie natürlich mindestens ein Versions-Tag in Ihrem Repo haben, um zu beginnen.

git tag -a v0.0.1
10
cmcginty

Erwähnenswert ist auch, dass sowie __version__ als semi-std. in python so ist __version_info__ das ist ein Tupel, in den einfachen Fällen kann man einfach etwas machen wie:

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

... und du bekommst das __version__ String aus einer Datei oder was auch immer.

6
James Antill

Es scheint keine Standardmethode zu geben, um eine Versionszeichenfolge in ein python paket einzubetten. Die meisten Pakete, die ich gesehen habe, verwenden eine Variante Ihrer Lösung, d. H. Eitner

  1. Betten Sie die Version in setup.py Ein und lassen Sie setup.py Ein Modul generieren (z. B. version.py), Das nur Versionsinformationen enthält, die von Ihrem Paket importiert wurden, oder

  2. Umgekehrt: Geben Sie die Versionsinformationen in Ihr Paket ein und importieren Sie that, um die Version in setup.py Zu setzen.

6
dF.

Ich habe auch einen anderen Stil gesehen:

>>> Django.VERSION
(1, 1, 0, 'final', 0)
4
L1ker

Pfeil behandelt es auf interessante Weise.

Jetzt (seit 2e5031b )

Im arrow/__init__.py:

__version__ = 'x.y.z'

Im setup.py:

from arrow import __version__

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

Vorher

Im arrow/__init__.py:

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

Im 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__'),
    # [...]
)
2
Anto

Wenn Sie NumPy-Distutils verwenden, hat numpy.distutils.misc_util.Configuration Eine make_svn_version_py() - Methode, die die Revisionsnummer in package.__svn_version__ Einbettet Variable version.

0
Matt