Script de post-installation avec Python setuptools

97

Est-il possible de spécifier un fichier de script Python post-installation dans le cadre du fichier setuptools setup.py afin qu'un utilisateur puisse exécuter la commande:

python setup.py install

sur une archive de fichier de projet locale, ou

pip install <name>

pour un projet PyPI et le script sera exécuté à la fin de l'installation standard de setuptools? Je cherche à effectuer des tâches de post-installation qui peuvent être codées dans un seul fichier de script Python (par exemple, livrer un message de post-installation personnalisé à l'utilisateur, extraire des fichiers de données supplémentaires à partir d'un référentiel source distant différent).

Je suis tombé sur cette réponse SO d'il y a plusieurs années qui aborde le sujet et il semble que le consensus à l'époque était que vous deviez créer une sous-commande d'installation. Si tel est toujours le cas, serait-il possible pour quelqu'un de fournir un exemple de la façon de procéder afin qu'il ne soit pas nécessaire que l'utilisateur saisisse une deuxième commande pour exécuter le script?

Chris Simpkins
la source
4
J'espère automatiser l'exécution du script plutôt que de demander à l'utilisateur de saisir une deuxième commande. Des pensées?
Chris Simpkins
1
C'est peut-être ce que vous cherchez: stackoverflow.com/questions/17806485/…
limp_chimp
1
Je vous remercie! Je vais vérifier
Chris Simpkins
1
Si vous en avez besoin, cet article de blog que j'ai trouvé par un rapide google semble être utile. (Voir également Extension et réutilisation de Setuptools dans la documentation.)
abarnert
1
@Simon Eh bien, vous regardez un commentaire d'il y a 4 ans sur quelque chose qui n'est probablement pas ce que quelqu'un avec ce problème veut, donc vous ne pouvez pas vraiment vous attendre à ce qu'il soit surveillé et tenu à jour. Si c'était une réponse, cela vaudrait la peine de trouver de nouvelles ressources pour les remplacer, mais ce n'est pas le cas. Si vous avez besoin d'informations obsolètes, vous pouvez toujours utiliser la Wayback Machine, ou vous pouvez rechercher la section équivalente dans la documentation actuelle.
abarnert

Réponses:

92

Remarque: La solution ci-dessous ne fonctionne que lors de l'installation d'un zip ou d'une tarball de distribution source, ou lors de l'installation en mode modifiable à partir d'une arborescence source. Cela ne fonctionnera pas lors de l'installation à partir d'une roue binaire ( .whl)


Cette solution est plus transparente:

Vous allez faire quelques ajouts à setup.pyet il n'y a pas besoin d'un fichier supplémentaire.

Vous devez également considérer deux post-installations différentes; un pour le mode développement / modifiable et l'autre pour le mode installation.

Ajoutez ces deux classes qui incluent votre script de post-installation à 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

et insérez l' cmdclassargument pour setup()fonctionner dans setup.py:

setup(
    ...

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

    ...
)

Vous pouvez même appeler des commandes shell pendant l'installation, comme dans cet exemple qui fait la préparation de pré-installation:

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(
    ...

PS il n'y a aucun point d'entrée de pré-installation disponible sur setuptools. Lisez cette discussion si vous vous demandez pourquoi il n'y en a pas.

mertyildiran
la source
Cela semble plus propre que les autres, mais cela n'exécute-t-il pas le code personnalisé avant la installcommande réelle ?
raphinesse
7
Il est à vous: si vous appelez runle parent d' abord , votre commande est un post-installation, sinon il est une pré-installation. J'ai mis à jour la réponse pour refléter cela.
kynan
1
en utilisant cette solution, il semble que les install_requiresdépendances soient ignorées
ealfonso
7
Cela n'a pas fonctionné pour moi avec pip3. Le script d'installation s'est exécuté lors de la publication du package, mais pas lors de son installation.
Eric Wiener
1
@JuanAntonioOrozco J'ai mis à jour le lien rompu en utilisant Wayback Machine. Je ne sais pas pourquoi il est cassé en ce moment même. Peut-être que quelque chose ne va pas avec bugs.python.org en ce moment.
mertyildiran
14

Remarque: La solution ci-dessous ne fonctionne que lors de l'installation d'un zip ou d'une tarball de distribution source, ou lors de l'installation en mode modifiable à partir d'une arborescence source. Cela ne fonctionnera pas lors de l'installation à partir d'une roue binaire ( .whl)


C'est la seule stratégie qui a fonctionné pour moi lorsque le script de post-installation nécessite que les dépendances de package aient déjà été installées:

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
la source
Pourquoi enregistrez-vous un atexitgestionnaire plutôt que d'appeler simplement la fonction de post-installation après l'étape d'installation?
kynan
1
@kynan Because setuptoolsest assez peu documenté. D'autres ont déjà modifié leurs réponses à cette Q&R avec les bonnes solutions.
Apalala
3
Et bien les autres réponses ne fonctionnent pas pour moi: soit le script de post-installation n'est pas exécuté, soit les dépendances ne sont plus gérées. Jusqu'à présent, je m'en tiendrai atexitet je ne redéfinirai pasinstall.run() (c'est la raison pour laquelle les dépendances ne sont plus gérées). De plus, afin de connaître le répertoire d'installation, j'ai mis _post_install()comme méthode de new_install, ce qui me permet d'accéder à self.install_purelibet self.install_platlib(je ne sais pas lequel utiliser, mais self.install_libc'est faux, bizarrement).
zezollo
2
J'avais aussi des problèmes avec les dépendances et atexit fonctionne pour moi
ealfonso
7
Aucune des méthodes présentées ici ne semble fonctionner avec des roues. Les roues n'exécutent pas setup.py donc, les messages ne s'affichent que lors de la construction, pas lors de l'installation du package.
JCGB
7

Remarque: La solution ci-dessous ne fonctionne que lors de l'installation d'un zip ou d'une tarball de distribution source, ou lors de l'installation en mode modifiable à partir d'une arborescence source. Cela ne fonctionnera pas lors de l'installation à partir d'une roue binaire ( .whl)


Une solution pourrait être d'inclure un post_setup.pydans setup.pyle répertoire. post_setup.pycontiendra une fonction qui effectue la post-installation et setup.pyne l'importera et la lancera qu'au moment opportun.

Dans 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},
        ...
    )

Dans post_setup.py:

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

if __name__ == '__main__':
    main()

Avec l'idée commune de lancer setup.pydepuis son répertoire, vous pourrez importer post_setup.pysinon il lancera une fonction vide.

Dans post_setup.py, l' if __name__ == '__main__':instruction vous permet de lancer manuellement la post-installation à partir de la ligne de commande.

zoulou
la source
4
Dans mon cas, le remplacement run()entraîne la non-installation des dépendances du package.
Apalala
1
@Apalala qui était parce que le mauvais a cmdclassété remplacé, j'ai corrigé ce problème .
kynan
1
Ah, enfin, nous trouvons la bonne réponse. Comment se fait-il que les mauvaises réponses obtiennent autant de votes sur StackOverflow? En effet, vous devez exécuter votre post_install() après l' install_data.run(self)autre , vous allez manquer des choses. Comme data_filesau moins. Merci kynan.
personal_cloud
1
Ça ne marche pas pour moi. Je suppose que, pour une raison quelconque, la commande install_datan'est pas exécutée dans mon cas. Alors, n'a-t-il pas atexitl'avantage de s'assurer que le script de post-installation sera exécuté à la fin, dans n'importe quelle situation?
zezollo
3

Combinant les réponses de @Apalala, @Zulu et @mertyildiran; cela a fonctionné pour moi dans un environnement 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},
...

Cela vous donne également accès au chemin d'installation du package dans install_path, pour effectuer des travaux sur le shell.

Ezbob
la source
2

Je pense que le moyen le plus simple d'effectuer la post-installation et de conserver les exigences est de décorer l'appel à 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=['...
    )
)

Cela fonctionnera setup()lors de la déclaration setup. Une fois terminé l'installation des exigences, il exécutera la _post_install()fonction, qui exécutera la fonction interne _post_actions().

Mbm
la source
1
Avez-vous essayé cela? J'essaie avec Python 3.4 et l'installation fonctionne normalement mais les post_actions ne sont pas exécutées ...
dojuba
1

Si vous utilisez atexit, il n'est pas nécessaire de créer une nouvelle cmdclass. Vous pouvez simplement créer votre registre atexit juste avant l'appel setup (). Cela fait la même chose.

De plus, si vous avez besoin d'installer d'abord les dépendances, cela ne fonctionne pas avec pip install car votre gestionnaire atexit sera appelé avant que pip ne mette les packages en place.

myjay610
la source
Comme quelques suggestions publiées ici, celle-ci ne tient pas compte du fait que vous exécutez ou non en mode «installation». C'est la raison pour laquelle des classes de "commande" personnalisées sont utilisées.
BuvinJ le
1

Je n'ai pas pu résoudre un problème avec les recommandations présentées, alors voici ce qui m'a aidé.

Vous pouvez appeler la fonction que vous souhaitez exécuter après l' installation juste après setup()dans setup.py, comme ça:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()
sdrenn00
la source