Respuestas:
Para recuperar la versión desde el interior de su paquete en tiempo de ejecución (lo que parece estar preguntando realmente), puede usar:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
Si desea ir al revés (que parece ser lo que otros autores de respuestas aquí parecen haber pensado que estaba preguntando), coloque la cadena de versión en un archivo separado y lea el contenido de ese archivo setup.py
.
Puede hacer una versión.py en su paquete con una __version__
línea, luego leerla desde setup.py usando execfile('mypackage/version.py')
, para que se establezca __version__
en el espacio de nombres setup.py.
Si desea una forma mucho más simple que funcione con todas las versiones de Python e incluso con lenguajes que no sean de Python que puedan necesitar acceso a la cadena de versión:
Almacene la cadena de versión como el único contenido de un archivo de texto plano, llamado eg VERSION
, y lea ese archivo durante setup.py
.
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
El mismo VERSION
archivo funcionará exactamente igual en cualquier otro programa, incluso los que no sean Python, y solo necesita cambiar la cadena de versión en un lugar para todos los programas.
Por cierto, NO importe su paquete desde su setup.py como se sugiere en otra respuesta aquí: parecerá funcionar para usted (porque ya tiene instaladas las dependencias de su paquete), pero causará estragos en los nuevos usuarios de su paquete , ya que no podrán instalar su paquete sin instalar primero manualmente las dependencias.
execfile
funciona muy bien ... pero (lamentablemente) no funciona con Python 3.
with open('mypackage/version.py') as f: exec(f.read())
en lugar de execfile('mypackage/version.py')
. (De stackoverflow.com/a/437857/647002 )
mymodule
Imagina esta configuración:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
Luego imagine un escenario habitual en el que tenga dependencias y se setup.py
vea así:
setup(...
install_requires=['dep1','dep2', ...]
...)
Y un ejemplo __init__.py
:
from mymodule.myclasses import *
from mymodule.version import __version__
Y por ejemplo myclasses.py
:
# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2
mymodule
durante la configuraciónSi sus setup.py
importaciones, mymodule
entonces durante la configuración , probablemente obtendrá un ImportError
. Este es un error muy común cuando su paquete tiene dependencias. Si su paquete no tiene otras dependencias que las incorporadas, puede estar seguro; Sin embargo, esta no es una buena práctica. La razón de esto es que no es a prueba de futuro; diga mañana que su código necesita consumir alguna otra dependencia.
__version__
?Si hardcode __version__
de setup.py
entonces puede no coincidir con la versión que le enviará en su módulo. Para ser coherente, lo pondría en un lugar y lo leería desde el mismo lugar cuando lo necesite. Al import
usarlo puede obtener el problema # 1.
setuptools
Se podría utilizar una combinación de open
, exec
y proporcionar un diccionario para exec
las variables add:
# setup.py
from setuptools import setup, find_packages
from distutils.util import convert_path
main_ns = {}
ver_path = convert_path('mymodule/version.py')
with open(ver_path) as ver_file:
exec(ver_file.read(), main_ns)
setup(...,
version=main_ns['__version__'],
...)
Y en mymodule/version.py
exponer la versión:
__version__ = 'some.semantic.version'
De esta manera, la versión se envía con el módulo, y no tiene problemas durante la instalación al intentar importar un módulo que tiene dependencias faltantes (aún no se ha instalado).
La mejor técnica es definir __version__
en el código de su producto, luego importarlo en setup.py desde allí. Esto le da un valor que puede leer en su módulo en ejecución, y solo tiene un lugar para definirlo.
Los valores en setup.py no están instalados y setup.py no se queda después de la instalación.
Lo que hice (por ejemplo) en coverage.py:
# coverage/__init__.py
__version__ = "3.2"
# setup.py
from coverage import __version__
setup(
name = 'coverage',
version = __version__,
...
)
ACTUALIZACIÓN (2017): coverage.py ya no se importa para obtener la versión. Importar su propio código puede hacer que sea desinstalable, porque su código de producto intentará importar dependencias, que aún no están instaladas, porque setup.py es lo que las instala.
__version__
que contiene está roto de alguna manera, su importación también se romperá. Python no sabe cómo interpretar solo las declaraciones que desea. @pjeby tiene razón: si su módulo necesita importar otros módulos, es posible que aún no estén instalados y será caótico. Esta técnica funciona si tiene cuidado de que la importación no genere una larga cadena de otras importaciones.
pip install <packagename>
, me sale el siguiente error: ImportError: No module named <packagename>
. ¡AVISE a sus lectores que no puede ejecutar archivos setup.py como este en entornos donde el paquete no está instalado!
No estaba contento con estas respuestas ... no quería requerir herramientas de configuración, ni crear un módulo completamente separado para una sola variable, así que se me ocurrió esto.
Para cuando esté seguro de que el módulo principal está en estilo pep8 y seguirá así:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
Si desea ser más cuidadoso y usar un analizador real:
import ast
version = '0.30.unknown2'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
version = ast.parse(line).body[0].value.s
break
setup.py es algo así como un módulo desechable, así que no es un problema si es un poco feo.
Actualización: Es muy gracioso porque me he alejado de esto en los últimos años y comenzó a usar un archivo separado en el paquete de llamada meta.py
. Puse muchos metadatos allí que quizás quiera cambiar con frecuencia. Entonces, no solo por un valor.
ast.get_docstring()
con algunos .split('\n')[0].strip()
etc. para completar automáticamente description
desde la fuente. Una cosa menos para mantener sincronizado
Cree un archivo en su árbol de origen, por ejemplo, en su base de datos / su paquete / _version.py. Deje que el archivo contenga una sola línea de código, como esta:
__version__ = "1.1.0-r4704"
Luego, en su setup.py, abra ese archivo y analice el número de versión de esta manera:
verstr = "desconocido" tratar: verstrline = open ('yourpackage / _version.py', "rt"). read () excepto EnvironmentError: Pase # Bien, no hay un archivo de versión. más: VSRE = r "^ __ versión__ = ['\"] ([^' \ "] *) ['\"] " mo = re.search (VSRE, verstrline, re.M) si mo: verstr = mo.group (1) más: raise RuntimeError ("no se puede encontrar la versión en su paquete / _version.py")
Finalmente, en yourbasedir/yourpackage/__init__.py
import _version así:
__version__ = "desconocido" tratar: de _version import __version__ excepto ImportError: # Nos estamos ejecutando en un árbol que no tiene un _version.py, por lo que no sabemos cuál es nuestra versión. pasar
Un ejemplo de código que hace esto es el paquete "pyutil" que mantengo. (Consulte PyPI o búsqueda de google: stackoverflow no me permite incluir un hipervínculo en esta respuesta).
@pjeby tiene razón en que no debe importar su paquete desde su propio setup.py. Eso funcionará cuando lo pruebe creando un nuevo intérprete de Python y ejecutando setup.py en primer lugar: python setup.py
pero hay casos en que no funcionará. Esto se debe a import youpackage
que no significa leer el directorio de trabajo actual para un directorio llamado "yourpackage", significa buscar en la actual sys.modules
una clave "yourpackage" y luego hacer varias cosas si no está allí. Por lo tanto, siempre funciona cuando lo hace python setup.py
porque tiene un nuevo, vacío sys.modules
, pero esto no funciona en general.
Por ejemplo, ¿qué sucede si py2exe está ejecutando su setup.py como parte del proceso de empaquetar una aplicación? He visto un caso como este donde py2exe pondría el número de versión incorrecto en un paquete porque el paquete estaba obteniendo su número de versión deimport myownthing
en su setup.py, pero anteriormente se había importado una versión diferente de ese paquete durante la ejecución de py2exe. Del mismo modo, ¿qué sucede si setuptools, easy_install, distribution o distutils2 intentan construir su paquete como parte de un proceso de instalación de un paquete diferente que depende del suyo? Luego, si su paquete es importante en el momento en que se está evaluando su setup.py, o si ya hay una versión de su paquete que se importó durante la vida de este intérprete de Python, o si la importación de su paquete requiere la instalación de otros paquetes primero , o tiene efectos secundarios, puede cambiar los resultados. He tenido varias dificultades para tratar de reutilizar los paquetes de Python que causaron problemas para herramientas como py2exe y setuptools porque su setup.py importa el paquete para encontrar su número de versión.
Por cierto, esta técnica funciona muy bien con herramientas para crear automáticamente el yourpackage/_version.py
archivo para usted, por ejemplo, leyendo su historial de control de revisiones y escribiendo un número de versión basado en la etiqueta más reciente en el historial de control de revisiones. Aquí hay una herramienta que hace eso para darcs: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst y aquí hay un fragmento de código que hace lo mismo para git: http: // github .com / warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py # L34
Esto también debería funcionar, utilizando expresiones regulares y dependiendo de los campos de metadatos para tener un formato como este:
__fieldname__ = 'value'
Use lo siguiente al comienzo de su setup.py:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
Después de eso, puede usar los metadatos en su script de esta manera:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
Con una estructura como esta:
setup.py
mymodule/
/ __init__.py
/ version.py
/ myclasses.py
donde version.py contiene:
__version__ = 'version_string'
Puedes hacer esto en setup.py :
import sys
sys.path[0:0] = ['mymodule']
from version import __version__
Esto no causará ningún problema con las dependencias que tenga en su mymodule / __ init__.py
Para evitar importar un archivo (y así ejecutar su código), uno podría analizarlo y recuperar el version
atributo del árbol de sintaxis:
# assuming 'path' holds the path to the file
import ast
with open(path, 'rU') as file:
t = compile(file.read(), path, 'exec', ast.PyCF_ONLY_AST)
for node in (n for n in t.body if isinstance(n, ast.Assign)):
if len(node.targets) == 1:
name = node.targets[0]
if isinstance(name, ast.Name) and \
name.id in ('__version__', '__version_info__', 'VERSION'):
v = node.value
if isinstance(v, ast.Str):
version = v.s
break
if isinstance(v, ast.Tuple):
r = []
for e in v.elts:
if isinstance(e, ast.Str):
r.append(e.s)
elif isinstance(e, ast.Num):
r.append(str(e.n))
version = '.'.join(r)
break
Este código intenta encontrar la asignación __version__
o VERSION
en el nivel superior del retorno del módulo es el valor de cadena. El lado derecho puede ser una cadena o una tupla.
Hay mil maneras de pelar un gato, aquí está el mío:
# Copied from (and hacked):
# https://github.com/pypa/virtualenv/blob/develop/setup.py#L42
def get_version(filename):
import os
import re
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, filename))
version_file = f.read()
f.close()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
Limpieza https://stackoverflow.com/a/12413800 de @ gringo-suave:
from itertools import ifilter
from os import path
from ast import parse
with open(path.join('package_name', '__init__.py')) as f:
__version__ = parse(next(ifilter(lambda line: line.startswith('__version__'),
f))).body[0].value.s
Ahora, esto es asqueroso y necesita un poco de refinamiento (incluso puede haber una llamada de miembro descubierta en pkg_resources que me perdí), pero simplemente no veo por qué esto no funciona, ni por qué nadie lo ha sugerido hasta la fecha (Googlear ha no subió esto) ... tenga en cuenta que esto es Python 2.x, y requeriría requerir pkg_resources (suspiro):
import pkg_resources
version_string = None
try:
if pkg_resources.working_set is not None:
disto_obj = pkg_resources.working_set.by_key.get('<my pkg name>', None)
# (I like adding ", None" to gets)
if disto_obj is not None:
version_string = disto_obj.version
except Exception:
# Do something
pass
Quisimos poner la meta información sobre nuestro paquete pypackagery
en __init__.py
, pero no pudo ya que cuenta con dependencias de terceros como PJ Eby ya se ha señalado (véase su respuesta y la advertencia con respecto a la condición de carrera).
Lo resolvimos creando un módulo separado pypackagery_meta.py
que contiene solo la metainformación:
"""Define meta information about pypackagery package."""
__title__ = 'pypackagery'
__description__ = ('Package a subset of a monorepo and '
'determine the dependent packages.')
__url__ = 'https://github.com/Parquery/pypackagery'
__version__ = '1.0.0'
__author__ = 'Marko Ristin'
__author_email__ = 'marko.ristin@gmail.com'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'
luego importó la metainformación en packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
y finalmente lo usé en setup.py
:
import pypackagery_meta
setup(
name=pypackagery_meta.__title__,
version=pypackagery_meta.__version__,
description=pypackagery_meta.__description__,
long_description=long_description,
url=pypackagery_meta.__url__,
author=pypackagery_meta.__author__,
author_email=pypackagery_meta.__author_email__,
# ...
py_modules=['packagery', 'pypackagery_meta'],
)
Debe incluir pypackagery_meta
en su paquete con py_modules
el argumento de configuración. De lo contrario, no puede importarlo durante la instalación, ya que la distribución empaquetada carecería de él.
Simple y directo, cree un archivo llamado source/package_name/version.py
con los siguientes contenidos:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
Luego, en su archivo source/package_name/__init__.py
, importa la versión para que otras personas la usen:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
Ahora puedes poner esto setup.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
version_file = open( filepath )
__version__ ,= re.findall( '__version__ = "(.*)"', version_file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
finally:
version_file.close()
Probado esto con Python 2.7
, 3.3
, 3.4
, 3.5
, 3.6
y 3.7
en Linux, Windows y Mac OS. Utilicé en mi paquete que tiene pruebas de integración y unidad para todas estas plataformas. Puedes ver los resultados desde .travis.yml
y appveyor.yml
aquí:
Una versión alternativa está usando el administrador de contexto:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
with open( filepath ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
También puede usar el codecs
módulo para manejar errores unicode tanto en Python 2.7
como en3.6
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
try:
filepath = 'source/package_name/version.py'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
Si está escribiendo un módulo Python al 100% en C / C ++ usando Extensiones Python C, puede hacer lo mismo, pero usando C / C ++ en lugar de Python.
En este caso, cree lo siguiente setup.py
:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
from setuptools import setup, Extension
try:
filepath = 'source/version.h'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
setup(
name = 'package_name',
version = __version__,
package_data = {
'': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
},
ext_modules = [
Extension(
name = 'package_name',
sources = [
'source/file.cpp',
],
include_dirs = ['source'],
)
],
)
Que lee la versión del archivo version.h
:
const char* __version__ = "1.0.12";
Pero, no olvide crear el MANIFEST.in
para incluir el version.h
archivo:
include README.md
include LICENSE.txt
recursive-include source *.h
Y está integrado en la aplicación principal con:
#include <Python.h>
#include "version.h"
// create the module
PyMODINIT_FUNC PyInit_package_name(void)
{
PyObject* thismodule;
...
// https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue
PyObject_SetAttrString( thismodule, "__version__", Py_BuildValue( "s", __version__ ) );
...
}
Referencias
Implemente el paquete en el servidor y la convención de nomenclatura de archivos para paquetes de índices:
ejemplo para la conversión de versión dinámica de pip:
ganar:
Mac:
from setuptools_scm import get_version
def _get_version():
dev_version = str(".".join(map(str, str(get_version()).split("+")[0]\
.split('.')[:-1])))
return dev_version
Encuentre el ejemplo setup.py llama a la versión de pip dinámica que coincide con git commit
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
Estoy usando una variable de entorno como la siguiente
VERSIÓN = 0.0.0 python setup.py sdist bdist_wheel
En setup.py
import os
setup(
version=os.environ['VERSION'],
...
)
Para verificar la coherencia con la versión del empaquetador, estoy usando el siguiente script.
PKG_VERSION=`python -c "import pkg; print(pkg.__version__)"`
if [ $PKG_VERSION == $VERSION ]; then
python setup.py sdist bdist_wheel
else
echo "Package version differs from set env variable"
fi