Risposte:
Per recuperare la versione dall'interno del pacchetto in fase di runtime (ciò che la domanda sembra effettivamente porre), è possibile utilizzare:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Se vuoi fare il contrario (che sembra essere quello che altri autori della risposta sembrano aver pensato che stavi chiedendo), metti la stringa della versione in un file separato e leggi il contenuto di quel file setup.py
.
È possibile creare un version.py nel pacchetto con una __version__
riga, quindi leggerlo da setup.py utilizzando execfile('mypackage/version.py')
, in modo che sia impostato __version__
nello spazio dei nomi setup.py.
Se vuoi un modo molto più semplice che funzioni con tutte le versioni di Python e anche con i linguaggi non Python che potrebbero aver bisogno di accedere alla stringa di versione:
Memorizza la stringa di versione come unico contenuto di un file di testo semplice, chiamato ad esempio VERSION
, e leggi quel file durante setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
Lo stesso VERSION
file funzionerà quindi esattamente anche in qualsiasi altro programma, anche in quelli non Python, e dovrai solo cambiare la stringa di versione in un posto per tutti i programmi.
A proposito, NON importare il pacchetto dal tuo setup.py come suggerito in un'altra risposta qui: sembrerà funzionare per te (perché hai già installato le dipendenze del pacchetto), ma causerà il caos sui nuovi utenti del pacchetto , poiché non saranno in grado di installare il pacchetto senza prima installare manualmente le dipendenze.
execfile
funziona davvero bene ... ma (purtroppo) non funziona con Python 3.
with open('mypackage/version.py') as f: exec(f.read())
al posto di execfile('mypackage/version.py')
. (Da stackoverflow.com/a/437857/647002 )
mymodule
Immagina questa configurazione:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Quindi immagina uno scenario abituale in cui hai dipendenze e setup.py
assomiglia a:
setup(...
install_requires=['dep1','dep2', ...]
...)
E un esempio __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
E per esempio myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
durante l'installazioneSe i vostri setup.py
importazioni mymodule
allora durante l'installazione si sarebbe molto probabilmente ottenere una ImportError
. Questo è un errore molto comune quando il pacchetto ha dipendenze. Se il tuo pacchetto non ha dipendenze diverse dai builtin, potresti essere al sicuro; tuttavia questa non è una buona pratica. La ragione di ciò è che non è a prova di futuro; dì domani che il tuo codice deve consumare qualche altra dipendenza.
__version__
?Se hardcode __version__
in setup.py
allora non può corrispondere alla versione che si sarebbe spedito in modulo. Per essere coerenti, lo metti in un posto e lo leggi dallo stesso posto quando ne hai bisogno. L'utilizzo import
potrebbe causare il problema n. 1.
setuptools
Dovresti usare una combinazione di open
, exec
e fornire un comando per exec
aggiungere variabili:
# setup.py
from setuptools import setup, find_packages
from distutils.util import convert_path
main_ns = {}
ver_path = convert_path('mymodule/version.py')
with open(ver_path) as ver_file:
exec(ver_file.read(), main_ns)
setup(...,
version=main_ns['__version__'],
...)
E in mymodule/version.py
esporre la versione:
__version__ = 'some.semantic.version'
In questo modo, la versione viene fornita con il modulo e non si verificano problemi durante l'installazione durante il tentativo di importare un modulo con dipendenze mancanti (ancora da installare).
La tecnica migliore consiste nel definire __version__
il codice del prodotto, quindi importarlo in setup.py da lì. Questo ti dà un valore che puoi leggere nel tuo modulo in esecuzione e avere un solo posto per definirlo.
I valori in setup.py non sono installati e setup.py non si attacca dopo l'installazione.
Cosa ho fatto (ad esempio) in coverage.py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
AGGIORNAMENTO (2017): coverage.py non si importa più per ottenere la versione. L'importazione del proprio codice può renderlo non installabile, poiché il codice del prodotto proverà a importare dipendenze, che non sono ancora state installate, poiché setup.py è ciò che le installa.
__version__
in esso è rotto in qualche modo, anche l'importazione verrà interrotta. Python non sa come interpretare solo le affermazioni che desideri. @pjeby ha ragione: se il tuo modulo deve importare altri moduli, potrebbero non essere ancora installati e sarà caotico. Questa tecnica funziona se stai attento che l'importazione non induca una lunga catena di altre importazioni.
pip install <packagename>
, ricevo il seguente errore: ImportError: No module named <packagename>
. AVVISO ai lettori che non è possibile eseguire file setup.py come questo in ambienti in cui il pacchetto non è già installato!
Non ero contento di queste risposte ... non volevo richiedere setuptools, né creare un modulo completamente separato per una singola variabile, quindi ho pensato a queste.
Per quando sei sicuro che il modulo principale sia in stile pep8 e rimarrà tale:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Se desideri essere molto attento e utilizzare un vero parser:
import ast
version = '0.30.unknown2'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
version = ast.parse(line).body[0].value.s
break
setup.py è in qualche modo un modulo usa e getta, quindi non è un problema se è un po 'brutto.
Aggiornamento: abbastanza divertente, mi sono allontanato da questo negli ultimi anni e ho iniziato a usare un file separato nel pacchetto chiamato meta.py
. Ho inserito molti metadati che potrei voler cambiare frequentemente. Quindi, non solo per un valore.
ast.get_docstring()
con alcuni .split('\n')[0].strip()
etc per compilare automaticamente description
dalla fonte. Una cosa in meno da mantenere sincronizzata
Crea un file nella tua struttura di origine, ad esempio in yourbasedir / yourpackage / _version.py. Lascia che quel file contenga solo una singola riga di codice, in questo modo:
__version__ = "1.1.0-r4704"
Quindi in setup.py, apri quel file e analizza il numero di versione in questo modo:
verstr = "sconosciuto" provare: verstrline = open ('yourpackage / _version.py', "rt"). read () tranne EnvironmentError: pass # Va bene, non esiste un file di versione. altro: VSRE = r "^ __ version__ = ['\"] ([^' \ "] *) ['\"] " mo = re.search (VSRE, verstrline, re.M) se mo: verstr = mo.group (1) altro: raise RuntimeError ("impossibile trovare la versione nel tuo pacchetto / _version.py")
Infine, in yourbasedir/yourpackage/__init__.py
import _version in questo modo:
__version__ = "sconosciuto" provare: da _version import __version__ tranne ImportError: # Stiamo correndo su un albero che non ha _version.py, quindi non sappiamo quale sia la nostra versione. passaggio
Un esempio di codice che fa questo è il pacchetto "pyutil" che mantengo. (Vedi PyPI o ricerca su Google - StackOverflow non mi consente di includere un collegamento ipertestuale ad esso in questa risposta.)
@pjeby ha ragione a non importare il pacchetto dal proprio setup.py. Funzionerà quando lo testerai creando un nuovo interprete Python ed eseguendo setup.py in primo luogo:, python setup.py
ma ci sono casi in cui non funzionerà. Questo perché import youpackage
non significa leggere l'attuale directory di lavoro per una directory denominata "yourpackage", significa cercare nella corrente sys.modules
una chiave "yourpackage" e quindi fare varie cose se non è lì. Quindi funziona sempre quando lo fai python setup.py
perché hai un nuovo, vuoto sys.modules
, ma questo non funziona in generale.
Ad esempio, cosa succede se py2exe sta eseguendo setup.py come parte del processo di creazione del pacchetto di un'applicazione? Ho visto un caso come questo in cui py2exe inseriva il numero di versione errato in un pacchetto perché il pacchetto stava ottenendo il suo numero di versione daimport myownthing
in setup.py, ma una versione diversa di quel pacchetto era stata precedentemente importata durante l'esecuzione di py2exe. Allo stesso modo, cosa succede se setuptools, easy_install, distribut o distutils2 sta cercando di compilare il pacchetto come parte di un processo di installazione di un pacchetto diverso che dipende dal tuo? Quindi se il tuo pacchetto è impossibile al momento della valutazione di setup.py, o se esiste già una versione del pacchetto che è stata importata durante la vita di questo interprete Python o se importare il pacchetto richiede prima l'installazione di altri pacchetti o ha effetti collaterali, può modificare i risultati. Ho avuto diverse difficoltà nel provare a riutilizzare i pacchetti Python che hanno causato problemi a strumenti come py2exe e setuptools perché il loro setup.py importa il pacchetto stesso per trovare il suo numero di versione.
A proposito, questa tecnica si adatta perfettamente agli strumenti per creare automaticamente il yourpackage/_version.py
file, ad esempio leggendo la cronologia dei controlli di revisione e scrivendo un numero di versione basato sul tag più recente nella cronologia dei controlli di revisione. Ecco uno strumento che lo fa per darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst ed ecco uno snippet di codice che fa la stessa cosa per git: http: // github .com / Warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Questo dovrebbe anche funzionare, usando espressioni regolari e in base ai campi dei metadati per avere un formato come questo:
__fieldname__ = 'value'
Utilizzare quanto segue all'inizio di setup.py:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Successivamente, puoi utilizzare i metadati nel tuo script in questo modo:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Con una struttura come questa:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
dove version.py contiene:
__version__ = 'version_string'
Puoi farlo in setup.py :
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Ciò non causerà alcun problema con le dipendenze che hai nel tuo mymodule / __ init__.py
Per evitare di importare un file (e quindi eseguire il suo codice) è possibile analizzarlo e recuperare l' version
attributo dall'albero della sintassi:
# assuming 'path' holds the path to the file
import ast
with open(path, 'rU') as file:
t = compile(file.read(), path, 'exec', ast.PyCF_ONLY_AST)
for node in (n for n in t.body if isinstance(n, ast.Assign)):
if len(node.targets) == 1:
name = node.targets[0]
if isinstance(name, ast.Name) and \
name.id in ('__version__', '__version_info__', 'VERSION'):
v = node.value
if isinstance(v, ast.Str):
version = v.s
break
if isinstance(v, ast.Tuple):
r = []
for e in v.elts:
if isinstance(e, ast.Str):
r.append(e.s)
elif isinstance(e, ast.Num):
r.append(str(e.n))
version = '.'.join(r)
break
Questo codice tenta di trovare l' assegnazione __version__
o VERSION
al livello superiore del ritorno del modulo è il valore di stringa. Il lato destro può essere una stringa o una tupla.
Esistono mille modi per scuoiare un gatto - ecco il mio:
# Copied from (and hacked):
# https://github.com/pypa/virtualenv/blob/develop/setup.py#L42
def get_version(filename):
import os
import re
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, filename))
version_file = f.read()
f.close()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
Pulizia https://stackoverflow.com/a/12413800 da @ gringo-suave:
from itertools import ifilter
from os import path
from ast import parse
with open(path.join('package_name', '__init__.py')) as f:
__version__ = parse(next(ifilter(lambda line: line.startswith('__version__'),
f))).body[0].value.s
Ora questo è disgustoso e necessita di un po 'di raffinamento (potrebbe esserci persino una chiamata di un membro scoperto in pkg_resources che ho perso), ma semplicemente non vedo perché non funziona, né perché nessuno lo abbia suggerito fino ad oggi non acceso) ... nota che si tratta di Python 2.xe richiederebbe pkg_resources (sigh):
import pkg_resources
version_string = None
try:
if pkg_resources.working_set is not None:
disto_obj = pkg_resources.working_set.by_key.get('<my pkg name>', None)
# (I like adding ", None" to gets)
if disto_obj is not None:
version_string = disto_obj.version
except Exception:
# Do something
pass
Abbiamo voluto mettere le meta-informazioni sul nostro pacchetto pypackagery
in __init__.py
, ma non poteva in quanto ha le dipendenze di terze parti come PJ Eby già sottolineato (vedi la sua risposta e l'avviso per quanto riguarda la condizione di competizione).
Lo abbiamo risolto creando un modulo separato pypackagery_meta.py
che contiene solo le meta informazioni:
"""Define meta information about pypackagery package."""
__title__ = 'pypackagery'
__description__ = ('Package a subset of a monorepo and '
'determine the dependent packages.')
__url__ = 'https://github.com/Parquery/pypackagery'
__version__ = '1.0.0'
__author__ = 'Marko Ristin'
__author_email__ = 'marko.ristin@gmail.com'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'
quindi importato le meta informazioni in packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
e finalmente l'ho usato in setup.py
:
import pypackagery_meta
setup(
name=pypackagery_meta.__title__,
version=pypackagery_meta.__version__,
description=pypackagery_meta.__description__,
long_description=long_description,
url=pypackagery_meta.__url__,
author=pypackagery_meta.__author__,
author_email=pypackagery_meta.__author_email__,
# ...
py_modules=['packagery', 'pypackagery_meta'],
)
È necessario includere pypackagery_meta
nel pacchetto con l' py_modules
argomento setup. Altrimenti, non è possibile importarlo al momento dell'installazione poiché la distribuzione impacchettata mancherebbe.
Semplice e diretto, crea un file chiamato source/package_name/version.py
con i seguenti contenuti:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Quindi, sul tuo file source/package_name/__init__.py
, importi la versione che altre persone possono usare:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Ora puoi metterlo setup.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
version_file = open( filepath )
__version__ ,= re.findall( '__version__ = "(.*)"', version_file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
finally:
version_file.close()
Testato questo con Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
e 3.7
su Linux, Windows e Mac OS. Ho usato sul mio pacchetto che ha Test di integrazione e unità per tutte queste piattaforme. Puoi vedere i risultati da .travis.yml
e appveyor.yml
qui:
Una versione alternativa utilizza il gestore di contesto:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
with open( filepath ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
Puoi anche utilizzare il codecs
modulo per gestire gli errori Unicode sia su Python 2.7
che su3.6
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
try:
filepath = 'source/package_name/version.py'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
Se stai scrivendo un modulo Python al 100% in C / C ++ usando le estensioni Python C, puoi fare la stessa cosa, ma usando C / C ++ invece di Python.
In questo caso, creare quanto segue setup.py
:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
from setuptools import setup, Extension
try:
filepath = 'source/version.h'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
setup(
name = 'package_name',
version = __version__,
package_data = {
'': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
},
ext_modules = [
Extension(
name = 'package_name',
sources = [
'source/file.cpp',
],
include_dirs = ['source'],
)
],
)
Che legge la versione dal file version.h
:
const char* __version__ = "1.0.12";
Ma non dimenticare di creare MANIFEST.in
per includere il version.h
file:
include README.md
include LICENSE.txt
recursive-include source *.h
Ed è integrato nell'applicazione principale con:
#include <Python.h>
#include "version.h"
// create the module
PyMODINIT_FUNC PyInit_package_name(void)
{
PyObject* thismodule;
...
// https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue
PyObject_SetAttrString( thismodule, "__version__", Py_BuildValue( "s", __version__ ) );
...
}
Riferimenti:
distribuire pacchetto su server e convenzione di denominazione dei file per i pacchetti di indici:
esempio per la conversione della versione dinamica pip:
vincere:
Mac:
from setuptools_scm import get_version
def _get_version():
dev_version = str(".".join(map(str, str(get_version()).split("+")[0]\
.split('.')[:-1])))
return dev_version
Trova l'esempio setup.py chiama la versione pip dinamica corrispondente da git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Sto usando una variabile d'ambiente come di seguito
VERSIONE = 0.0.0 python setup.py sdist bdist_wheel
In setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Per un controllo di coerenza con la versione del packer, sto usando lo script seguente.
PKG_VERSION=`python -c "import pkg; print(pkg.__version__)"`
if [ $PKG_VERSION == $VERSION ]; then
python setup.py sdist bdist_wheel
else
echo "Package version differs from set env variable"
fi