python - para - ¿Cuál es la forma correcta de compartir la versión del paquete con setup.py y el paquete?
lista de modulos de python (7)
Con distutils
, setuptools
, etc., se especifica una versión del paquete en setup.py
:
# file: setup.py
...
setup(
name=''foobar'',
version=''1.0.0'',
# other attributes
)
Me gustaría poder acceder al mismo número de versión desde el paquete:
>>> import foobar
>>> foobar.__version__
''1.0.0''
Podría agregar __version__ = ''1.0.0''
a __init__.py de mi paquete, pero también me gustaría incluir importaciones adicionales en mi paquete para crear una interfaz simplificada para el paquete:
# file: __init__.py
from foobar import foo
from foobar.bar import Bar
__version__ = ''1.0.0''
y
# file: setup.py
from foobar import __version__
...
setup(
name=''foobar'',
version=__version__,
# other attributes
)
Sin embargo, estas importaciones adicionales pueden hacer que falle la instalación de foobar
si importan otros paquetes que aún no están instalados. ¿Cuál es la forma correcta de compartir la versión del paquete con setup.py y el paquete?
Configure la versión solo en setup.py
y lea su propia versión con pkg_resources
, consultando de manera efectiva los metadatos de setuptools
:
archivo: setup.py
setup(
name=''foobar'',
version=''1.0.0'',
# other attributes
)
archivo: __init__.py
from pkg_resources import get_distribution
__version__ = get_distribution(''foobar'').version
Para que esto funcione en todos los casos, donde podría terminar ejecutando esto sin haberlo instalado, pruebe DistributionNotFound
y la ubicación de distribución:
from pkg_resources import get_distribution, DistributionNotFound
import os.path
try:
_dist = get_distribution(''foobar'')
# Normalize case for Windows systems
dist_loc = os.path.normcase(_dist.location)
here = os.path.normcase(__file__)
if not here.startswith(os.path.join(dist_loc, ''foobar'')):
# not installed, but there is another version that *is*
raise DistributionNotFound
except DistributionNotFound:
__version__ = ''Please install this project with setup.py''
else:
__version__ = _dist.version
En función de la respuesta y los comentarios aceptados , esto es lo que terminé haciendo:
archivo: setup.py
setup(
name=''foobar'',
version=''1.0.0'',
# other attributes
)
archivo: __init__.py
from pkg_resources import get_distribution, DistributionNotFound
__project__ = ''foobar''
__version__ = None # required for initial installation
try:
__version__ = get_distribution(__project__).version
except DistributionNotFound:
VERSION = __project__ + ''-'' + ''(local)''
else:
VERSION = __project__ + ''-'' + __version__
from foobar import foo
from foobar.bar import Bar
Explicación:
__project__
es el nombre del proyecto para instalar, que puede ser diferente del nombre del paqueteVERSION
es lo que muestro en mis interfaces de línea de comandos cuando se solicita--version
las importaciones adicionales (para la interfaz de paquete simplificada) solo se producen si el proyecto se ha instalado realmente
Estoy de acuerdo con la filosofía de @ stefano-m sobre:
Tener version = "xyz" en la fuente y analizarlo en setup.py es definitivamente la solución correcta, en mi humilde opinión. Mucho mejor que (al revés) dependiendo de la magia del tiempo de ejecución.
Y esta respuesta se deriva de la respuesta de @ zero-piraeus. El punto es "no use importaciones en setup.py, en cambio, lea la versión de un archivo".
Uso la expresión regular para analizar la __version__
para que no tenga que ser la última línea de un archivo dedicado en absoluto. De hecho, todavía pongo la __version__
sola fuente de verdad dentro de la __version__
mi proyecto.
Jerarquía de carpetas (solo archivos relevantes):
package_root/
|- main_package/
| `- __init__.py
`- setup.py
main_package/__init__.py
:
# You can have other dependency if you really need to
from main_package.some_module import some_function_or_class
# Define your version number in the way you mother told you,
# which is so straightforward that even your grandma will understand.
__version__ = "1.2.3"
__all__ = (
some_function_or_class,
# ... etc.
)
setup.py
:
from setuptools import setup
import re, io
__version__ = re.search(
r''__version__/s*=/s*[/'"]([^/'"]*)[/'"]'', # It excludes inline comment too
io.open(''main_package/__init__.py'', encoding=''utf_8_sig'').read()
).group(1)
# The beautiful part is, I don''t even need to check exceptions here.
# If something messes up, let the build process fail noisy, BEFORE my release!
setup(
version=__version__,
# ... etc.
)
... que todavía no es ideal ... pero funciona.
Y por cierto, en este punto puedes probar tu nuevo juguete de esta manera:
python setup.py --version
1.2.3
PD: Este documento oficial de empaque de Python (y su mirror ) describe más opciones. Su primera opción también es usar regex. (Depende de la expresión regular exacta que use, puede o no manejar comillas dentro de la cadena de versión. Sin embargo, en general no es un gran problema).
PPS: la solución en ADAL Python ahora se transfiere a esta respuesta.
Hay varios métodos propuestos en las guías de Packaging en python.org
.
La respuesta aceptada requiere que el paquete haya sido instalado. En mi caso, necesitaba extraer los parámetros de instalación (incluida __version__
) de la fuente setup.py
. Encontré una solución directa y simple mientras miraba las pruebas del paquete setuptools . Buscando más información sobre el atributo _setup_stop_after
me llevó a una publicación anterior de la lista de correo que menciona distutils.core.run_setup
, que me lleva a los documentos reales necesarios . Después de todo eso, aquí está la solución simple:
archivo setup.py
:
from setuptools import setup
setup(name=''funniest'',
version=''0.1'',
description=''The funniest joke in the world'',
url=''http://github.com/storborg/funniest'',
author=''Flying Circus'',
author_email=''[email protected]'',
license=''MIT'',
packages=[''funniest''],
zip_safe=False)
file extract.py
:
from distutils.core import run_setup
dist = run_setup(''./setup.py'', stop_after=''init'')
dist.get_version()
No creo que haya una respuesta canónica a esto, pero mi método (ya sea copiado directamente o ligeramente modificado a partir de lo que he visto en varios otros lugares) es el siguiente:
Jerarquía de carpetas (solo archivos relevantes):
package_root/
|- main_package/
| |- __init__.py
| `- _version.py
`- setup.py
main_package/_version.py
:
"""Version information."""
# The following line *must* be the last in the module, exactly as formatted:
__version__ = "1.0.0"
main_package/__init__.py
:
"""Something nice and descriptive."""
from main_package.some_module import some_function_or_class
# ... etc.
from main_package._version import __version__
__all__ = (
some_function_or_class,
# ... etc.
)
setup.py
:
from setuptools import setup
setup(
version=open("main_package/_version.py").readlines()[-1].split()[-1].strip("/"''"),
# ... etc.
)
... que es feo como el pecado ... pero funciona, y lo he visto o algo así en paquetes distribuidos por personas que esperaría saber una mejor manera si hubiera una.
Pon __version__
en your_pkg/__init__.py
, y analiza en setup.py
usando ast
:
import ast
import importlib.util
from pkg_resources import safe_name
PKG_DIR = ''my_pkg''
def find_version():
"""Return value of __version__.
Reference: https://.com/a/42269185/
"""
file_path = importlib.util.find_spec(PKG_DIR).origin
with open(file_path) as file_obj:
root_node = ast.parse(file_obj.read())
for node in ast.walk(root_node):
if isinstance(node, ast.Assign):
if len(node.targets) == 1 and node.targets[0].id == "__version__":
return node.value.s
raise RuntimeError("Unable to find version string.")
setup(name=safe_name(PKG_DIR),
version=find_version(),
packages=[PKG_DIR],
...
)
Si usa Python <3.4, tenga en cuenta que importlib.util.find_spec
no está disponible. Además, no se puede confiar en que cualquier importlib
de importlib
esté disponible para setup.py
. En este caso, use:
import os
file_path = os.path.join(os.path.dirname(__file__), PKG_DIR, ''__init__.py'')