programa para modulos lista crear python setuptools distutils

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 paquete

  • VERSION 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.



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