¿Forma estándar de incrustar la versión en el paquete de Python?

265

¿Existe una forma estándar de asociar la cadena de versión con un paquete de Python de manera que pueda hacer lo siguiente?

import foo
print foo.version

Me imagino que hay alguna manera de recuperar esos datos sin ningún tipo de codificación adicional, ya que las cadenas menores / mayores ya están especificadas setup.py. La solución alternativa que encontré fue tener import __version__en mi foo/__init__.pyy luego haber __version__.pygenerado por setup.py.

Dimitri Tcaciuc
fuente
77
Para su información, hay una muy buena descripción en: packaging.python.org/en/latest/…
ionelmc
1
La versión de un paquete instalado se puede recuperar de los metadatos con las herramientas de configuración , por lo que en muchos casos setup.pyes suficiente colocar solo la versión . Ver esta pregunta .
saaj
2
Para su información, existen básicamente 5 patrones comunes para mantener la única fuente de verdad (tanto en la configuración como en el tiempo de ejecución) para el número de versión.
KF Lin
La documentación de @ionelmc Python enumera 7 opciones diferentes para un solo suministro . ¿No contradice eso el concepto de una " fuente única de verdad "?
Stevoisiak
@StevenVascellaro no estoy seguro de lo que estás preguntando. Hay muchas formas enumeradas allí porque la guía de empaque no quiere ser obstinada.
ionelmc

Respuestas:

136

No es directamente una respuesta a su pregunta, pero debería considerar nombrarla __version__, no version.

Esto es casi un cuasi-estándar. Muchos módulos en la biblioteca estándar usan __version__, y esto también se usa en muchos módulos de terceros, por lo que es el cuasi-estándar.

Por lo general, __version__es una cadena, pero a veces también es un flotador o tupla.

Editar: como lo menciona S.Lott (¡Gracias!), PEP 8 lo dice explícitamente:

Nombres del módulo de nivel Dunder

Módulo de nivel "dunders" (es decir nombres con dos principales y dos guiones se arrastran), tales como __all__, __author__, __version__, etc. deben ser colocados después de la docstring módulo, pero antes de cualquier declaración de importación excepto de __future__las importaciones.

También debe asegurarse de que el número de versión se ajuste al formato descrito en PEP 440 ( PEP 386, una versión anterior de este estándar).

oefe
fuente
9
Debe ser una cadena y tener una version_info para la versión de tupla.
James Antill el
James: ¿Por qué __version_info__específicamente? (Que "inventa" su propia palabra de doble guión bajo). [Cuando James comentó, los guiones bajos no hicieron nada en los comentarios, ahora indican énfasis, por lo que James realmente __version_info__también escribió . --- ed.]
Puede ver algo sobre qué versión debería decir en packages.python.org/distribute/… Esa página se trata de distribuir, pero el significado del número de versión se está convirtiendo en un estándar de facto.
sienkiew
2
Correcto. Parece que estas PEP se contradicen entre sí. Bueno, PEP 8 dice "if" y "crud", por lo que realmente no respalda el uso de la expansión de palabras clave VCS. Además, si alguna vez cambia a un VCS diferente, perderá la información de revisión. Por lo tanto, sugeriría usar una información de versión compatible con PEP 386/440 incrustada en un solo archivo fuente, al menos para proyectos más grandes.
oefe
2
¿Dónde pondrías esa versión ? Teniendo en cuenta que esta es la versión aceptada, me encantaría ver esa información adicional aquí.
darkgaze
120

Utilizo un solo _version.pyarchivo como "lugar canónico" para almacenar la información de la versión:

  1. Proporciona un __version__atributo.

  2. Proporciona la versión estándar de metadatos. Por lo tanto, será detectado por pkg_resourcesu otras herramientas que analicen los metadatos del paquete (EGG-INFO y / o PKG-INFO, PEP 0345).

  3. No importa su paquete (o cualquier otra cosa) al compilarlo, lo que puede causar problemas en algunas situaciones. (Vea los comentarios a continuación sobre los problemas que esto puede causar).

  4. Solo hay un lugar donde se escribe el número de versión, por lo que solo hay un lugar para cambiarlo cuando cambia el número de versión, y hay menos posibilidades de versiones inconsistentes.

Así es como funciona: el "único lugar canónico" para almacenar el número de versión es un archivo .py, llamado "_version.py" que está en su paquete de Python, por ejemplo en myniftyapp/_version.py. Este archivo es un módulo de Python, ¡pero tu setup.py no lo importa! (Eso anularía la función 3.) En cambio, setup.py sabe que el contenido de este archivo es muy simple, algo como:

__version__ = "3.6.5"

Y entonces tu setup.py abre el archivo y lo analiza, con un código como:

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

Luego, su setup.py pasa esa cadena como el valor del argumento "versión" setup(), satisfaciendo así la característica 2.

Para satisfacer la función 1, puede hacer que su paquete (en tiempo de ejecución, no en el momento de la configuración) importe el archivo _version de myniftyapp/__init__.pyesta manera:

from _version import __version__

Aquí hay un ejemplo de esta técnica que he estado usando durante años.

El código en ese ejemplo es un poco más complicado, pero el ejemplo simplificado que escribí en este comentario debería ser una implementación completa.

Aquí hay un código de ejemplo para importar la versión .

Si ve algún problema con este enfoque, hágamelo saber.

Zooko
fuente
8
¿Podría describir los problemas que motivan al n. ° 3? Glyph dijo que tenía algo que ver con "a setuptools le gusta fingir que su código no está en ninguna parte del sistema cuando se ejecuta setup.py", pero los detalles ayudarían a convencerme a mí y a los demás.
Ivan Kozik
2
@Iva Ahora, ¿en qué orden debe hacer esto la herramienta? No puede (en el / virtualenv sistema setuptools PIP / día de hoy) siquiera sabe lo que los deps son hasta que se evalúa su setup.py. Además, si intentara hacer profundidad completa primero y hacer todas las profundidades antes de hacerlo, se atascaría si hubiera deps circulares. Pero si intenta compilar este paquete antes de instalar las dependencias, si importa su paquete desde el suyo setup.py, no necesariamente podrá importar sus deps o las versiones correctas de sus deps.
Zooko
3
¿Podría escribir el archivo "version.py" desde "setup.py" en lugar de analizarlo? Eso parece más simple.
Jonathan Hartley
3
Jonathan Hartley: Estoy de acuerdo en que sería un poco más simple para su "setup.py" escribir el archivo "version.py" en lugar de analizarlo, pero abriría una ventana por inconsistencia, cuando haya editado su setup.py tener la nueva versión pero aún no ha ejecutado setup.py para actualizar el archivo version.py. Otra razón para que la versión canónica esté en un pequeño archivo separado es que facilita que otras herramientas, como las herramientas que leen el estado de control de revisión, escriban el archivo de versión.
Zooko
3
Un enfoque similar es execfile("myniftyapp/_version.py")desde setup.py, en lugar de intentar analizar el código de versión manualmente. Sugerido en stackoverflow.com/a/2073599/647002 : la discusión allí también puede ser útil.
medmunds
97

Reescrito 2017-05

Después de más de diez años de escribir código Python y administrar varios paquetes, llegué a la conclusión de que el bricolaje quizás no sea el mejor enfoque.

Comencé a usar el pbrpaquete para lidiar con el control de versiones en mis paquetes. Si está utilizando git como su SCM, esto encajará en su flujo de trabajo como por arte de magia, ahorrando sus semanas de trabajo (se sorprenderá de lo complejo que puede ser el problema).

A partir de hoy, pbr ocupa el puesto número 11 del paquete de python más utilizado y alcanzar este nivel no incluyó ningún truco sucio: era solo uno: solucionar un problema de empaquetado común de una manera muy simple.

pbr puede hacer más de la carga de mantenimiento del paquete, no se limita al control de versiones, pero no lo obliga a adoptar todos sus beneficios.

Entonces, para darle una idea de cómo se ve adoptar pbr en una confirmación, eche un vistazo al empaque de pbr

Probablemente haya observado que la versión no está almacenada en absoluto en el repositorio. PBR lo detecta desde las ramas y etiquetas de Git.

No necesita preocuparse por lo que sucede cuando no tiene un repositorio git porque pbr "compila" y almacena en caché la versión cuando empaqueta o instala las aplicaciones, por lo que no hay dependencia del tiempo de ejecución en git.

Vieja solución

Aquí está la mejor solución que he visto hasta ahora y también explica por qué:

Dentro 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'

Dentro yourpackage/__init__.py:

from .version import __version__

Dentro setup.py:

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

Si conoce otro enfoque que parece ser mejor, hágamelo saber.

Sorin
fuente
12
Err, no. execfile () no existe en Python 3, por lo que es mejor usar exec (open (). read ()).
Christophe Vu-Brugier
44
¿por qué no también from .version import __version__en setup.py?
Aprillion
44
@Aprillion Debido a que el paquete no se carga cuando se setup.pyestá ejecutando, pruébalo, obtendrás un error (o al menos lo hice :-))
darthbith
3
El enlace a pbr da como resultado una mala puerta de enlace.
MERose
44
pbr , sin duda, es una gran herramienta, pero no pudo abordar la pregunta. ¿Cómo puede acceder a la versión actual o al paquete instalado a través de bpr ?
nad2000
29

Según el PEP 396 diferido (números de versión del módulo) , hay una forma propuesta de hacerlo. Describe, con justificación, un estándar (ciertamente opcional) para los módulos a seguir. Aquí hay un fragmento:

3) Cuando un módulo (o paquete) incluye un número de versión, la versión DEBE estar disponible en el __version__atributo.

4) Para los módulos que viven dentro de un paquete de espacio de nombres, el módulo DEBE incluir el __version__atributo. El paquete de espacio de nombres en sí NO DEBE incluir su propio __version__atributo.

5) El __version__valor del atributo DEBE ser una cadena.

Pensamiento extraño
fuente
13
Ese PEP no es aceptado / estandarizado, sino diferido (debido a la falta de interés). Por lo tanto, es un poco engañoso afirmar que " hay una forma estándar " especificada por él.
tejedor
@weaver: ¡Dios mío! Aprendí algo nuevo. No sabía que era algo que necesitaba verificar.
Pensamiento extraño
44
Editado para tener en cuenta que no es un estándar. Ahora me siento avergonzado, porque he planteado solicitudes de funciones en proyectos pidiéndoles que sigan este "estándar".
Pensamiento extraño
1
Tal vez debería hacerse cargo del trabajo de estandarización en ese PEP, ya que parece interesado :)
weaver
Esto funcionaría para versionar un módulo individual, pero no estoy seguro de que se aplique a versionar un proyecto completo.
Stevoisiak
21

Aunque esto probablemente sea demasiado tarde, hay una alternativa un poco más simple a la respuesta anterior:

__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)

(Y sería bastante simple convertir porciones de números de versión que se incrementan automáticamente a una cadena usando str()).

Por supuesto, por lo que he visto, la gente tiende a usar algo como la versión mencionada anteriormente cuando la usa __version_info__, y como tal la almacena como una tupla de entradas; sin embargo, no veo el punto de hacerlo, ya que dudo que haya situaciones en las que realice operaciones matemáticas, como sumas y restas en porciones de números de versión para cualquier propósito además de curiosidad o auto-incremento (e incluso entonces, int()y str()se puede usar con bastante facilidad). (Por otro lado, existe la posibilidad de que el código de otra persona espere una tupla numérica en lugar de una tupla de cadena y, por lo tanto, falle).

Esta es, por supuesto, mi propio punto de vista, y con gusto me gustaría la opinión de otros sobre el uso de una tupla numérica.


Como me recordó Shezi, las comparaciones (léxicas) de cadenas de números no necesariamente tienen el mismo resultado que las comparaciones numéricas directas; se requerirían ceros iniciales para proporcionar eso. Entonces, al final, almacenar __version_info__(o como se llame) como una tupla de valores enteros permitiría comparaciones de versiones más eficientes.

PINCHAZO
fuente
12
bonito (+1), pero ¿no preferirías números en lugar de cadenas? por ejemplo__version_info__ = (1,2,3)
oriP
3
La comparación de cadenas puede volverse peligrosa cuando los números de versión exceden 9, ya que, por ejemplo, '10' <'2'.
D Coetzee
13
Hago esto también, pero ligeramente modificado para abordar las __version_info__ = (0, 1, 0) __version__ = '.'.join(map(str, __version_info__))
entradas
2
El problema __version__ = '.'.join(__version_info__)es que __version_info__ = ('1', '2', 'beta')se convertirá 1.2.beta, no 1.2betao1.2 beta
nagisa
44
Creo que el problema con este enfoque es dónde colocar las líneas de código que declaran la __versión__. Si están en setup.py, entonces su programa no puede importarlos desde su paquete. Quizás esto no sea un problema para ti, en cuyo caso, está bien. Si van dentro de su programa, su setup.py puede importarlos, pero no debería, ya que setup.py se ejecuta durante la instalación cuando las dependencias de su programa aún no están instaladas (setup.py se utiliza para determinar cuáles son las dependencias .) De ahí la respuesta de Zooko: analizar manualmente el valor de un archivo fuente del producto, sin importar el paquete del producto
Jonathan Hartley
11

Muchas de estas soluciones aquí ignoran las gitetiquetas de versión, lo que aún significa que debe rastrear la versión en varios lugares (incorrecto). Abordé esto con los siguientes objetivos:

  • Derive todas las referencias de versión de Python de una etiqueta en el gitrepositorio
  • Automatizar git tag/ pushy setup.py uploadpasos con un solo comando que no toma entradas.

Cómo funciona:

  1. A partir de un make releasecomando, se encuentra e incrementa la última versión etiquetada en el repositorio de git. La etiqueta se devuelve a origin.

  2. Las Makefiletiendas de la versión en src/_version.pydonde será leído por setup.pye incluyó también en el comunicado. ¡No verifique el _version.pycontrol de la fuente!

  3. setup.pyEl comando lee la nueva cadena de versión package.__version__.

Detalles:

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

El releaseobjetivo siempre incrementa el dígito de la tercera versión, pero puede usar next_minor_vero next_major_verpara incrementar los otros dígitos. Los comandos se basan en el versionbump.pyscript que se registra en la raíz del repositorio

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

Esto hace el trabajo pesado cómo procesar e incrementar el número de versión git.

__init__.py

El my_module/_version.pyarchivo se importa a my_module/__init__.py. Coloque aquí cualquier configuración de instalación estática que desee distribuir con su módulo.

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

setup.py

El último paso es leer la información de la versión del my_modulemódulo.

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__'],
    ...
    ...
)

Por supuesto, para que todo esto funcione, tendrá que tener al menos una etiqueta de versión en su repositorio para comenzar.

git tag -a v0.0.1
cmcginty
fuente
1
de hecho, todo este hilo olvida que un VCS es muy importante en esta discusión. solo una observación: el incremento de la versión debería seguir siendo un proceso manual, por lo que la forma preferida sería 1. crear y empujar manualmente una etiqueta 2. dejar que las herramientas VCS descubran esa etiqueta y la almacenen donde sea necesario (wow, esta interfaz de edición SO realmente me está paralizando - Tuve que editar esto una docena de veces solo para lidiar con las nuevas líneas ¡Y TODAVÍA NO FUNCIONA! @ # $% ^ & *)
axd
No es necesario usarlo versionbump.pycuando tenemos un increíble paquete de bumpversion para python.
Orán
@Oran Miré versionbump. Los documentos no son muy claros y no maneja el etiquetado muy bien. En mis pruebas parece entrar en estados que hacen que se bloquee: subprocess.CalledProcessError: Comando '[' git ',' commit ',' -F ',' / var / folder / rl / tjyk4hns7kndnx035p26wg692g_7t8 / T / tmppishngbo '] 'devolvió el estado de salida no cero 1.
cmcginty
1
¿Por qué no _version.pydebería rastrearse con el control de versiones?
Stevoisiak
1
@StevenVascellaro Esto complica el proceso de liberación. Ahora debe asegurarse de que sus etiquetas y confirmaciones coincidan con el valor en _version.py. El uso de una etiqueta de versión única es una IMO más limpia y significa que puede crear una versión directamente desde la interfaz de usuario de github.
cmcginty
10

Yo uso un archivo JSON en el paquete dir. Esto se ajusta a los requisitos de Zooko.

Dentro pkg_dir/pkg_info.json:

{"version": "0.1.0"}

Dentro 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'],
    ...
    )

Dentro 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']

También pongo otra información en pkg_info.json, como autor. Me gusta usar JSON porque puedo automatizar la administración de metadatos.

Andy Lee
fuente
¿Podría explicar cómo utilizar json para automatizar la gestión de metadatos? ¡Gracias!
ryanjdillon
6

También vale la pena señalar que además de __version__ser un semi-estándar. en Python también lo __version_info__que es una tupla, en los casos simples puedes hacer algo como:

__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])

... y puedes obtener la __version__cadena de un archivo, o lo que sea.

James Antill
fuente
1
¿Tiene alguna referencia / enlace sobre el origen de este uso de __version_info__?
Craig McQueen
3
Bueno, es el mismo mapeo que sys.version a sys.version_info. Entonces: docs.python.org/library/sys.html#sys.version_info
James Antill
77
Es más fácil hacer el mapeo en la otra dirección ( __version_info__ = (1, 2, 3)y __version__ = '.'.join(map(str, __version_info__))).
Eric O Lebigot
2
@EOL - __version__ = '.'.join(str(i) for i in __version_info__)- un poco más largo pero más pitónico.
ArtOfWarfare
2
No estoy seguro de que lo que propongas sea claramente más pitónico, ya que contiene una variable ficticia que no es realmente necesaria y cuyo significado es un poco difícil de expresar ( ino tiene significado, version_numes un poco largo y ambiguo ...). Incluso tomo la existencia de map()Python como una fuerte pista de que debería usarse aquí, ya que lo que necesitamos hacer aquí es el caso de uso típico de map()(uso con una función existente): no veo muchos otros usos razonables.
Eric O Lebigot
5

No parece haber una forma estándar de incrustar una cadena de versión en un paquete de Python. La mayoría de los paquetes que he visto usan alguna variante de su solución, es decir, eitner

  1. Incruste la versión setup.pyy setup.pygenere un módulo (p version.py. Ej. ) Que contenga solo información de la versión, importada por su paquete, o

  2. En el reverso: poner la información de la versión en su propio envase, y la importación que para configurar la versión en setup.py

dF.
fuente
Me gusta su recomendación, pero ¿cómo generar este módulo desde setup.py?
sorin
1
Me gusta la idea de la opción (1), parece más simple que la respuesta de Zooko de analizar el número de versión de un archivo. Pero no puede asegurarse de que version.py se cree cuando un desarrollador simplemente clone su repositorio. A menos que registre version.py, lo que abre la pequeña arruga de que puede cambiar el número de versión en setup.py, commit, release y luego tiene que (barra oblicua) confirmar el cambio en version.py.
Jonathan Hartley
Presumiblemente, podría generar el módulo usando algo como "" "con open (" mypackage / version.py "," w ") como fp: fp.write (" __ version__ == '% s' \ n "% (__version__,) ) "" "
Jonathan Hartley
1
Creo que la opción 2. es susceptible de fallar durante la instalación como se señala en los comentarios a la respuesta de JAB.
Jonathan Hartley
¿Qué hay de eso? Pones __version__ = '0.0.1' "(donde la versión es una cadena, por supuesto) en el __init__.py" del paquete principal de tu software. Luego vaya al punto 2: en la configuración que realiza desde el paquete .__ init__ import __version__ como v, y luego configura la variable v como la versión de su setup.py. Luego importe mypack como mi, imprima mi .__ versión__ imprimirá la versión. La versión se almacenará en un solo lugar, accesible desde todo el código, en un archivo que no importe nada más relacionado con el software.
SeF
5

La flecha lo maneja de una manera interesante.

Ahora (desde 2e5031b )

En arrow/__init__.py:

__version__ = 'x.y.z'

En setup.py:

from arrow import __version__

setup(
    name='arrow',
    version=__version__,
    # [...]
)

antes de

En arrow/__init__.py:

__version__ = 'x.y.z'
VERSION = __version__

En setup.py:

def grep(attrname):
    pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
    strval, = re.findall(pattern, file_text)
    return strval

file_text = read(fpath('arrow/__init__.py'))

setup(
    name='arrow',
    version=grep('__version__'),
    # [...]
)
Anto
fuente
lo que es file_text?
ely
2
La solución actualizada es realmente dañina. Cuando se está ejecutando setup.py, no necesariamente verá la versión del paquete desde la ruta del archivo local. Puede volver a una versión instalada previamente, por ejemplo, al ejecutarse pip install -e .en una rama de desarrollo o algo así durante la prueba. setup.py no debe confiar en importar el paquete que está en proceso de instalación para determinar los parámetros para la implementación. Yikes
ely
Sí, no sé por qué Arrow decidió regresar a esta solución. Además, el mensaje de confirmación dice "Actualización de setup.py con los estándares modernos de Python " ... 🤷
Anto
4

También vi otro estilo:

>>> django.VERSION
(1, 1, 0, 'final', 0)
L1ker
fuente
1
Sí, yo también lo vi. Por cierto, cada respuesta toma otro estilo, así que ahora no sé qué estilo es un "estándar". Buscando los PEP mencionados ...
kbec
Otra forma vista; El cliente Python de Mongo usa una versión simple, sin guiones bajos. Entonces esto funciona; $ python >>> import pymongo >>> pymongo.version '2.7'
AnneTheAgile
Implementar .VERSIONno significa que no tenga que implementar __version__.
Acumenus
¿Requiere esto implementar djangoen el proyecto?
Stevoisiak
3

Usando setuptoolsypbr

No hay una forma estándar de administrar la versión, pero la forma estándar de administrar sus paquetes es setuptools.

La mejor solución que he encontrado en general para administrar la versión es usar setuptoolscon la pbrextensión. Esta es ahora mi forma estándar de administrar la versión.

Configurar su proyecto para el empaquetado completo puede ser excesivo para proyectos simples, pero si necesita administrar la versión, probablemente esté en el nivel correcto para configurar todo. Hacerlo también hace que su paquete sea liberable en PyPi para que todos puedan descargarlo y usarlo con Pip.

PBR mueve la mayoría de los metadatos de las setup.pyherramientas a un setup.cfgarchivo que luego se usa como fuente para la mayoría de los metadatos, que puede incluir la versión. Esto permite que los metadatos se empaqueten en un ejecutable usando algo como pyinstallersi fuera necesario (si es así, probablemente necesitará esta información ), y separa los metadatos de los otros scripts de configuración / administración de paquetes. Puede actualizar directamente la cadena de versión de forma setup.cfgmanual, y se extraerá de la *.egg-infocarpeta al compilar las versiones de su paquete. Sus scripts pueden acceder a la versión de los metadatos utilizando varios métodos (estos procesos se describen en las secciones a continuación).

Al usar Git para VCS / SCM, esta configuración es aún mejor, ya que extraerá muchos de los metadatos de Git para que su repositorio pueda ser su fuente principal de verdad para algunos de los metadatos, incluida la versión, los autores, los registros de cambios, etc. Para la versión específicamente, creará una cadena de versión para la confirmación actual basada en las etiquetas git en el repositorio.

Como PBR extraerá la versión, el autor, el registro de cambios y otra información directamente de su repositorio de git, por lo que algunos de los metadatos setup.cfgpueden omitirse y generarse automáticamente cada vez que se crea una distribución para su paquete (usando setup.py)

Versión actual en tiempo real

setuptoolsextraerá la información más reciente en tiempo real usando setup.py:

python setup.py --version

Esto extraerá la última versión del setup.cfgarchivo o del repositorio de git, según la última confirmación realizada y las etiquetas que existen en el repositorio. Sin embargo, este comando no actualiza la versión en una distribución.

Actualizando la versión

Cuando crea una distribución con setup.py(es decir py setup.py sdist, por ejemplo), toda la información actual se extraerá y almacenará en la distribución. Esto esencialmente ejecuta el setup.py --versioncomando y luego almacena esa información de versión en la package.egg-infocarpeta en un conjunto de archivos que almacenan metadatos de distribución.

Nota sobre el proceso para actualizar los metadatos de la versión:

Si no está utilizando pbr para extraer datos de versión de git, simplemente actualice su setup.cfg directamente con la información de la nueva versión (lo suficientemente fácil, pero asegúrese de que esta sea una parte estándar de su proceso de lanzamiento).

Si está usando git y no necesita crear una fuente o distribución binaria (usando python setup.py sdisto uno de los python setup.py bdist_xxxcomandos), la forma más sencilla de actualizar la información de repositorio de git en su <mypackage>.egg-infocarpeta de metadatos es simplemente ejecutar el python setup.py installcomando. Esto ejecutará todas las funciones PBR relacionadas con la extracción de metadatos del repositorio de git y actualizará su .egg-infocarpeta local , instalará ejecutables de script para cualquier punto de entrada que haya definido y otras funciones que puede ver desde la salida cuando ejecuta este comando.

Tenga en cuenta que la .egg-infocarpeta generalmente se excluye de ser almacenada en el repositorio git en .gitignorearchivos Python estándar (como Gitignore.IO ), ya que puede generarse desde su fuente. Si se excluye, asegúrese de tener un "proceso de lanzamiento" estándar para actualizar los metadatos localmente antes del lanzamiento, y cualquier paquete que cargue en PyPi.org o distribuya de otra manera debe incluir estos datos para tener la versión correcta. Si desea que el repositorio de Git contenga esta información, puede excluir que se ignoren archivos específicos (es decir, agregar !*.egg-info/PKG_INFOa .gitignore)

Accediendo a la versión desde un script

Puede acceder a los metadatos desde la compilación actual dentro de los scripts de Python en el paquete mismo. Para la versión, por ejemplo, hay varias formas de hacer esto que he encontrado hasta ahora:

## This one is a new built-in as of Python 3.8.0 should become the standard
from importlib-metadata import version

v0 = version("mypackage")
print('v0 {}'.format(v0))

## I don't like this one because the version method is hidden
import pkg_resources  # part of setuptools

v1 = pkg_resources.require("mypackage")[0].version
print('v1 {}'.format(v1))

# Probably best for pre v3.8.0 - the output without .version is just a longer string with
# both the package name, a space, and the version string
import pkg_resources  # part of setuptools

v2 = pkg_resources.get_distribution('mypackage').version
print('v2 {}'.format(v2))

## This one seems to be slower, and with pyinstaller makes the exe a lot bigger
from pbr.version import VersionInfo

v3 = VersionInfo('mypackage').release_string()
print('v3 {}'.format(v3))

Puede poner uno de estos directamente en su __init__.pypaquete para extraer la información de la versión de la siguiente manera, similar a algunas otras respuestas:

__all__ = (
    '__version__',
    'my_package_name'
)

import pkg_resources  # part of setuptools

__version__ = pkg_resources.get_distribution("mypackage").version
LightCC
fuente
Reformateado por voto negativo para responder directamente la pregunta ahora.
LightCC
1

Después de varias horas de tratar de encontrar la solución confiable más simple, estas son las partes:

cree un archivo version.py DENTRO de la carpeta de su paquete "/ mypackage":

# 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__ = '1.2.7'

en setup.py:

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

en la carpeta principal init .py:

from .version import __version__

La exec()función ejecuta el script fuera de cualquier importación, ya que setup.py se ejecuta antes de que se pueda importar el módulo. Todavía solo necesita administrar el número de versión en un archivo en un lugar, pero desafortunadamente no está en setup.py. (Esa es la desventaja, pero no tener errores de importación es la ventaja)

Marc Maxmeister
fuente
1

Se ha completado mucho trabajo hacia el versionado uniforme y en apoyo de las convenciones desde que se hizo esta pregunta por primera vez . Las opciones de palatable ahora se detallan en la Guía del usuario de Python Packaging . También es digno de mención que los esquemas de número de versión son relativamente estrictos en Python por PEP 440 , por lo que mantener las cosas sanas es fundamental si su paquete se lanzará a la tienda de quesos .

Aquí hay un desglose acortado de las opciones de versiones:

  1. Lea el archivo en setup.py( setuptools ) y obtenga la versión.
  2. Utilice una herramienta de compilación externa (para actualizar tanto __init__.pycomo el control de origen), por ejemplo , bump2version , changes o zest.releaser .
  3. Establezca el valor en una __version__variable global en un módulo específico.
  4. Coloque el valor en un archivo de texto VERSIÓN simple para que setup.py y el código lean.
  5. Establezca el valor a través de una setup.pyversión y use importlib.metadata para recogerlo en tiempo de ejecución. (Advertencia, hay versiones anteriores a 3.8 y posteriores a 3.8).
  6. Establezca el valor __version__en sample/__init__.pye importe la muestra en setup.py.
  7. Use setuptools_scm para extraer el control de versiones del control de origen para que sea la referencia canónica, no el código.

TENGA EN CUENTA que (7) podría ser el enfoque más moderno (los metadatos de compilación son independientes del código, publicado por automatización). También TENGA EN CUENTA que si se usa la configuración para la liberación del paquete, un simple python3 setup.py --versioninformará la versión directamente.

ingyhere
fuente
-1

Por lo que vale, si está usando NumPy distutils, numpy.distutils.misc_util.Configurationtiene un make_svn_version_py()método que incrusta el número de revisión dentro package.__svn_version__de la variable version.

Mate
fuente
¿Puede proporcionar más detalles o un ejemplo de cómo funcionaría esto?
Stevoisiak
Hmm En 2020, esto es (¿siempre fue así?) Destinado a FORTRAN . El paquete "numpy.distutils es parte de NumPy que extiende distutils estándar de Python para tratar con las fuentes de Fortran".
ingyhere
-1
  1. Use un version.pyarchivo solo con __version__ = <VERSION>param en el archivo. En el setup.pyarchivo, importe el __version__parámetro y coloque su valor en el setup.pyarchivo de esta manera: version=__version__
  2. Otra forma es usar solo un setup.pyarchivo con version=<CURRENT_VERSION>: CURRENT_VERSION está codificado.

Como no queremos cambiar manualmente la versión en el archivo cada vez que creamos una nueva etiqueta (lista para lanzar una nueva versión del paquete), podemos usar lo siguiente ...

Recomiendo mucho el paquete bumpversion . Lo he estado usando durante años para crear una versión.

comience agregando version=<VERSION>a su setup.pyarchivo si aún no lo tiene.

Deberías usar un script corto como este cada vez que topes una versión:

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

Luego agregue un archivo por repositorio llamado .bumpversion.cfg::

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

Nota:

  • Puede usar el __version__parámetro debajo del version.pyarchivo como se sugirió en otras publicaciones y actualizar el archivo de bumpversion de esta manera: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • Usted debe git commit o git resettodo en su cesión temporal, de lo contrario obtendrá un error de recompra sucio.
  • Asegúrese de que su entorno virtual incluya el paquete de bumpversion, sin él no funcionará.
Orán
fuente
@cmcginty Disculpe la demora, verifique mi respuesta ^^^: tenga en cuenta que debe confirmar git o restablecer todo en su repositorio y asegurarse de que su entorno virtual incluya el paquete de bumpversion, sin él no funcionará. Utiliza la última versión.
Orán
No tengo claro qué solución se sugiere aquí. ¿Está recomendando el seguimiento manual de la versión con version.pyo con ella bumpversion?
Stevoisiak
@StevenVascellaro Sugiero usar bumpversion, nunca use versiones manuales. Lo que intenté explicar es que puedes dirigir bumpversion para actualizar la versión en el archivo setup.py o mejor aún usarlo para actualizar el archivo version.py. Es una práctica más común actualizar el archivo version.py y tomar el __version__valor param en el archivo setup.py. Mi solución se usa en producción y es una práctica común. Nota: para ser claros, usar bumpversion como parte de un script es la mejor solución, colóquelo en su CI y será una operación automática.
Orán
-3

Si usa CVS (o RCS) y desea una solución rápida, puede usar:

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

(Por supuesto, el número de revisión será sustituido por CVS).

Esto le brinda una versión fácil de imprimir y una información de versión que puede usar para verificar que el módulo que está importando tiene al menos la versión esperada:

import my_module
assert my_module.__version_info__ >= (1, 1)
Martin Ibert
fuente
¿En qué archivo recomienda guardar guardar __version__? ¿Cómo se incrementaría el número de versión con esta solución?
Stevoisiak