Question

Existe-t-il un moyen standard d'associer une chaîne de version à un package python de telle sorte que je puisse effectuer les opérations suivantes?

import foo
print foo.version

J'imagine qu'il existe un moyen de récupérer ces données sans codage en dur supplémentaire, car 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, puis __version__.py généré par <=>.

Était-ce utile?

La solution

Ce n'est pas directement une réponse à votre question, mais vous devriez envisager de le nommer __version__, pas version.

Ceci est presque une quasi-standard. De nombreux modules de la bibliothèque standard utilisent <=> et ceci est également utilisé dans beaucoup de modules tiers , il s’agit donc du quasi standard.

En général, <=> est une chaîne, mais parfois c'est aussi un flottant ou un tuple.

Modifier: comme mentionné par S.Lott (merci!), PEP 8 le dit explicitement:

  

Version comptable

     

Si vous devez avoir Subversion, CVS ou RCS crud dans votre fichier source,   faites-le comme suit.

    __version__ = "$Revision: 63990 $"
    # $Source$
     

Ces lignes doivent être incluses après la docstring du module, avant   tout autre code, séparé par une ligne vierge en haut et en bas.

Vous devez également vous assurer que le numéro de version est conforme au format décrit dans PEP 440. ( PEP 386 , une version antérieure de cette norme).

Autres conseils

J'utilise un seul et même fichier _version.py comme " one Cannonical Place " pour stocker les informations de version:

  1. Il fournit un __version__ attribut.

  2. Il fournit la version standard des métadonnées. Par conséquent, il sera détecté par pkg_resources ou par d'autres outils qui analysent les métadonnées du package (EGG-INFO et / ou PKG-INFO, PEP 0345).

  3. Il n'importe pas votre package (ni quoi que ce soit d'autre) lors de la construction de votre package, ce qui peut entraîner 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, il n'y a donc qu'un seul endroit pour le changer lorsque le numéro de version change, et il y a moins de risque de versions incohérentes.

Voici comment cela fonctionne: le & "; un lieu canonique &"; pour stocker le numéro de version est un fichier .py, nommé " _version.py " qui se trouve dans votre package Python, par exemple dans myniftyapp/_version.py. Ce fichier est un module Python, mais votre setup.py ne l’importe pas! (Cela annulerait la fonctionnalité 3.) Au lieu de cela, votre setup.py sait que le contenu de ce fichier est très simple, quelque chose comme:

__version__ = "3.6.5"

Et ainsi votre setup.py ouvre le fichier et l’analyse 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,))

Ensuite, votre setup.py transmet cette chaîne en tant que valeur de la & version; " argument à setup(), satisfaisant ainsi la fonctionnalité 2.

Pour satisfaire à la fonctionnalité 1, vous pouvez demander à votre paquet (au moment de l'exécution, pas au moment de l'installation!) d'importer le fichier _version à partir de myniftyapp/__init__.py comme ceci:

from _version import __version__

Voici un exemple de cette technique Je l'utilise depuis des années.

Le code de cet exemple est un peu plus compliqué, mais l'exemple simplifié que j'ai écrit dans ce commentaire devrait être une implémentation complète.

Voici un exemple de code d'importation la version .

Si vous voyez quelque chose qui ne va pas avec cette approche, merci de me le faire savoir.

Réécrit 2017-05

Après plus de dix ans d'écriture de code Python et de gestion de divers packages, j'en suis venu à la conclusion que le bricolage n'était peut-être pas la meilleure approche.

J'ai commencé à utiliser pbr package pour gérer le contrôle de version dans mes packages. Si vous utilisez git en tant que SCM, cela s’intégrera parfaitement dans votre flux de travail, ce qui vous épargnera des semaines de travail (vous serez surpris de la complexité du problème).

À ce jour, pbr est classé 11ème paquet python le plus utilisé et n'a pas atteint ce niveau N'incluez aucune astuce sale: un seul: résoudre un problème d’emballage commun de manière très simple.

yourpackage/version.py peut faire plus de travail de maintenance du paquet, ne se limite pas à la gestion des versions mais ne vous oblige pas à en adopter tous les avantages.

Donc, pour vous donner une idée de la manière dont il semble adopter pbr dans un commit, jetez un coup d'oeil swich emballage pour pfr

Vous constaterez probablement que la version n’est pas du tout stockée dans le référentiel. PBR le détecte dans les branches et les tags Git.

Inutile de vous inquiéter de ce qui se passe lorsque vous n'avez pas de référentiel git, car pbr fait & "compiler &"; et mettre en cache la version lorsque vous compressez ou installez les applications, il n'y a donc aucune dépendance d'exécution à l'égard de git.

Ancienne solution

Voici la meilleure solution que j'ai vue jusqu'à présent et qui explique également pourquoi:

À l'intérieur yourpackage/__init__.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 setup.py:

from .version import __version__

À l'intérieur <=>:

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

Si vous connaissez une autre approche qui semble meilleure, faites-le moi savoir.

Selon le reporté PEP 396 (numéros de version de module) , un moyen proposé de le faire. Il décrit, avec justification, une norme (certes optionnelle) pour les modules à suivre. Voici un extrait:

  

3) Lorsqu'un module (ou package) inclut un numéro de version, cette version DEVRAIT être disponible dans l'attribut __version__.

     

4)   Pour les modules qui résident dans un paquet d'espace de noms, le module DEVRAIT inclure l'attribut <=>. Le paquet d'espace de noms lui-même NE DEVRAIT PAS inclure son propre <=> attribut.

     

5)   La <=> valeur de l'attribut DEVRAIT être une chaîne.

Bien que ce soit probablement trop tard, il existe une alternative un peu 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 portions de numéros de version auto-incrémentées en chaîne à l'aide de 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 lorsqu'ils utilisent __version_info__, et à ce titre, le stockent comme un tuple d'intes; cependant, je ne vois pas trop l'intérêt de le faire, car je doute que vous puissiez effectuer des opérations mathématiques telles que l'addition et la soustraction de portions de numéros de version à des fins autres que la curiosité ou l'auto-incrémentation (et même dans ce cas, int() et <=> peuvent être utilisés assez facilement). (D'autre part, il est possible que le code de quelqu'un d'autre attende un tuple numérique plutôt qu'un tuple de chaîne et qu'il échoue ainsi.)

C’est, bien sûr, mon point de vue, et j’aimerais bien que les autres nous disent comment utiliser un tuple numérique.

Comme me l'a rappelé Shezi, les comparaisons (lexicales) des chaînes de nombres n'ont pas nécessairement le même résultat que les comparaisons numériques directes; des zéros au début seraient nécessaires pour répondre à cela. Donc, en fin de compte, stocker <=> (ou ce que cela s'appellera) sous forme de tuple de valeurs entières permettrait une comparaison plus efficace des versions.

J'utilise un fichier JSON dans le répertoire dir. Cela répond aux exigences de 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']

Je mets également d'autres informations dans pkg_info.json, comme l'auteur. je souhaite utiliser JSON car je peux automatiser la gestion des métadonnées.

Beaucoup de ces solutions ignorent les git balises de version, ce qui signifie que vous devez suivre la version à plusieurs endroits (mauvais). J'ai abordé cette question avec les objectifs suivants:

  • Dérivez toutes les références de version python d'une balise dans le git tag référentiel
  • Automatisez les étapes push / setup.py upload et make release avec une seule commande ne prenant aucune entrée.

Comment ça marche:

  1. Dans une commande origin, la dernière version marquée du référentiel git est trouvée et incrémentée. La balise est repoussée à Makefile.

  2. Le src/_version.py enregistre la version dans setup.py où elle sera lue par _version.py et également incluse dans la version. Ne pas vérifier package.__version__ dans le contrôle de source!

  3. La commande
  4. release lit la nouvelle chaîne de version à partir de next_minor_ver.

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/(.*)-.*/\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

La next_major_ver cible incrémente toujours le troisième chiffre de la version, mais vous pouvez utiliser versionbump.py ou my_module/_version.py pour incrémenter les autres chiffres. Les commandes reposent sur le script my_module/__init__.py archivé à la racine du référentiel

.

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

Ceci explique comment traiter et incrémenter le numéro de version de my_module.

__ init __. py

Le fichier <=> est importé dans <=>. Indiquez ici la configuration d'installation statique que vous souhaitez distribuer avec votre module.

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

setup.py

La dernière étape consiste à lire les informations de version à partir du <=> 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 référentiel pour commencer.

git tag -a v0.0.1

Il ne semble pas exister de méthode standard pour incorporer une chaîne de version dans un package python. La plupart des packages que j'ai vus utilisent une variante de votre solution, à savoir eitner

  1. Intégrez la version dans setup.py et faites en sorte que version.py génère un module (par exemple, <=>) contenant uniquement les informations de version importées par votre package ou

  2. .
  3. L'inverse: placez les informations de version dans votre paquet lui-même et importez ce pour définir la version dans <=>

Il convient également de noter que ainsi que __version__ être un semi-std. en python, il en est ainsi __version_info__ qui est un tuple, dans les cas simples, vous pouvez simplement 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 d'un fichier ou autre.

J'ai aussi vu un autre style:

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

flèche le gère de manière 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 NumPy distutils, numpy.distutils.misc_util.Configuration a un make_svn_version_py() méthode intégrant le numéro de révision entre package.__svn_version__ dans la variable version.

.
  1. Utilisez un fichier version.py uniquement avec __version__ = <VERSION> param dans le fichier. Dans le fichier setup.py, importez le paramètre __version__ et mettez-le dans le fichier version=__version__ comme ceci: version=<CURRENT_VERSION>
  2. Une autre méthode consiste à utiliser uniquement un version=<VERSION> fichier avec .bumpversion.cfg - la CURRENT_VERSION est codée en dur.

Etant donné que nous ne souhaitons pas modifier manuellement la version du fichier chaque fois que nous créons un nouveau tag (prêt à publier une nouvelle version du package), nous pouvons utiliser les éléments suivants.

Je recommande vivement le package bumpversion . Je l'utilise depuis des années pour créer une version.

commencez par ajouter [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>] à votre git commit fichier si vous ne l'avez pas déjà.

Vous devez utiliser un court script comme celui-ci chaque fois que vous cognez une version:

bumpversion (patch|minor|major) - choose only one option
git push
git push --tags

Ajoutez ensuite un fichier par référentiel appelé: git reset:

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

Remarque:

  • Vous pouvez utiliser le paramètre <=> sous <=> fichier comme suggéré dans d'autres publications et mettre à jour le fichier bumpversion comme ceci: <=>
  • Vous devez <=> ou <=> tout ce qui se trouve dans votre rapport, sinon vous obtiendrez une erreur de rapport sale.
  • Assurez-vous que votre environnement virtuel inclut le package de bumpversion. Sans cela, il ne fonctionnera pas.

Si vous utilisez CVS (ou RCS) et souhaitez une solution rapide, vous pouvez utiliser:

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

(Bien entendu, le numéro de révision vous sera remplacé par CVS.)

Ceci vous donne une version imprimable et des informations sur la 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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top