Méthode Standard pour intégrer une version dans un paquet python?
y a-t-il une façon standard d'associer une chaîne de version à un paquet python de telle manière que je puisse faire ce qui suit?
import foo
print foo.version
j'imagine qu'il y a un moyen de récupérer ces données sans aucun codage supplémentaire, puisque les chaînes mineures/majeures sont déjà spécifiées dans setup.py
. La solution Alternative que j'ai trouvée était d'avoir import __version__
dans mon foo/__init__.py
et ensuite avoir __version__.py
généré par setup.py
.
14 réponses
pas directement une réponse à votre question, mais vous devriez envisager de l'appeler __version__
, pas version
.
c'est presque une quasi-norme. Beaucoup de modules dans la bibliothèque standard utilisent __version__
, et cela est également utilisé dans lots de modules tiers, donc c'est le quasi-standard.
habituellement, __version__
est une chaîne, mais parfois c'est aussi un flotteur ou un tuple.
modifier: comme mentionné par S. Lott (Merci!!!!), PEP 8 le dit explicitement:
Version Comptabilité
si vous devez avoir Subversion, CVS ou RCS crud dans votre fichier source, faire comme suit.
__version__ = "$Revision: 63990 $" # $Source$
ces lignes doivent être incluses après docstring du module, avant tout autre code, séparés par une ligne vide au-dessus et au-dessous.
vous devez aussi faire s'assurer que le numéro de version est conforme au format décrit dans PEP 440 ( PEP 386 une version précédente de cette norme).
j'utilise un seul fichier _version.py
pour stocker les informations de version:
-
il fournit un attribut
__version__
. -
Il fournit à la norme de métadonnées version. Il sera donc détecté par
pkg_resources
ou d'autres outils qui analysent les métadonnées du paquet (EGG-INFO et/ou PKG-INFO, PEP 0345). -
It n'importe pas votre paquet (ou quoi que ce soit d'autre) lors de la construction de votre paquet, ce qui peut causer des problèmes dans certaines situations. (Voir les commentaires ci-dessous sur les problèmes que cela peut causer.)
-
il n'y a qu'un seul endroit où le numéro de version est écrit, donc il n'y a qu'un seul endroit pour le changer lorsque le numéro de version change, et il y a moins de chances de versions incohérentes.
Voici comment il works: le" one canonical place " pour stocker le numéro de version est A. fichier py, nommé "_version.py" qui est dans votre paquet Python, par exemple dans myniftyapp/_version.py
. Ce fichier est un module Python, mais votre setup.py il ne l'importe pas! (Cela irait à l'encontre de la caractéristique 3.) À la place de votre setup.py sait que le contenu de ce fichier est très simple, quelque chose comme:
__version__ = "3.6.5"
et donc votre setup.py ouvre le fichier et le parse, avec un code comme:
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,))
puis votre setup.py passe cette chaîne comme valeur de l'argument "version" à setup()
, satisfaisant ainsi la caractéristique 2.
pour satisfaire la fonctionnalité 1, vous pouvez avoir votre paquet (à l'exécution, pas à l'installation!) importer le fichier _version de myniftyapp/__init__.py
comme ceci:
from _version import __version__
Voici un exemple de cette technique que j'utilise depuis des années.
le code dans cet exemple est peu plus compliqué, mais l'exemple simplifié que j'ai écrit dans ce commentaire doit être une mise en œuvre complète.
Voici exemple de code d'importation de la version .
si vous voyez quelque chose de mal avec cette approche, s'il vous plaît faites le moi savoir.
réécrit 2017-05
après plus de dix ans d'écriture de code Python et de gestion de différents paquets, je suis arrivé à la conclusion que DIY n'est peut-être pas la meilleure approche.
j'ai commencé à utiliser le paquet pbr
pour gérer les versions de mes paquets. Si vous utilisez git comme SCM, cela s'intégrera dans votre flux de travail comme par magie, vous économisant ainsi vos semaines de travail (vous serez surpris de la complexité du problème).
en date d'aujourd'hui pbr est classé #11 paquet python le plus utilisé et atteindre ce niveau n'a pas inclus De Sales trucs: était seulement un: la réparation d'un problème d'Emballage commun d'une manière très simple.
pbr
peut faire plus de la charge de maintenance du paquet, n'est pas limité à la version mais il ne vous oblige pas à adopter tous ses avantages.
donc pour vous donner une idée de comment il semble adopter pbr en un seul commit ont un look swiching emballage de droits d'obtenteur
Probablement vous observé que la version n'est pas stocké dans le repository. PBR le détecte à partir des branches et des étiquettes Git.
pas besoin de s'inquiéter de ce qui se passe lorsque vous n'avez pas de dépôt git parce que pbr" compile " et cache la version lorsque vous empaquetez ou installez les applications, donc il n'y a pas de dépendance d'exécution sur git.
Vieux solution
Voici la meilleure solution que j'ai vu jusqu'à présent et il explique aussi pourquoi:
à l'Intérieur 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'
à l'Intérieur yourpackage/__init__.py
:
from .version import __version__
à l'Intérieur setup.py
:
exec(open('yourpackage/version.py').read())
setup(
...
version=__version__,
...
si vous connaissez une autre approche, faites-le moi savoir.
selon la version différée PEP 396 (numéros de version des modules) , il y a un moyen proposé de le faire. Il décrit, avec justification, une norme (certes facultative) pour les modules à suivre. Voici un extrait:
3) Lorsqu'un module (ou un paquet) comprend un numéro de version, la version doit être disponible dans l'attribut
__version__
.4) Pour les modules qui vivent à l'intérieur d'un paquet namespace, le module Doit inclure l'attribut
__version__
. Le paquet namespace lui-même ne devrait pas inclure son propre attribut__version__
.5) La valeur de l'attribut
__version__
devrait être une chaîne de caractères.
bien que ce soit probablement beaucoup trop tard, il existe une alternative légèrement plus simple à la réponse précédente:
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(et il serait assez simple de convertir des parties Auto-incrémentantes de numéros de version en une chaîne de caractères en utilisant str()
.)
bien sûr, d'après ce que j'ai vu, les gens ont tendance à utiliser quelque chose comme la version mentionnée précédemment en utilisant __version_info__
, et en tant que tel, stocker comme un tuple d'ints; cependant, je ne suis pas tout à fait voir le point en faisant ainsi, comme je doute qu'il y ait des situations où vous effectueriez des opérations mathématiques telles que l'addition et la soustraction sur des portions de numéros de version pour n'importe quelle fin en dehors de la curiosité ou de l'auto-incrémentation (et même alors, int()
et str()
peut être utilisé assez facilement). (D'un autre côté, il y a la possibilité de quelqu'un d'autre code attend numérique tuple plutôt qu'une chaîne n-uplet et donc défaut.)
c'est, bien sûr, mon j'aimerais que les autres donnent leur avis sur l'utilisation d'un tuple numérique.
comme shezi me l'a rappelé, les comparaisons (lexicales) de chaînes de nombres n'ont pas nécessairement le même résultat que les comparaisons numériques directes; des zéros de tête seraient nécessaires pour prévoir cela. Ainsi, en fin de compte, le stockage de __version_info__
(ou quel que soit son nom) en tant que tuple de valeurs entières permettrait des comparaisons de version plus efficaces.
j'utilise un fichier JSON dans le paquet dir. Cela correspond Zooko.
à l'Intérieur pkg_dir/pkg_info.json
:
{"version": "0.1.0"}
à l'Intérieur 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'],
...
)
à l'Intérieur 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']
j'ai aussi mis d'autres informations dans pkg_info.json
, comme l'auteur. Je
j'aime utiliser JSON parce que je peux automatiser la gestion des métadonnées.
il ne semble pas y avoir de méthode standard pour intégrer une chaîne de version dans un paquet python. La plupart des paquets que j'ai vu utilisent une variante de votre solution, i.e. eitner
-
intégrer la version dans
setup.py
et avoirsetup.py
générer un module (par exempleversion.py
) contenant seulement des informations de version, qui est importé par votre paquet, ou -
L'inverse: mettre les informations de version dans votre forfait elle-même, et d'importer que de définir la version dans
setup.py
vaut également la peine de noter est que, ainsi que __version__
étant un semi-std. en python est donc __version_info__
qui est un tuple, dans les cas simples, vous pouvez juste faire quelque chose comme:
__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])
...et vous pouvez obtenir la chaîne __version__
à partir d'un fichier, ou autre.
beaucoup de ces solutions ici ignorent git
étiquettes de version qui signifie encore que vous devez suivre la version à plusieurs endroits (mauvais). Je me suis approché de cela avec les buts suivants:
- Tirer toutes les version de python de références à partir d'une balise dans le
git
repo - Automatiser
git tag
/push
etsetup.py upload
suit avec une seule commande qui ne prend pas d'entrées.
Comment ça marche?:
-
D'une commande
make release
, la dernière version étiquetée dans le git repo est trouvée et incrémentée. L'étiquette est ramenée àorigin
. -
le
Makefile
stocke la version danssrc/_version.py
où il sera lu parsetup.py
et également inclus dans la version. ne cochez pas_version.py
dans le contrôle à la source! -
setup.py
commande lit la nouvelle chaîne de version depackage.__version__
.
détails:
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/(.*)-.*//')
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 cible release
incrémente toujours le chiffre de la 3e version, mais vous pouvez utiliser le next_minor_ver
ou next_major_ver
pour incrémenter les autres chiffres. Les commandes s'appuient sur le script versionbump.py
qui est coché dans la racine du repo
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
Cela fait le levage lourd comment traiter et incrémenter le numéro de version de git
.
_ _ init__.py
le my_module/_version.py
est importé dans my_module/__init__.py
. Mettre statique installer config ici que vous voulez distribué avec votre module.
from ._version import __version__
__author__ = ''
__email__ = ''
setup.py
la dernière étape est de lire les informations de version du module 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__'],
...
...
)
bien sûr, pour que tout cela fonctionne, vous devez avoir au moins une balise de version dans votre pension pour commencer.
git tag -a v0.0.1
j'ai aussi vu un autre style:
>>> django.VERSION
(1, 1, 0, 'final', 0)
flèche poignées d'une façon intéressante.
Dans arrow/__init__.py
:
__version__ = '0.8.0'
VERSION = __version__
Dans 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__'),
# [...]
)
pour ce que ça vaut, si vous utilisez les distutils de NumPy, numpy.distutils.misc_util.Configuration
a une make_svn_version_py()
méthode qui intègre le numéro de révision à l'intérieur de package.__svn_version__
dans la variable version
.
- utiliser un
version.py
fichier seulement avec__version__ = <VERSION>
param dans le fichier. Dans le fichiersetup.py
importez le paramètre__version__
et mettez sa valeur dans le fichiersetup.py
comme ceci:version=__version__
- une autre façon est d'utiliser juste un fichier
setup.py
avecversion=<CURRENT_VERSION>
- la version actuelle est codée en dur.
puisque nous ne voulons pas changer manuellement la version dans le fichier chaque fois que nous créons une nouvelle étiquette (prêt pour publier une nouvelle version du paquet), nous pouvons utiliser ce qui suit..
je recommande fortement paquet bumpversion . Je l'utilise depuis des années pour faire tomber une version.
commencez par ajouter version=<VERSION>
à votre fichier setup.py
si vous ne l'avez pas déjà.
vous devriez utiliser un script court comme celui-ci chaque fois que vous heurtez une version:
bumpversion (patch|minor|major) - choose only one option
git push
git push --tags
puis Ajouter un fichier par rapport appelé: .bumpversion.cfg
:
[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]
Note:
- vous pouvez utiliser
__version__
paramètre sousversion.py
fichier comme il a été suggéré dans d'autres messages et mettre à jour le fichier bumpversion comme ceci:[bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
- Vous doit
git commit
ougit reset
tout dans votre pension, sinon vous obtiendrez un sale repo erreur. - Assurez-vous que votre environnement virtuel inclut le paquet de bumpversion, sans lui, il ne fonctionnera pas.
si vous utilisez CVS (ou RCS) et que vous souhaitez une solution rapide, vous pouvez utiliser:
__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])
(bien sûr, le numéro de révision vous sera substitué par CVS.)
ceci vous donne une version pour imprimer et une info de version que vous pouvez utiliser pour vérifier que le module que vous importez a au moins la version attendue:
import my_module
assert my_module.__version_info__ >= (1, 1)