Secuencia de comandos posterior a la instalación con herramientas de configuración de Python

97

¿Es posible especificar un archivo de secuencia de comandos de Python posterior a la instalación como parte del archivo setuptools setup.py para que un usuario pueda ejecutar el comando?

python setup.py install

en un archivo de archivo de proyecto local, o

pip install <name>

para un proyecto PyPI y el script se ejecutará al finalizar la instalación de las herramientas de configuración estándar? Estoy buscando realizar tareas posteriores a la instalación que se puedan codificar en un solo archivo de secuencia de comandos de Python (por ejemplo, entregar un mensaje personalizado posterior a la instalación al usuario, extraer archivos de datos adicionales de un repositorio de origen remoto diferente).

Me encontré con esta respuesta SO de hace varios años que aborda el tema y parece que el consenso en ese momento era que necesita crear un subcomando de instalación. Si ese sigue siendo el caso, ¿sería posible que alguien proporcione un ejemplo de cómo hacer esto para que no sea necesario que el usuario ingrese un segundo comando para ejecutar el script?

Chris Simpkins
fuente
4
Espero automatizar la ejecución del script en lugar de requerir que el usuario ingrese un segundo comando. ¿Alguna idea?
Chris Simpkins
1
Esto podría ser lo que estás buscando: stackoverflow.com/questions/17806485/…
limp_chimp
1
¡Gracias! Lo comprobaré
Chris Simpkins
1
Si necesita esto, parece que esta publicación de blog que encontré en un Google rápido sería útil. (Consulte también Ampliación y reutilización de herramientas de configuración en los documentos)
abarnert
1
@Simon Bueno, estás viendo un comentario de hace 4 años sobre algo que probablemente no sea lo que quiere alguien con este problema, así que no puedes esperar que sea monitoreado y actualizado. Si esta fuera una respuesta, valdría la pena el esfuerzo de encontrar nuevos recursos para reemplazarlos, pero no lo es. Si necesita la información desactualizada, siempre puede usar Wayback Machine, o puede buscar la sección equivalente en los documentos actuales.
abarnert

Respuestas:

92

Nota: La siguiente solución solo funciona cuando se instala un archivo comprimido o tarball de distribución de código fuente, o se instala en modo editable desde un árbol de código fuente. Será no trabajar cuando se instala desde una rueda binario ( .whl)


Esta solución es más transparente:

Hará algunas adiciones setup.pyy no es necesario un archivo adicional.

También debe considerar dos posinstalaciones diferentes; uno para el modo de desarrollo / editable y el otro para el modo de instalación.

Agregue estas dos clases que incluyen su secuencia de comandos posterior a la instalación para setup.py:

from setuptools import setup
from setuptools.command.develop import develop
from setuptools.command.install import install


class PostDevelopCommand(develop):
    """Post-installation for development mode."""
    def run(self):
        develop.run(self)
        # PUT YOUR POST-INSTALL SCRIPT HERE or CALL A FUNCTION

class PostInstallCommand(install):
    """Post-installation for installation mode."""
    def run(self):
        install.run(self)
        # PUT YOUR POST-INSTALL SCRIPT HERE or CALL A FUNCTION

e inserte el cmdclassargumento para setup()funcionar en setup.py:

setup(
    ...

    cmdclass={
        'develop': PostDevelopCommand,
        'install': PostInstallCommand,
    },

    ...
)

Incluso puede llamar a comandos de shell durante la instalación, como en este ejemplo que hace la preparación previa a la instalación:

from setuptools import setup
from setuptools.command.develop import develop
from setuptools.command.install import install
from subprocess import check_call


class PreDevelopCommand(develop):
    """Pre-installation for development mode."""
    def run(self):
        check_call("apt-get install this-package".split())
        develop.run(self)

class PreInstallCommand(install):
    """Pre-installation for installation mode."""
    def run(self):
        check_call("apt-get install this-package".split())
        install.run(self)


setup(
    ...

PD: no hay puntos de entrada de preinstalación disponibles en setuptools. Lea esta discusión si se pregunta por qué no hay ninguno.

mertyildiran
fuente
Parece más limpio que los demás, pero ¿no ejecuta esto el código personalizado antes del installcomando real ?
raphinesse
7
Depende de usted: si primero llama runal padre , su comando es una instalación posterior, de lo contrario, es una instalación previa. Actualicé la respuesta para reflejar esto.
kynan
1
al usar esta solución, parece que las install_requiresdependencias se ignoran
ealfonso
7
Esto no funcionó para mí con pip3. El script de instalación se ejecutó al publicar el paquete, pero no al instalarlo.
Eric Wiener
1
@JuanAntonioOrozco He actualizado el enlace roto usando Wayback Machine. No sé por qué está roto en este mismo momento. Quizás algo anda mal con bugs.python.org en este momento.
mertyildiran
14

Nota: La siguiente solución solo funciona cuando se instala un archivo comprimido o tarball de distribución de código fuente, o se instala en modo editable desde un árbol de código fuente. Será no trabajar cuando se instala desde una rueda binario ( .whl)


Esta es la única estrategia que me ha funcionado cuando el script posterior a la instalación requiere que las dependencias del paquete ya se hayan instalado:

import atexit
from setuptools.command.install import install


def _post_install():
    print('POST INSTALL')


class new_install(install):
    def __init__(self, *args, **kwargs):
        super(new_install, self).__init__(*args, **kwargs)
        atexit.register(_post_install)


setuptools.setup(
    cmdclass={'install': new_install},
Apalala
fuente
¿Por qué registra un atexitcontrolador en lugar de simplemente llamar a la función posterior a la instalación después del paso de instalación?
kynan
1
@kynan Porque setuptoolsestá bastante poco documentado. Otros ya han modificado sus respuestas a estas preguntas y respuestas con las soluciones correctas.
Apalala
3
Bueno, las otras respuestas no me funcionan: o el script posterior a la instalación no se ejecuta o las dependencias ya no se manejan. Hasta ahora, me ceñiré atexity no redefiniré install.run()(esta es la razón por la que las dependencias ya no se manejan). Además, para conocer el directorio de instalación, he puesto _post_install()como método de new_install, qué me permite acceder self.install_pureliby self.install_platlib(no sé cuál usar, pero self.install_libestá mal, extrañamente).
zezollo
2
También estaba teniendo problemas con las dependencias y atexit funciona para mí
ealfonso
7
Ninguno de los métodos aquí parece funcionar con ruedas. Las ruedas no ejecutan setup.py, por lo tanto, los mensajes solo se muestran al compilar, no al instalar el paquete.
JCGB
7

Nota: La siguiente solución solo funciona cuando se instala un archivo comprimido o tarball de distribución de código fuente, o se instala en modo editable desde un árbol de código fuente. Será no trabajar cuando se instala desde una rueda binario ( .whl)


Una solución podría ser incluir un directorio post_setup.pyin setup.py. post_setup.pycontendrá una función que realiza la instalación posterior y setup.pysolo la importará y ejecutará en el momento adecuado.

En setup.py:

from distutils.core import setup
from distutils.command.install_data import install_data

try:
    from post_setup import main as post_install
except ImportError:
    post_install = lambda: None

class my_install(install_data):
    def run(self):
        install_data.run(self)
        post_install()

if __name__ == '__main__':
    setup(
        ...
        cmdclass={'install_data': my_install},
        ...
    )

En post_setup.py:

def main():
    """Do here your post-install"""
    pass

if __name__ == '__main__':
    main()

Con la idea común de iniciar setup.pydesde su directorio, podrá importar, de lo post_setup.pycontrario, lanzará una función vacía.

En post_setup.py, la if __name__ == '__main__':declaración le permite iniciar manualmente la instalación posterior desde la línea de comandos.

zulú
fuente
4
En mi caso, anular run()hace que las dependencias del paquete no se instalen.
Apalala
1
@Apalala eso fue porque cmdclassse reemplazó el mal , lo he arreglado.
kynan
1
Ah, finalmente, encontramos la respuesta correcta. ¿Cómo es que las respuestas incorrectas obtienen tantos votos en StackOverflow? De hecho, debe ejecutar su post_install() búsqueda, de lo install_data.run(self)contrario, se perderán algunas cosas. Como data_filesal menos. Gracias kynan.
personal_cloud
1
No me funciona. Supongo que, por alguna razón, el comando install_datano se ejecuta en mi caso. Entonces, ¿no tiene atexitla ventaja de garantizar que el script posterior a la instalación se ejecutará al final, en cualquier situación?
zezollo
3

Combinando las respuestas de @Apalala, @Zulu y @mertyildiran; esto funcionó para mí en un entorno Python 3.5:

import atexit
import os
import sys
from setuptools import setup
from setuptools.command.install import install

class CustomInstall(install):
    def run(self):
        def _post_install():
            def find_module_path():
                for p in sys.path:
                    if os.path.isdir(p) and my_name in os.listdir(p):
                        return os.path.join(p, my_name)
            install_path = find_module_path()

            # Add your post install code here

        atexit.register(_post_install)
        install.run(self)

setup(
    cmdclass={'install': CustomInstall},
...

Esto también le da acceso a la ruta de instalación del paquete en install_path, para hacer un trabajo de shell.

Ezbob
fuente
2

Creo que la forma más fácil de realizar la posinstalación y mantener los requisitos es decorar la llamada a setup(...):

from setup tools import setup


def _post_install(setup):
    def _post_actions():
        do_things()
    _post_actions()
    return setup

setup = _post_install(
    setup(
        name='NAME',
        install_requires=['...
    )
)

Esto se ejecutará setup()al declarar setup. Una vez que haya terminado con la instalación de los requisitos, ejecutará la _post_install()función, que ejecutará la función interna _post_actions().

MBM
fuente
1
¿Intentaste esto? Estoy intentando con Python 3.4 y la instalación funciona normalmente pero las post_actions no se ejecutan ...
dojuba
1

Si usa atexit, no es necesario crear una nueva clase cmd. Simplemente puede crear su registro atexit justo antes de la llamada setup (). Hace la misma cosa.

Además, si necesita que se instalen las dependencias primero, esto no funciona con la instalación de pip, ya que se llamará a su controlador atexit antes de que pip mueva los paquetes a su lugar.

myjay610
fuente
Como algunas sugerencias publicadas aquí, esta no tiene en cuenta si está ejecutando o no en modo "instalar" o no. Ese es el motivo por el que se emplean clases personalizadas de "comandos".
BuvinJ
1

No pude resolver un problema con ninguna de las recomendaciones presentadas, así que esto es lo que me ayudó.

Puede llamar a la función que desea ejecutar después de la instalación justo después setup()de setup.py, así:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()
sdrenn00
fuente