Comment pourrais-je obtenir la version définie dans setup.py
de mon paquet (pour --version
, ou à d’autres fins)?
Pour récupérer la version de votre package au moment de l'exécution (ce que votre question semble réellement demander), vous pouvez utiliser:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Si vous voulez aller dans l'autre sens (ce qui semble être ce que d'autres auteurs de la réponse semblent avoir pensé que vous posiez la question), placez la chaîne de version dans un fichier séparé et lisez le contenu de ce fichier dans setup.py
.
Vous pouvez créer un fichier version.py dans votre paquet avec une ligne __version__
, Puis le lire à partir de setup.py en utilisant execfile('mypackage/version.py')
, de manière à définir __version__
Dans la configuration. py espace de noms.
Si vous voulez un moyen beaucoup plus simple qui fonctionnera avec toutes les versions de Python) et même les langages non Python pouvant nécessiter un accès à la chaîne de version:
Stockez la chaîne de version en tant que contenu unique d’un fichier texte brut, nommé par exemple. VERSION
et lisez ce fichier pendant setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
Le même fichier VERSION
fonctionnera alors exactement de la même manière dans tout autre programme, même non-Python, et il vous suffira de changer la chaîne de version en un seul endroit pour tous les programmes.
En passant, NE PAS importer votre paquet depuis votre fichier setup.py comme suggéré dans une autre réponse: cela semblera fonctionner pour vous (parce que les dépendances de votre paquet sont déjà installées), mais cela fera des ravages chez les nouveaux utilisateurs de votre paquet. , car ils ne pourront pas installer votre paquet sans installer d'abord manuellement les dépendances.
mymodule
Imaginez cette configuration:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Puis imaginez un scénario habituel dans lequel vous avez des dépendances et où setup.py
Ressemble à ceci:
setup(...
install_requires=['dep1','dep2', ...]
...)
Et un exemple __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
Et par exemple myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
lors de l'installationSi votre setup.py
Importe mymodule
alors pendant l'installation, vous obtiendrez probablement un ImportError
. C'est une erreur très courante lorsque votre paquet a des dépendances. Si votre paquet n'a pas d'autres dépendances que les commandes intégrées, vous pouvez être en sécurité; Cependant, ce n'est pas une bonne pratique. La raison en est que cela n’est pas à l’avenir; dites demain que votre code doit consommer une autre dépendance.
__version__
?Si vous codez en dur __version__
Dans setup.py
, Il se peut que sa version ne corresponde pas à celle que vous auriez livrée dans votre module. Pour être cohérent, vous le placeriez au même endroit et le liriez au même endroit quand vous en auriez besoin. En utilisant import
, vous pouvez obtenir le problème n ° 1.
setuptools
Vous utiliseriez une combinaison de open
, exec
et fournissez un dict pour exec
afin d'ajouter des variables:
# 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__'],
...)
Et dans mymodule/version.py
Exposer la version:
__version__ = 'some.semantic.version'
De cette manière, la version est livrée avec le module et vous ne rencontrez aucun problème lors de l'installation lorsque vous essayez d'importer un module comportant des dépendances manquantes (à installer).
La meilleure technique consiste à définir __version__
dans votre code produit, puis importez-le dans setup.py à partir de là. Cela vous donne une valeur que vous pouvez lire dans votre module en cours d’exécution et n’a qu’un seul emplacement pour la définir.
Les valeurs de setup.py ne sont pas installées et setup.py ne reste pas après l'installation.
Qu'est-ce que j'ai fait (par exemple) dans coverage.py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
UPDATE (2017): coverage.py ne s'importe plus pour obtenir la version. L'importation de votre propre code peut le rendre désinstallable, car votre code produit essaiera d'importer des dépendances, qui ne sont pas encore installées, car c'est setup.py qui les installe.
Votre question est un peu vague, mais je pense que ce que vous demandez, c'est comment la spécifier.
Vous devez définir __version__
ainsi:
__version__ = '1.4.4'
Et vous pourrez alors confirmer que setup.py connaît la version que vous venez de spécifier:
% ./setup.py --version
1.4.4
Je n'étais pas satisfait de ces réponses ... je ne voulais pas exiger de setuptools, ni créer un module séparé pour une seule variable, alors je les ai conçues.
Pour quand vous êtes sûr que le module principal est dans le style pep8 et le restera:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Si vous souhaitez être particulièrement prudent et utiliser un véritable analyseur syntaxique:
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 est un peu un module jetable, donc ce n’est pas un problème s’il est un peu laid.
Créez un fichier dans votre arborescence source, par exemple. dans yourbasedir/yourpackage/_version.py. Laissez ce fichier ne contenir qu'une seule ligne de code, comme ceci:
__version__ = "1.1.0-r4704"
Ensuite, dans votre setup.py, ouvrez ce fichier et analysez le numéro de version comme ceci:
verstr = "unknown" essayez: verstrline = open ('yourpackage/_version.py', "rt"). read () sauf EnvironmentError: pass # OK, il n’existe pas de fichier de version. sinon: VSRE = r "^ __ version__ = ['\"] ([^'\"] *) ['\"]" mo = re.search (VSRE, verstrline, re.M) si mo: verstr = mo.group (1) sinon : raise RuntimeError ("impossible de trouver la version dans votre package/_version.py")
Enfin, dans yourbasedir/yourpackage/__init__.py
, Importez la version _ comme ceci:
__ version__ = "unknown" essayez: à partir de _version import __version __ sauf ImportError: # Nous courons dans un arbre qui ne fonctionne pas. t ont une _version.py, nous ne savons donc pas quelle est notre version. pass
Un exemple de code qui fait cela est le paquet "pyutil" que je gère. (Voir PyPI ou recherche Google - stackoverflow ne m'autorise pas à inclure un lien hypertexte dans cette réponse.)
@pjeby a raison de ne pas importer votre paquet depuis son propre fichier setup.py. Cela fonctionnera si vous le testez en créant un nouvel interpréteur Python et en exécutant setup.py: python setup.py
, Mais il y a des cas où cela ne fonctionnera pas. C’est parce que import youpackage
Ne veut pas dire lire le répertoire de travail actuel d’un répertoire nommé "yourpackage", cela signifie chercher dans le sys.modules
Actuel une clé "yourpackage", puis faire divers Cela fonctionne toujours quand vous faites python setup.py
parce que vous avez un nouveau sys.modules
vide, mais cela ne fonctionne pas en général.
Par exemple, que se passe-t-il si py2exe exécute votre fichier setup.py dans le cadre du processus de création d’une application? J'ai vu un cas comme celui-ci où py2exe mettrait le mauvais numéro de version sur un paquet car le paquet recevait son numéro de version de import myownthing
Dans son fichier setup.py, mais une version différente de ce paquet avait été précédemment importé lors de l'exécution de py2exe. De même, que se passe-t-il si setuptools, easy_install, distribution ou distutils2 essaie de construire votre paquet dans le cadre d’un processus d’installation d’un autre paquet qui dépend du vôtre? Ensuite, que votre paquet soit importable au moment de l’évaluation de son fichier setup.py ou qu’une version de votre paquet ait déjà été importée au cours de cette Python, ou votre package requiert l'installation préalable d'autres packages, ou bien ses effets secondaires peuvent modifier les résultats. Plusieurs tentatives pour réutiliser Python qui posaient des problèmes pour des outils tels que py2exe et setuptools car leur fichier setup.py importe le paquet lui-même afin de trouver son numéro de version.
En passant, cette technique fonctionne bien avec les outils permettant de créer automatiquement le fichier yourpackage/_version.py
, Par exemple en lisant votre historique de contrôle de révision et en écrivant un numéro de version basé sur la balise la plus récente de l'historique de contrôle de révision. Voici un outil qui fait cela pour darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst et voici un extrait de code qui fait la même chose pour git: http://github.com/warner/python-ecdsa/blob/0ed702a9d4057ecf33eea969b8cf280eaccd89a1/setup.py#L34
Avec une structure comme celle-ci:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
où version.py contient:
__version__ = 'version_string'
Vous pouvez le faire dans setup.py:
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Cela ne posera aucun problème avec les dépendances que vous avez dans votre mymodule/__ init __. Py
Cela devrait également fonctionner, en utilisant des expressions régulières et en fonction des champs de métadonnées pour avoir un format comme celui-ci:
__field= 'value'
Utilisez ce qui suit au début de votre setup.py:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Après cela, vous pouvez utiliser les métadonnées de votre script comme ceci:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Pour éviter d'importer un fichier (et donc d'exécuter son code), on pourrait l'analyser et récupérer l'attribut version
à partir de l'arbre de syntaxe:
# 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
Ce code tente de trouver le __version__
ou VERSION
L'affectation au niveau supérieur du retour du module est une valeur chaîne. Le côté droit peut être une chaîne ou un tuple.
Il y a mille façons de peler un chat - voici le mien:
# 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.")
Nettoyage https://stackoverflow.com/a/124138 de @ 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
Nous voulions mettre les méta-informations sur notre paquet pypackagery
dans __init__.py
, mais n'a pas pu, car il existe des dépendances tierces, comme l'a déjà souligné PJ Eby (voir sa réponse et l'avertissement relatif à la situation de concurrence critique).
Nous l'avons résolu en créant un module séparé pypackagery_meta.py
qui contient seulement la méta-information:
"""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__ = '[email protected]'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'
puis importé la méta-information dans packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
et finalement utilisé dans 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'],
)
Vous devez inclure pypackagery_meta
dans votre paquet avec py_modules
argument de configuration. Sinon, vous ne pourrez pas l'importer lors de l'installation car il en manquerait dans la distribution empaquetée.
Maintenant, c’est brutal et a besoin d’être peaufiné (il se peut même qu’un appel de membre non couvert dans pkg_resources que j’ai manqué), mais je ne vois tout simplement pas pourquoi cela ne fonctionne pas, ni pourquoi personne ne l’a suggéré à ce jour (googler around has a pas vu cela) ... notez qu'il s'agit de Python 2.x, et nécessiterait l'exigence de 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
Simple et direct, créez un fichier nommé source/package_name/version.py
avec le contenu suivant:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Ensuite, sur votre dossier source/package_name/__init__.py
, vous importez la version à l’intention des autres utilisateurs:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Maintenant, vous pouvez mettre ceci sur 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()
Testé cela avec Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
et 3.7
sur Linux, Windows et Mac OS. J'ai utilisé sur mon paquet qui a l'intégration et les tests unitaires pour toutes ces plateformes. Vous pouvez voir les résultats de .travis.yml
et appveyor.yml
ici:
Une autre version utilise le gestionnaire de contexte:
#!/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 ) )
Vous pouvez également utiliser le module codecs
pour gérer les erreurs Unicode à la fois sur Python 2.7
et 3.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 ) )
Si vous écrivez un Python à 100% en C/C++ à l'aide de Python C, vous pouvez faire la même chose, mais en utilisant C/C++ à la place de Python.
Dans ce cas, créez le 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'],
)
],
)
Ce qui lit la version du fichier version.h
:
const char* __version__ = "1.0.12";
Mais n'oubliez pas de créer le MANIFEST.in
pour inclure le version.h
fichier:
include README.md
include LICENSE.txt
recursive-include source *.h
Et il est intégré à l'application principale avec:
#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__ ) );
...
}
Les références: