Forma estándar de incrustar versión en paquete de python?
Pregunta
Hay una manera estándar de asociado de la cadena de la versión con un paquete de python, de tal manera que yo podría hacer lo siguiente?
import foo
print foo.version
Me imagino que hay alguna manera de recuperar los datos sin ningún tipo de extra codificar, ya que los pequeños/grandes cadenas se especifican en setup.py
ya.La solución alternativa que he encontrado para tener import __version__
en mi foo/__init__.py
y luego han __version__.py
generado por setup.py
.
Solución
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 <=>, y esto también se usa en lotes de módulos de terceros , entonces es el cuasi-estándar.
Por lo general, <=> es una cadena, pero a veces también es un flotador o una tupla.
Editar: según lo mencionado por S.Lott (¡Gracias!), PEP 8 lo dice explícitamente:
Contabilidad de versiones
Si tiene que tener Subversion, CVS o RCS crud en su archivo fuente, hazlo de la siguiente manera.
__version__ = "$Revision: 63990 $" # $Source$
Estas líneas deben incluirse después de la cadena de documentación del módulo, antes cualquier otro código, separado por una línea en blanco arriba y abajo.
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 esta norma).
Otros consejos
Utilizo un solo archivo _version.py
como " una vez canónico lugar " para almacenar información de la versión:
-
Proporciona un atributo
__version__
. -
Proporciona la versión estándar de metadatos. Por lo tanto, será detectado por
pkg_resources
u otras herramientas que analizan los metadatos del paquete (EGG-INFO y / o PKG-INFO, PEP 0345). -
No importa su paquete (o cualquier otra cosa) al compilar su paquete, lo que puede causar problemas en algunas situaciones. (Consulte los comentarios a continuación sobre los problemas que esto puede causar).
-
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 " un 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 así 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,))
Entonces su setup.py pasa esa cadena como el valor de " version " argumento para 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 desde myniftyapp/__init__.py
de esta manera:
from _version import __version__
Aquí está un ejemplo de esta técnica que Lo 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í está código de ejemplo de importación la versión .
Si ve algún problema con este enfoque, hágamelo saber.
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.
Empecé a usar el paquete pbr
para tratar el control de versiones en mis paquetes. Si está utilizando git como su SCM, esto se ajustará a su flujo de trabajo como magia, ahorrando sus semanas de trabajo (se sorprenderá de lo complejo que puede ser el problema).
A partir de hoy pbr está clasificado como el paquete de python más utilizado en el puesto número 11 y alcanzó este nivel no No incluye ningún truco sucio: era solo uno: solucionar un problema de empaquetado común de una manera muy simple.
yourpackage/version.py
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 swiching embalaje a 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 es necesario preocuparse por lo que sucede cuando no tiene un repositorio git porque pbr hace " compila " y guarde en caché la versión cuando empaquete o instale las aplicaciones, para que no haya dependencia del tiempo de ejecución en git.
Solución anterior
Aquí está la mejor solución que he visto hasta ahora y también explica por qué:
Dentro de 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'
Dentro de setup.py
:
from .version import __version__
Dentro de <=>:
exec(open('yourpackage/version.py').read())
setup(
...
version=__version__,
...
Si conoce otro enfoque que parece ser mejor, hágamelo saber.
Según el PEP 396 (números de versión del módulo) diferido, hay Una forma propuesta de hacer esto. 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 atributo
__version__
.4) Para los módulos que viven dentro de un paquete de espacio de nombres, el módulo DEBE incluir el atributo <=>. El paquete de espacio de nombres en sí NO DEBE incluir su propio atributo <=>.
5) El valor del atributo <=> DEBE ser una cadena.
A pesar de que este es, probablemente, demasiado tarde, hay un poco más simple alternativa a la respuesta anterior:
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(Y sería bastante simple para convertir incremento automático de las porciones de los números de versión a una cadena con str()
.)
Por supuesto, de lo que he visto, la gente tiende a usar algo como la ya mencionada versión cuando se utiliza __version_info__
, y como este tipo de almacén es como una tupla de enteros;sin embargo, no acabo de ver el punto de hacerlo, como me cabe duda de que hay situaciones donde se iba a realizar operaciones matemáticas de suma y resta en las porciones de los números de versión para cualquier propósito, además de la curiosidad o de auto-incremento (e incluso entonces, int()
y str()
puede ser utilizado con bastante facilidad).(Por otro lado, existe la posibilidad de que el código de otra persona esperando un numérica tupla en lugar de una cadena de tupla y no pudiendo por tanto.)
Este es, por supuesto, mi propio punto de vista, y yo con mucho gusto a los demás' entrada sobre el uso de un número de tupla.
Como shezi me recordó, (léxico) las comparaciones de cadenas de número no necesariamente tienen el mismo resultado directo comparaciones numéricas;ceros a la izquierda / a para que.Así que al final, almacenando __version_info__
(o lo que sería llamado) como una tupla de valores enteros permitiría más eficiente de la versión comparaciones.
Uso un archivo JSON en el directorio del paquete. Esto se ajusta a los requisitos de Zooko.
Dentro de pkg_dir/pkg_info.json
:
{"version": "0.1.0"}
Dentro de 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 de 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. yo
me gusta usar JSON porque puedo automatizar la administración de metadatos.
Muchas de estas soluciones, aquí ignorar git
etiquetas de versión que todavía significa que tienen que seguir la pista de la versión en varios lugares (malo).Me acerqué a este con los siguientes objetivos:
- Derivan todas de la versión de python referencias de un tag en el
git
repo - Automatizar
git tag
/push
ysetup.py upload
pasos con un solo comando, que no toma los insumos.
Cómo funciona:
A partir de una
make release
comando, la última versión de etiquetado en el repositorio git que se encuentra y se incrementa.La etiqueta es empujado de nuevo aorigin
.El
Makefile
almacena la versión ensrc/_version.py
donde va a ser leído porsetup.py
y también se incluyó en la liberación. No comprobar_version.py
en el control de código fuente!setup.py
comando lee la nueva versión de la cadena depackage.__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 release
destino siempre incrementos de la versión 3 dígitos, pero se puede utilizar el next_minor_ver
o next_major_ver
el incremento de los otros dígitos.Los comandos se basan en la versionbump.py
secuencia de comandos que se registró en la raíz de la 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
Esto hace el trabajo pesado de cómo procesar y incrementar el número de versión de git
.
__init__.py
El my_module/_version.py
archivo se importa en my_module/__init__.py
.Poner estática instalar config aquí que desea 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 de la my_module
el mó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 deberás tener al menos una versión de la etiqueta en la repo para empezar.
git tag -a v0.0.1
No parece ser una forma estándar para integrar una cadena de versión en un paquete de python.La mayoría de los paquetes que he visto usar algunas variantes de su solución, es decir,eitner
Incrustar la versión en
setup.py
y hansetup.py
generar un módulo (por ejemplo,version.py
) que contiene sólo información de la versión, que es importado por el paquete, oA la inversa:poner la información de la versión en su propio paquete, y de importación que para establecer la versión en
setup.py
También vale la pena señalar que además de __version__
ser un semi-estándar. en Python también lo es __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 puede obtener la cadena <=> de un archivo, o lo que sea.
También vi otro estilo:
>>> django.VERSION
(1, 1, 0, 'final', 0)
arrow lo maneja de una manera interesante.
En arrow/__init__.py
:
__version__ = '0.8.0'
VERSION = __version__
En 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__'),
# [...]
)
Para lo que vale, si está usando NumPy distutils, numpy.distutils.misc_util.Configuration
tiene un make_svn_version_py()
método que incorpora el número de revisión dentro de package.__svn_version__
en la variable version
.
- El uso de un
version.py
archivo sólo con__version__ = <VERSION>
param en el archivo.En elsetup.py
importación de archivos de la__version__
param y poner su valor en elsetup.py
archivo como este:version=__version__
- Otra forma es utilizar un
setup.py
archivo conversion=<CURRENT_VERSION>
- el CURRENT_VERSION está codificado.
Ya no queremos cambiar manualmente la versión en el archivo cada vez que creamos una nueva etiqueta (listo para lanzar una nueva versión de paquete), podemos utilizar la siguiente..
Recomiendo altamente bumpversion paquete.He estado usando durante años a golpe de una versión.
comience por agregar version=<VERSION>
a su setup.py
archivo si no lo tiene ya.
Usted debe usar un breve script como este cada vez que das una versión:
bumpversion (patch|minor|major) - choose only one option
git push
git push --tags
A continuación, agregue un archivo por repo 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 utilizar
__version__
parámetro bajoversion.py
archivo de como se sugirió en otros posts y actualizaciones de la bumpversion archivo como este:[bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
- Usted debe
git commit
ogit reset
todo en su repo, de lo contrario obtendrá un sucio repo de error. - Asegúrese de que su entorno virtual incluye el paquete de bumpversion, sin que esto no va a funcionar.
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 proporciona una versión para 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)