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 .

188
demandé sur Tshepang 2009-01-19 21:05:54

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

99
répondu oefe 2014-10-03 14:07:18

j'utilise un seul fichier _version.py pour stocker les informations de version:

  1. il fournit un attribut __version__ .

  2. 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).

  3. 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.)

  4. 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.

91
répondu Zooko 2018-06-13 18:13:12

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.

80
répondu sorin 2018-01-05 19:25:57

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.

24
répondu Oddthinking 2017-02-20 21:45:36

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.

21
répondu JAB 2013-02-27 21:54:36

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.

9
répondu Andy Lee 2014-01-25 21:23:11

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

  1. intégrer la version dans setup.py et avoir setup.py générer un module (par exemple version.py ) contenant seulement des informations de version, qui est importé par votre paquet, ou

  2. 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

7
répondu dF. 2009-01-19 19:01:40

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.

6
répondu James Antill 2009-12-14 06:27:09

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 et setup.py upload suit avec une seule commande qui ne prend pas d'entrées.

Comment ça marche?:

  1. 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 .

  2. le Makefile stocke la version dans src/_version.py où il sera lu par setup.py et également inclus dans la version. ne cochez pas _version.py dans le contrôle à la source!

  3. setup.py commande lit la nouvelle chaîne de version de package.__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
6
répondu cmcginty 2017-08-16 06:20:30

j'ai aussi vu un autre style:

>>> django.VERSION
(1, 1, 0, 'final', 0)
4
répondu L1ker 2009-10-11 17:32:47

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__'),
    # [...]
)
2
répondu Anto 2016-10-26 12:59:55

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 .

0
répondu Matt 2009-01-19 18:42:23
  1. utiliser un version.py fichier seulement avec __version__ = <VERSION> param dans le fichier. Dans le fichier setup.py importez le paramètre __version__ et mettez sa valeur dans le fichier setup.py comme ceci: version=__version__
  2. une autre façon est d'utiliser juste un fichier setup.py avec version=<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 sous version.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 ou git 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.
-1
répondu Oran 2018-02-07 23:54:57

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)
-2
répondu Martin Ibert 2017-06-02 10:11:23