Modo standard per incorporare la versione nel pacchetto python?


265

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, dal momento che le stringhe minori / maggiori sono setup.pygià specificate . Una soluzione alternativa che ho trovato era quella di avere import __version__nel mio foo/__init__.pye poi __version__.pygenerato da setup.py.


7
Cordiali saluti, c'è un'ottima panoramica su: packaging.python.org/en/latest/…
ionelmc

1
La versione di un pacchetto installato può essere recuperata dai metadati con setuptools , quindi in molti casi setup.pyè sufficiente inserire solo la versione . Vedere questa domanda .
saaj,

2
Cordiali saluti, ci sono fondamentalmente 5 modelli comuni per mantenere l'unica fonte di verità (sia in fase di installazione che di runtime) per il numero di versione.
KF Lin,

La documentazione di @ionelmc Python elenca 7 diverse opzioni per il single-souring . Ciò non contraddice il concetto di " singola fonte di verità "?
Stevoisiak,

@StevenVascellaro non sono sicuro di quello che stai chiedendo. Ci sono molti modi elencati qui perché la guida all'imballaggio non vuole essere considerata.
ionelmc,

Risposte:


136

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

Questo è quasi un quasi standard. Molti moduli nella libreria standard usano __version__, e questo è usato anche in molti moduli di terze parti, quindi è quasi standard.

Di solito, __version__è una stringa, ma a volte è anche un float o una tupla.

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

Nomi secondari a livello di modulo

Modulo di livello "dunders" (cioè nomi con due principali e due sottolineature trascinamento) quali __all__, __author__, __version__, ecc devono essere posizionati dopo la docstring modulo ma prima di qualsiasi istruzione di importazione tranne da __future__importazioni.

È inoltre necessario assicurarsi che il numero di versione sia conforme al formato descritto in PEP 440 ( PEP 386 una versione precedente di questo standard).


9
Dovrebbe essere una stringa e avere un version_info per la versione tupla.
James Antill,

James: Perché nello __version_info__specifico? (Che "inventa" la tua parola doppia sottolineatura.) [Quando James ha commentato, i caratteri di sottolineatura non hanno fatto nulla nei commenti, ora indicano enfasi, quindi anche James ha scritto __version_info__. --- ed.]

Puoi vedere qualcosa su ciò che la versione dovrebbe dire su pacchetti.python.org/distribute/… Quella pagina riguarda la distribuzione, ma il significato del numero di versione sta diventando di fatto uno standard.
sienkiew,

2
Destra. Sembra che questi PEP si contraddicano a vicenda. Bene, PEP 8 dice "if" e "crud", quindi non approva realmente l'espansione delle parole chiave VCS. Inoltre, se passi mai a un VCS diverso, perderai le informazioni di revisione. Pertanto, suggerirei di utilizzare un'informazione sulla versione conforme a PEP 386/440 incorporata in un singolo file sorgente, almeno per progetti più grandi.
oefe,

2
Dove metteresti quella versione . Considerando che questa è la versione accettata, mi piacerebbe vedere queste informazioni aggiuntive qui.
darkgaze,

120

Uso un singolo _version.pyfile come "luogo un tempo cannonico" per memorizzare le informazioni sulla versione:

  1. Fornisce un __version__attributo.

  2. Fornisce la versione standard dei metadati. Pertanto verrà rilevato da pkg_resourceso 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 ciò può causare.)

  4. C'è un solo 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: "un posto canonico" per memorizzare il numero di versione è un file .py, denominato "_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ò vanificherebbe 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 dell'argomento "versione" a 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 in myniftyapp/__init__.pyquesto modo:

from _version import __version__

Ecco un esempio di questa tecnica che utilizzo 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 un esempio di codice per importare la versione .

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


8
Potresti descrivere i problemi che motivano # 3? Glifo disse che aveva qualcosa a che fare con "a setuptools piace fingere che il tuo codice non sia in nessun punto del sistema quando setup.py viene eseguito", ma i dettagli aiuterebbero a convincere me e gli altri.
Ivan Kozik,

2
@Iva Ora, in quale ordine lo strumento dovrebbe farlo? Non può (nel setuptools / PIP / virtualenv sistema di oggi) sa nemmeno quali sono le dipendenze sono fino a che non valuta la tua setup.py. Inoltre, se provasse a fare prima la profondità completa e tutti i deps prima di farlo, si bloccherebbe se ci fossero deps circolari. Ma se prova a compilare questo pacchetto prima di installare le dipendenze, quindi se importi il ​​tuo pacchetto dal tuo setup.py, non sarà necessariamente in grado di importare i suoi deps o le giuste versioni dei suoi deps.
Zooko,

3
Potresti scrivere il file "version.py" da "setup.py" invece di analizzarlo? Sembra più semplice.
Jonathan Hartley,

3
Jonathan Hartley: Sono d'accordo che sarebbe leggermente più semplice per il tuo "setup.py" scrivere il file "version.py" invece di analizzarlo, ma si aprirà una finestra per incoerenza, quando hai modificato setup.py per avere la nuova versione ma non hai ancora eseguito setup.py per aggiornare il file version.py. Un altro motivo per avere la versione canonica in un piccolo file separato è che rende facile per altri strumenti, come strumenti che leggono il tuo stato di controllo delle revisioni, scrivere il file della versione.
Zooko,

3
Un approccio simile è quello execfile("myniftyapp/_version.py")di all'interno di setup.py, piuttosto che cercare di analizzare manualmente il codice della versione. Suggerito in stackoverflow.com/a/2073599/647002 - anche la discussione potrebbe essere utile.
medmunds

97

Riscritto 2017-05

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

Ho iniziato a utilizzare il pbrpacchetto 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 al 11 ° pacchetto python più utilizzato e raggiungere questo livello non ha incluso alcun trucco sporco: era solo uno: risolvere un problema comune di packaging in un modo molto semplice.

pbr 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 al packaging di pbr

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

Non c'è bisogno di preoccuparsi di ciò che accade quando non si dispone di un repository git perché pbr "compila" e memorizza nella cache la versione quando si impacchettano o installano le applicazioni, quindi non c'è dipendenza runtime da git.

Vecchia soluzione

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

All'interno 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'

All'interno yourpackage/__init__.py:

from .version import __version__

All'interno setup.py:

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

Se conosci un altro approccio che sembra essere meglio, fammelo sapere.


12
Err no. execfile () non esiste in Python 3, quindi è meglio usare exec (open (). read ()).
Christophe Vu-Brugier,

4
perché non anche from .version import __version__in setup.py?
Aprillion

4
@Aprillion Perché il pacchetto non è caricato quando setup.pyè in esecuzione - provalo, otterrai un errore (o almeno, l'ho fatto :-))
darthbith,

3
Il collegamento a pbr risulta in un gateway non valido.
MERose,

4
pbr , senza dubbio, è un ottimo strumento, ma non sei riuscito a rispondere alla domanda. Come è possibile accedere alla versione corrente o al pacchetto installato tramite bpr .
nad2000,

29

Secondo il PEP 396 differito (numeri di versione del modulo) , 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 __version__nell'attributo.

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

5) Il __version__valore dell'attributo DOVREBBE essere una stringa.


13
Quel PEP non è accettato / standardizzato, ma differito (per mancanza di interesse). Pertanto è un po 'fuorviante affermare che " esiste un modo standard " specificato da esso.
tessitore

@weaver: Oh mio! Ho imparato qualcosa di nuovo. Non sapevo che fosse qualcosa da controllare.
Pensando in modo strano il

4
Modificato per notare che non è uno standard. Ora mi sento in imbarazzo, perché ho sollevato richieste di funzionalità su progetti che chiedevano loro di seguire questo "standard".
Pensando in modo strano il

1
Forse dovresti occuparti del lavoro di standardizzazione su quel PEP, dato che sembri interessato :)
tessitore

Ciò funzionerebbe per il controllo delle versioni di un singolo modulo, ma non sono sicuro che si applicherebbe al controllo delle versioni di un progetto completo.
Stevoisiak,

21

Sebbene questo sia 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 a incremento automatico dei numeri di versione in una stringa usando str().)

Ovviamente, da quello che ho visto, le persone tendono ad usare qualcosa come la versione precedentemente menzionata durante l'uso __version_info__, e quindi archiviarla 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 addizione e sottrazione su porzioni di numeri di versione per qualsiasi scopo oltre a curiosità o auto-incremento (e anche allora, int()e str()può essere usato 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 ha ricordato Shezi, i confronti (lessicali) delle stringhe di numeri non hanno necessariamente lo stesso risultato dei confronti numerici diretti; gli zeri iniziali sarebbero tenuti a provvedere per questo. Quindi, alla fine, l'archiviazione __version_info__(o come si chiamerebbe) come una tupla di valori interi consentirebbe confronti di versioni più efficienti.


12
bello (+1), ma non preferiresti i numeri invece delle stringhe? ad es.__version_info__ = (1,2,3)
orip,

3
Il confronto delle stringhe può diventare pericoloso quando i numeri di versione superano 9, ad esempio '10' <'2'.
D Coetzee,

13
Faccio anche questo, ma leggermente ottimizzato per affrontare gli __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
ints

2
Il problema __version__ = '.'.join(__version_info__)è che __version_info__ = ('1', '2', 'beta')diventerà 1.2.beta, no 1.2betao1.2 beta
nagisa il

4
Penso che il problema con questo approccio sia dove inserire le righe di codice che dichiarano __version__. Se sono in setup.py, il tuo programma non può importarli dal suo pacchetto. Forse questo non è un problema per te, nel qual caso, va bene. Se rientrano nel tuo programma, setup.py può importarli, ma non dovrebbe, poiché setup.py viene eseguito durante l'installazione quando le dipendenze del programma non sono ancora installate (setup.py viene utilizzato per determinare quali sono le dipendenze .) Da qui la risposta di Zooko: analizzare manualmente il valore da un file di origine del prodotto, senza importare il pacchetto del prodotto
Jonathan Hartley

11

Molte di queste soluzioni ignorano i gittag di 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 gitrepository
  • Automatizza git tag/ pushe setup.py uploadpassi con un singolo comando che non accetta input.

Come funziona:

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

  2. I Makefilenegozi la versione in src/_version.pycui sarà letto da setup.pye comprendeva anche nella release. Non controllare _version.pynel controllo del codice sorgente!

  3. setup.pyIl comando legge la nuova stringa di versione da package.__version__.

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

Il releasetarget incrementa sempre la terza cifra della versione, ma è possibile utilizzare next_minor_vero next_major_verper aumentare le altre cifre. I comandi si basano sullo versionbump.pyscript che è registrato 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 fa il duro lavoro su come elaborare e incrementare il numero di versione da git.

__init__.py

Il my_module/_version.pyfile viene importato in my_module/__init__.py. Inserire qui qualsiasi configurazione di installazione statica che si desidera distribuire con il modulo.

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

setup.py

L'ultimo passo è leggere le informazioni sulla versione dal my_modulemodulo.

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

1
in effetti, l'intero thread dimentica che un VCS è molto importante in questa discussione. solo un obs: l'incremento della versione dovrebbe rimanere un processo manuale, quindi il modo preferito sarebbe 1. creare manualmente e spingere un tag 2. lasciare che gli strumenti VCS scoprano quel tag e lo memorizzino dove necessario (wow - questa interfaccia di editing SO mi sta davvero paralizzando - Ho dovuto modificarlo una dozzina di volte solo per occuparmi di newline E ANCORA NON FUNZIONA! @ # $% ^ & *)
axd

Non c'è bisogno di usarlo versionbump.pyquando abbiamo un fantastico pacchetto bumpversion per Python.
Oran,

@Oran Ho guardato versionbump. I documenti non sono molto chiari e non gestiscono molto bene i tag. Nel mio test sembra entrare in stati che lo causano in crash: subprocess.CalledProcessError: Command '[' git ',' commit ',' -F ',' / var / cartelle / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngbo '] 'restituito stato di uscita diverso da zero 1.
cmcginty

1
Perché non dovrebbe _version.pyessere monitorato con il controllo versione?
Stevoisiak,

1
@StevenVascellaro Questo complica il processo di rilascio. Ora devi assicurarti che i tag e gli commit corrispondano al valore in _version.py. L'uso di un tag a versione singola è un IMO più pulito e significa che puoi creare una versione direttamente dall'interfaccia utente di github.
cmcginty

10

Uso un file JSON nella directory del pacchetto. Questo si adatta alle esigenze di Zooko.

All'interno pkg_dir/pkg_info.json:

{"version": "0.1.0"}

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

All'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 inserito anche altre informazioni pkg_info.json, come l'autore. Mi piace usare JSON perché posso automatizzare la gestione dei metadati.


Potresti elaborare come utilizzare il json per automatizzare la gestione dei metadati? Grazie!
ryanjdillon,

6

Vale anche la pena notare che oltre ad __version__essere un semi-standard. in Python è così __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 __version__stringa da un file o qualsiasi altra cosa.


1
Hai riferimenti / link sull'origine di questo utilizzo di __version_info__?
Craig McQueen,

3
Beh, è ​​la stessa mappatura di sys.version su sys.version_info. Quindi: docs.python.org/library/sys.html#sys.version_info
James Antill il

7
È più semplice eseguire la mappatura nell'altra direzione ( __version_info__ = (1, 2, 3)e __version__ = '.'.join(map(str, __version_info__))).
Eric O Lebigot,

2
@EOL - __version__ = '.'.join(str(i) for i in __version_info__)- leggermente più lungo ma più pitonico.
ArtOfWarfare il

2
Non sono sicuro che ciò che proponi sia chiaramente più pitonico, in quanto contiene una variabile fittizia che non è realmente necessaria e il cui significato è un po 'difficile da esprimere ( inon ha significato, version_numè un po' lungo e ambiguo ...). Prendo anche l'esistenza di map()in Python come un forte suggerimento che dovrebbe essere usato qui, poiché ciò che dobbiamo fare qui è il tipico caso d' map()uso di (usare con una funzione esistente) —Non vedo molti altri usi ragionevoli.
Eric O Lebigot,

5

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, vale a dire eitner

  1. Incorporare la versione setup.pye setup.pygenerare un modulo (ad es. version.py) Contenente solo le informazioni sulla versione, importate dal pacchetto o

  2. L'inverso: mettere le informazioni sulla versione nel pacchetto stesso, e l'importazione che per impostare la versione in setup.py


Mi piace la tua raccomandazione, ma come generare questo modulo da setup.py?
sorin,

1
Mi piace l'idea dell'opzione (1), sembra più semplice della risposta di Zooko di analizzare il numero di versione da un file. Ma non puoi assicurarti che version.py venga creato quando uno sviluppatore semplicemente clona il tuo repository. A meno che non si esegua il check in version.py, che apre la piccola ruga che è possibile modificare il numero di versione in setup.py, eseguire il commit, rilasciare e quindi (slash dimenticare di) eseguire il commit della modifica in version.py.
Jonathan Hartley,

Presumibilmente potresti generare il modulo usando qualcosa come "" "con open (" mypackage / version.py "," w ") come fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "
Jonathan Hartley

1
Penso che l'opzione 2. sia suscettibile di errori durante l'installazione, come indicato nei commenti alla risposta di JAB.
Jonathan Hartley,

Che ne dici? Inserisci __version__ = '0.0.1' "(dove la versione è una stringa, ovviamente) in __init__.py" del pacchetto principale del tuo software. Quindi vai al punto 2: nell'installazione che fai dal pacchetto .__ init__ import __version__ come v, e quindi imposti la variabile v come versione di setup.py. Quindi importa mypack come mio, stampa la mia .__ versione__ stamperà la versione. La versione sarà archiviata in un solo posto, accessibile da tutto il codice, in un file che non importa nient'altro correlato al software.
SeF

5

la freccia lo gestisce in modo interessante.

Ora (dal 2e5031b )

In arrow/__init__.py:

__version__ = 'x.y.z'

In setup.py:

from arrow import __version__

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

Prima

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

che cos'è file_text?
ely

2
la soluzione aggiornata è effettivamente dannosa. Quando setup.py è in esecuzione, non vedrà necessariamente la versione del pacchetto dal percorso del file locale. Potrebbe tornare a una versione precedentemente installata, ad esempio l'esecuzione pip install -e .su un ramo di sviluppo o qualcosa durante il test. setup.py non dovrebbe assolutamente fare affidamento sull'importazione del pacchetto che sta installando per determinare i parametri per la distribuzione. Yikes.
ely

Sì, non so perché Arrow abbia deciso di regredire a questa soluzione. Inoltre, il messaggio di commit dice "Aggiornato setup.py con i moderni standard Python " ... 🤷
Anto

4

Ho visto anche un altro stile:

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

1
Sì, ho visto anche io. A proposito ogni risposta prende un altro stile, quindi ora non so quale stile sia uno "standard". In cerca di PEP menzionati ...
Kbec,

Un altro modo di vedere; Il client Python di Mongo utilizza la versione normale, senza caratteri di sottolineatura. Quindi funziona così; $ python >>> import pymongo >>> pymongo.version '2.7'
AnneTheAgile

L'implementazione .VERSIONnon significa che non devi implementare __version__.
Acumenus,

Ciò richiede l'implementazione djangonel progetto?
Stevoisiak,

3

Utilizzando setuptoolsepbr

Non esiste un modo standard per gestire la versione, ma il modo standard per gestire i pacchetti è setuptools.

La migliore soluzione che ho trovato nel complesso per la gestione della versione è quella di utilizzare setuptoolsl' pbrestensione. Questo è ora il mio modo standard di gestire la versione.

L'impostazione del progetto per il packaging completo può essere eccessiva per progetti semplici, ma se è necessario gestire la versione, probabilmente si è al livello giusto per impostare tutto. In questo modo, il pacchetto può essere rilasciato su PyPi in modo che tutti possano scaricarlo e utilizzarlo con Pip.

PBR sposta la maggior parte dei metadati dagli setup.pystrumenti e in un setup.cfgfile che viene quindi utilizzato come sorgente per la maggior parte dei metadati, che può includere la versione. Ciò consente ai metadati di essere impacchettati in un eseguibile usando qualcosa come pyinstallerse necessario (in tal caso, probabilmente avrai bisogno di queste informazioni ) e separa i metadati dagli altri script di gestione / installazione del pacchetto. È possibile aggiornare direttamente la stringa di versione setup.cfgmanualmente e verrà estratta nella *.egg-infocartella durante la creazione delle versioni del pacchetto. Gli script possono quindi accedere alla versione dai metadati utilizzando vari metodi (questi processi sono descritti nelle sezioni seguenti).

Quando si utilizza Git per VCS / SCM, questa configurazione è ancora migliore, poiché trarrà molti metadati da Git in modo che il repository possa essere la fonte principale di verità per alcuni dei metadati, tra cui versione, autori, log delle modifiche, ecc. Per la versione specifica, creerà una stringa di versione per il commit corrente in base ai tag git nel repository.

Poiché PBR estrarrà la versione, l'autore, il log delle modifiche e altre informazioni direttamente dal tuo repository git, così alcuni dei metadati setup.cfgpossono essere esclusi e generati automaticamente ogni volta che viene creata una distribuzione per il tuo pacchetto (usandosetup.py )

Versione corrente in tempo reale

setuptoolsestrarrà le ultime informazioni in tempo reale utilizzando setup.py:

python setup.py --version

Ciò estrarrà la versione più recente dal setup.cfgfile o dal repository git, in base all'ultimo commit eseguito e ai tag esistenti nel repository. Questo comando però non aggiorna la versione in una distribuzione.

Aggiornamento della versione

Quando si crea una distribuzione con setup.py(ad esempio py setup.py sdist, ad esempio), tutte le informazioni correnti verranno estratte e archiviate nella distribuzione. Questo essenzialmente esegue il setup.py --versioncomando e quindi memorizza le informazioni sulla versione nella package.egg-infocartella in un set di file che memorizzano i metadati di distribuzione.

Nota sul processo di aggiornamento dei metadati della versione:

Se non stai usando pbr per estrarre i dati della versione da git, allora aggiorna il tuo setup.cfg direttamente con le informazioni sulla nuova versione (abbastanza facile, ma assicurati che questa sia una parte standard del tuo processo di rilascio).

Se stai usando git e non hai bisogno di creare una distribuzione sorgente o binaria (usando python setup.py sdisto uno dei python setup.py bdist_xxxcomandi) il modo più semplice per aggiornare le informazioni di repository git nella <mypackage>.egg-infocartella dei metadati è semplicemente eseguire il python setup.py installcomando. Questo eseguirà tutte le funzioni PBR relative all'estrazione dei metadati dal repository git e aggiornerà la .egg-infocartella locale , installerà gli eseguibili di script per tutti i punti di ingresso che hai definito e altre funzioni che puoi vedere dall'output quando esegui questo comando.

Si noti che la .egg-infocartella è generalmente esclusa dall'archiviazione nel repository git stesso nei .gitignorefile Python standard (come da Gitignore.IO ), poiché può essere generata dal proprio sorgente. Se viene escluso, assicurarsi di disporre di un "processo di rilascio" standard per ottenere i metadati aggiornati localmente prima del rilascio e che tutti i pacchetti caricati su PyPi.org o altrimenti distribuiti devono includere questi dati per avere la versione corretta. Se vuoi che il repository Git contenga queste informazioni, puoi escludere file specifici dall'ignorare (cioè aggiungere !*.egg-info/PKG_INFOa .gitignore)

Accesso alla versione da uno script

Puoi accedere ai metadati dalla build corrente all'interno degli script Python nel pacchetto stesso. Per la versione, ad esempio, ci sono diversi modi per farlo che ho trovato finora:

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

Puoi inserire uno di questi direttamente nel tuo __init__.pypacchetto per estrarre le informazioni sulla versione come segue, in modo simile ad altre risposte:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version

Riformattato per voto negativo per rispondere direttamente alla domanda ora.
LightCC

1

Dopo diverse ore di ricerca della soluzione più semplice e affidabile, ecco le parti:

crea un file version.py ALL'INTERNO della cartella del tuo pacchetto "/ 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__,

nella cartella principale init .py:

from .version import __version__

La exec()funzione esegue lo script al di fuori di qualsiasi importazione, poiché setup.py viene eseguito prima che il modulo possa essere importato. Devi solo gestire il numero di versione in un file in un unico posto, ma sfortunatamente non è in setup.py. (questo è il rovescio della medaglia, ma non avere bug di importazione è il lato positivo)


1

Molto lavoro verso il controllo uniforme delle versioni e a supporto delle convenzioni è stato completato da quando questa domanda è stata posta per la prima volta . Le opzioni appetibili sono ora dettagliate nella Guida per l'utente di Python Packaging . È anche degno di nota il fatto che gli schemi dei numeri di versione sono relativamente rigidi in Python per PEP 440 , e quindi mantenere le cose sane è fondamentale se il tuo pacchetto verrà rilasciato al Cheese Shop .

Ecco una suddivisione abbreviata delle opzioni di versioning:

  1. Leggi il file in setup.py( setuptools ) e ottieni la versione.
  2. Utilizzare uno strumento di generazione esterno (per aggiornare sia __init__.pyil controllo del codice sorgente), ad esempio bump2version , modifiche o zest.releaser .
  3. Imposta il valore su una __version__variabile globale in un modulo specifico.
  4. Inserire il valore in un semplice file di testo VERSION affinché sia ​​setup.py sia il codice da leggere.
  5. Imposta il valore tramite una setup.pyversione e usa importlib.metadata per raccoglierlo in fase di esecuzione. (Attenzione, ci sono versioni pre-3.8 e post-3.8.)
  6. Impostare il valore su __version__in sample/__init__.pye importare sample in setup.py.
  7. Usa setuptools_scm per estrarre il controllo delle versioni dal controllo del codice sorgente in modo che sia il riferimento canonico, non il codice.

NOTA (7) potrebbe essere l' approccio più moderno (build metadata è indipendente dal codice, pubblicato dall'automazione). Si noti inoltre che se l'installazione viene utilizzata per il rilascio del pacchetto, un semplice python3 setup.py --versionriporterà direttamente la versione.


-1

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


Potete fornire maggiori dettagli o un esempio di come funzionerebbe?
Stevoisiak,

Hmm. Nel 2020, questo è (è sempre stato?) Destinato a FORTRAN . Il pacchetto "numpy.distutils fa parte di NumPy che estende le distutils Python standard per gestire le fonti Fortran".
ingyhere

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

Dal momento 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 version=<VERSION>al tuo setup.pyfile 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 .bumpversion.cfg::

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

Nota:

  • È possibile utilizzare il __version__parametro nel version.pyfile come è stato suggerito in altri post e aggiornare il file bumpversion in questo modo: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • È necessario git commit o git resettutto il resto del repository, altrimenti si otterrà un errore repository sporco.
  • Assicurati che il tuo ambiente virtuale includa il pacchetto di bumpversion, senza di esso non funzionerà.

@cmcginty Ci scusiamo per il ritardo, controlla la mia risposta ^^^ - nota che devi git commit o git resettare tutto nel tuo repository e assicurarti che il tuo ambiente virtuale includa il pacchetto di bumpversion, senza di esso non funzionerà. Usa l'ultima versione.
Oran,

Non sono abbastanza chiaro su quale soluzione venga proposta qui. Stai raccomandando il monitoraggio manuale della versione version.pyo il monitoraggio con bumpversion?
Stevoisiak,

@StevenVascellaro Sto suggerendo di usare bumpversion, non usare mai il versioning manuale. Quello che ho cercato di spiegare è che puoi indirizzare bumpversion per aggiornare la versione nel file setup.py o meglio ancora usarla per aggiornare il file version.py. È pratica più comune aggiornare il file version.py e prendere il __version__valore param nel file setup.py. La mia soluzione è utilizzata in produzione ed è una pratica comune. Nota: solo per essere chiari, usare bumpversion come parte di uno script è la soluzione migliore, inseriscilo nel tuo elemento della configurazione e sarà un'operazione automatica.
Oran,

-3

Se usi CVS (o RCS) e desideri una soluzione rapida, puoi utilizzare:

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

(Naturalmente, il numero di revisione verrà sostituito per te da 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)

In quale file stai raccomandando di salvare il salvataggio __version__? Come incrementare il numero di versione con questa soluzione?
Stevoisiak,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.