Domanda

Esiste un modo standard per associare la stringa di versione a un pacchetto python in modo che io possa fare quanto segue?

import foo
print foo.version

Immagino che ci sia un modo per recuperare quei dati senza alcun hardcoding aggiuntivo, poiché le stringhe minori / maggiori sono già specificate in setup.py. La soluzione alternativa che ho trovato era di avere import __version__ nel mio foo/__init__.py e quindi di avere __version__.py generato da <=>.

È stato utile?

Soluzione

Non direttamente una risposta alla tua domanda, ma dovresti considerare di nominarla __version__, non version.

Questo è quasi un quasi standard. Molti moduli nella libreria standard usano <=>, e questo è usato anche in lotti di moduli di terze parti , quindi è quasi standard.

Di solito, <=> è una stringa, ma a volte è anche un float o una tupla.

Modifica: come menzionato da S.Lott (grazie!), PEP 8 lo dice esplicitamente:

  

Contabilità versione

     

Se devi avere Subversion, CVS o RCS crud nel tuo file sorgente,   fallo come segue.

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

Queste righe devono essere incluse prima della docstring del modulo, prima   qualsiasi altro codice, separato da una riga vuota sopra e sotto.

Dovresti anche assicurarti che il numero di versione sia conforme al formato descritto in PEP 440 ( PEP 386 una versione precedente di questo standard).

Altri suggerimenti

Uso un singolo _version.py file come " una volta che la posizione cannonica " per memorizzare le informazioni sulla versione:

  1. Fornisce un __version__ attributo.

  2. Fornisce la versione standard dei metadati. Pertanto verrà rilevato da pkg_resources o altri strumenti che analizzano i metadati del pacchetto (EGG-INFO e / o PKG-INFO, PEP 0345).

  3. Non importa il pacchetto (o qualsiasi altra cosa) durante la creazione del pacchetto, il che può causare problemi in alcune situazioni. (Vedi i commenti qui sotto su quali problemi possono causare.)

  4. C'è solo un posto in cui il numero di versione è scritto, quindi c'è solo un posto per cambiarlo quando cambia il numero di versione, e c'è meno possibilità di versioni incoerenti.

Ecco come funziona: il " un posto canonico " per memorizzare il numero di versione è un file .py, chiamato " _version.py " che si trova nel pacchetto Python, ad esempio in myniftyapp/_version.py. Questo file è un modulo Python, ma setup.py non lo importa! (Ciò annullerebbe la funzione 3.) Invece setup.py sa che il contenuto di questo file è molto semplice, qualcosa del tipo:

__version__ = "3.6.5"

E così setup.py apre il file e lo analizza, con codice come:

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,))

Quindi setup.py passa quella stringa come valore di " versione " argomento su setup(), soddisfacendo così la funzione 2.

Per soddisfare la funzione 1, puoi avere il tuo pacchetto (in fase di esecuzione, non in fase di installazione!) importare il file _version da myniftyapp/__init__.py in questo modo:

from _version import __version__

Ecco un esempio di questa tecnica che Uso da anni.

Il codice in questo esempio è un po 'più complicato, ma l'esempio semplificato che ho scritto in questo commento dovrebbe essere un'implementazione completa.

Ecco esempio di codice di importazione la versione .

Se vedi qualcosa di sbagliato in questo approccio, per favore fatemelo sapere.

Riscritto 2017-05

Dopo oltre dieci anni di scrittura del codice Python e gestione di vari pacchetti, sono giunto alla conclusione che il fai-da-te non è forse l'approccio migliore.

Ho iniziato a utilizzare pbr pacchetto per gestire il controllo delle versioni nei miei pacchetti. Se stai usando git come SCM, questo si adatterà al tuo flusso di lavoro come per magia, salvando le tue settimane di lavoro (sarai sorpreso da quanto possa essere complesso il problema).

Ad oggi pbr è classificato il pacchetto python # 11 più usato e raggiungere questo livello non lo ha fatto ' t include eventuali trucchi sporchi: era solo uno: risolvere un problema di imballaggio comune in un modo molto semplice.

yourpackage/version.py può fare più del carico di manutenzione del pacchetto, non si limita al controllo delle versioni ma non ti obbliga ad adottare tutti i suoi vantaggi.

Quindi, per darti un'idea di come sembra adottare pbr in un commit dai un'occhiata swiching packaging in pbr

Probabilmente avresti osservato che la versione non è affatto archiviata nel repository. PBR lo rileva dai rami e dai tag Git.

Non devi preoccuparti di cosa succede quando non hai un repository git perché pbr fa " compila " e memorizza nella cache la versione quando impacchetti o installi le applicazioni, quindi non c'è dipendenza runtime da git.

Vecchia soluzione

Ecco la migliore soluzione che ho visto finora e spiega anche perché:

Interno yourpackage/__init__.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'

Interno setup.py:

from .version import __version__

Interno <=>:

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

Se conosci un altro approccio che sembra essere meglio fammi sapere.

Per i PEP 396 (numeri di versione del modulo) differiti, esiste un modo proposto per farlo. Descrive, con logica, uno standard (certamente facoltativo) per i moduli da seguire. Ecco uno snippet:

  

3) Quando un modulo (o pacchetto) include un numero di versione, la versione DOVREBBE essere disponibile nell'attributo __version__.

     

4)   Per i moduli che vivono all'interno di un pacchetto dello spazio dei nomi, il modulo DOVREBBE includere l'attributo <=>. Il pacchetto dello spazio dei nomi stesso NON DOVREBBE includere il proprio <=> attributo.

     

5)   Il valore <=> dell'attributo DOVREBBE essere una stringa.

Anche se probabilmente è troppo tardi, esiste un'alternativa leggermente più semplice alla risposta precedente:

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

(E sarebbe abbastanza semplice convertire porzioni con incremento automatico dei numeri di versione in una stringa usando str().)

Naturalmente, da quello che ho visto, le persone tendono ad usare qualcosa come la versione precedentemente menzionata quando si usa __version_info__, e come tale la memorizzano come una tupla di ints; tuttavia, non vedo esattamente il punto nel farlo, poiché dubito che ci siano situazioni in cui eseguiresti operazioni matematiche come l'addizione e la sottrazione su porzioni di numeri di versione per qualsiasi scopo oltre alla curiosità o all'auto-incremento (e anche allora, int() e <=> possono essere usati abbastanza facilmente). (D'altra parte, c'è la possibilità che il codice di qualcun altro si aspetti una tupla numerica piuttosto che una tupla di stringa e quindi fallisca.)

Questa è, ovviamente, la mia opinione personale e mi piacerebbe volentieri il contributo degli altri sull'uso di una tupla numerica.


Come mi ha ricordato Shezi, i confronti (lessicali) delle stringhe numeriche non hanno necessariamente lo stesso risultato dei confronti numerici diretti; gli zeri iniziali sarebbero tenuti a provvedere per questo. Quindi, alla fine, l'archiviazione di <=> (o come si chiamerebbe) come una tupla di valori interi consentirebbe confronti di versioni più efficienti.

Uso un file JSON nella directory del pacchetto. Questo soddisfa i requisiti di Zooko.

Interno pkg_dir/pkg_info.json:

{"version": "0.1.0"}

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

Interno 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']

Ho anche inserito altre informazioni in pkg_info.json, come l'autore. io mi piace usare JSON perché posso automatizzare la gestione dei metadati.

Molte di queste soluzioni qui ignorano i tag git versione, il che significa che è necessario tenere traccia della versione in più punti (non valido). Mi sono avvicinato a questo con i seguenti obiettivi:

  • Deriva tutti i riferimenti alla versione di Python da un tag nel git tag repo
  • Automatizza i passaggi push / setup.py upload e make release con un singolo comando che non accetta input.

Come funziona:

  1. Da un comando origin, l'ultima versione taggata nel repository git viene trovata e incrementata. Il tag viene rinviato a Makefile.

  2. src/_version.py memorizza la versione in setup.py dove verrà letta da _version.py e inclusa anche nella versione. Non controllare package.__version__ nel controllo del codice sorgente!

  3. Il comando
  4. release legge la stringa della nuova versione da next_minor_ver.

dettagli:

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

La destinazione next_major_ver incrementa sempre la cifra della 3a versione, ma è possibile utilizzare versionbump.py o my_module/_version.py per incrementare le altre cifre. I comandi si basano sullo script my_module/__init__.py che viene archiviato nella radice del repository

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

Questo risolve notevolmente il modo in cui elaborare e incrementare il numero di versione da my_module.

__ init __. Py

Il file <=> viene importato in <=>. Inserisci qui qualsiasi configurazione di installazione statica che desideri distribuire con il tuo modulo.

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

setup.py

L'ultimo passaggio è leggere le informazioni sulla versione dal <=> modulo.

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

Ovviamente, perché tutto ciò funzioni, per iniziare devi avere almeno un tag di versione nel tuo repository.

git tag -a v0.0.1

Non sembra esserci un modo standard per incorporare una stringa di versione in un pacchetto python. La maggior parte dei pacchetti che ho visto usano alcune varianti della tua soluzione, ad esempio eitner

  1. Incorporare la versione in setup.py e fare in modo che version.py generi un modulo (ad esempio <=>) contenente solo le informazioni sulla versione, importate dal pacchetto o

  2. Il contrario: inserisci le informazioni sulla versione nel pacchetto stesso e importa che per impostare la versione in <=>

Da notare anche che __version__ è un semi-standard. in python lo è anche __version_info__ che è una tupla, nei casi semplici puoi semplicemente fare qualcosa del tipo:

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

... e puoi ottenere la <=> stringa da un file o qualsiasi altra cosa.

Ho visto anche un altro stile:

>>> django.VERSION
(1, 1, 0, 'final', 0)

arrow lo gestisce in modo interessante.

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

Per quello che vale, se stai usando NumPy distutils, numpy.distutils.misc_util.Configuration ha un make_svn_version_py() che incorpora il numero di revisione all'interno di package.__svn_version__ nella variabile version.

  1. Utilizza un file version.py solo con __version__ = <VERSION> param nel file. Nel file setup.py importa il parametro __version__ e inserisci il suo valore nel file version=__version__ in questo modo: version=<CURRENT_VERSION>
  2. Un altro modo è usare solo un version=<VERSION> file con .bumpversion.cfg - CURRENT_VERSION è hardcoded.

Dato che non vogliamo cambiare manualmente la versione nel file ogni volta che creiamo un nuovo tag (pronto a rilasciare una nuova versione del pacchetto), possiamo usare quanto segue ..

Consiglio vivamente il pacchetto bumpversion . Lo uso da anni per presentare una versione.

inizia aggiungendo [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>] al tuo git commit file se non lo hai già.

Dovresti usare uno script breve come questo ogni volta che esegui il bump di una versione:

bumpversion (patch|minor|major) - choose only one option
git push
git push --tags

Quindi aggiungere un file per repository chiamato: git reset:

[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]

Nota:

  • Puoi utilizzare il parametro <=> in <=> file come è stato suggerito in altri post e aggiornare il file bumpversion in questo modo: <=>
  • Devi <=> o <=> tutto nel tuo repository, altrimenti otterrai un errore repository sporco.
  • Assicurati che il tuo ambiente virtuale includa il pacchetto di bumpversion, senza di esso non funzionerà.

Se si utilizza CVS (o RCS) e si desidera una soluzione rapida, è possibile utilizzare:

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

(Naturalmente, il numero di revisione verrà sostituito dal CVS.)

Questo ti dà una versione stampabile e informazioni sulla versione che puoi usare per verificare che il modulo che stai importando abbia almeno la versione prevista:

import my_module
assert my_module.__version_info__ >= (1, 1)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top