Script post-instalación con herramientas de configuración de Python

¿Es posible especificar un archivo de script 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 proyecto local, o

 pip install  

para un proyecto PyPI y la secuencia de comandos se ejecutará una vez finalizada la instalación estándar de herramientas de instalación? Estoy buscando realizar tareas posteriores a la instalación que se pueden codificar en un solo archivo de script de Python (por ejemplo, entregar un mensaje personalizado posterior a la instalación al usuario, extraer archivos de datos adicionales de un repository 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 era necesario 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?

    Esta solución es más transparente:

    Hará algunas adiciones a setup.py y no es necesario un archivo adicional.

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

    Agregue estas dos clases que incluyen su script posterior a la instalación en 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): # PUT YOUR POST-INSTALL SCRIPT HERE or CALL A FUNCTION develop.run(self) class PostInstallCommand(install): """Post-installation for installation mode.""" def run(self): # PUT YOUR POST-INSTALL SCRIPT HERE or CALL A FUNCTION install.run(self) 

    e inserte el argumento cmdclass en la función setup() en setup.py :

     setup( ... cmdclass={ 'develop': PostDevelopCommand, 'install': PostInstallCommand, }, ... ) 

    Incluso puedes llamar comandos de shell durante la postinstalación, como esto:

     from setuptools import setup from setuptools.command.develop import develop from setuptools.command.install import install from subprocess import check_call class PostDevelopCommand(develop): """Post-installation for development mode.""" def run(self): check_call("apt-get install this-package".split()) develop.run(self) class PostInstallCommand(install): """Post-installation for installation mode.""" def run(self): check_call("apt-get install this-package".split()) install.run(self) setup( ... 

    PD: no hay ningún punto de entrada de preinstalación disponible en setuptools. Lea esta discusión si se pregunta por qué no hay ninguno.

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

     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}, 

    Una solución podría ser incluir un post_setup.py en el directorio setup.py . post_setup.py contendrá una función que hace que post-install y setup.py solo lo importarán y setup.py 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.py desde su directorio, podrá importar post_setup.py contrario lanzará una función vacía.

    En post_setup.py , la post_setup.py if __name__ == '__main__': permite iniciar manualmente la if __name__ == '__main__': desde la línea de comandos.

    Creo que la forma más fácil de realizar la postinstalación, y mantener los requisitos, es decorar la llamada a la 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 ejecutará setup() al declarar setup . Una vez hecho esto con la instalación de requisitos, ejecutará la función _post_install() , que ejecutará la función interna _post_actions() .

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

    Además, si necesita instalar primero las dependencias, 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.

    Combinando las respuestas de @Apalala, @Zulu y @mertyildiran; esto me funcionó 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 realizar un trabajo de shell.