Skript nach der Installation mit Python-Setuptools

96

Ist es möglich, eine Python-Skriptdatei nach der Installation als Teil der Datei setuptools setup.py anzugeben, damit ein Benutzer den folgenden Befehl ausführen kann:

python setup.py install

in einem lokalen Projektdateiarchiv oder

pip install <name>

für ein PyPI-Projekt und das Skript wird nach Abschluss der Standard-Setuptools-Installation ausgeführt? Ich möchte Aufgaben nach der Installation ausführen, die in einer einzelnen Python-Skriptdatei codiert werden können (z. B. eine benutzerdefinierte Nachricht nach der Installation an den Benutzer senden, zusätzliche Datendateien aus einem anderen Remote-Quell-Repository abrufen).

Ich bin auf diese SO-Antwort von vor einigen Jahren gestoßen , die sich mit dem Thema befasst, und es scheint, als ob der damalige Konsens darin bestand, dass Sie einen Unterbefehl zur Installation erstellen müssen. Wenn dies immer noch der Fall ist, kann dann jemand ein Beispiel dafür angeben, damit der Benutzer keinen zweiten Befehl eingeben muss, um das Skript auszuführen?

Chris Simpkins
quelle
4
Ich hoffe, dass die Skriptausführung automatisiert wird, anstatt dass der Benutzer einen zweiten Befehl eingeben muss. Irgendwelche Gedanken?
Chris Simpkins
1
Dies könnte das sein, wonach Sie suchen: stackoverflow.com/questions/17806485/…
limp_chimp
1
Danke dir! Ich werde es überprüfen
Chris Simpkins
1
Wenn Sie dies benötigen, scheint dieser Blog-Beitrag , den ich von einem schnellen Google gefunden habe, nützlich zu sein. (Siehe auch Erweitern und Wiederverwenden von Setuptools in den Dokumenten.)
Abarnert
1
@Simon Nun, Sie sehen sich einen Kommentar von vor 4 Jahren zu etwas an, das wahrscheinlich nicht das ist, was jemand mit diesem Problem will. Sie können also nicht wirklich erwarten, dass es überwacht und auf dem neuesten Stand gehalten wird. Wenn dies eine Antwort wäre, wäre es die Mühe wert, neue Ressourcen zu finden, um sie zu ersetzen, aber das ist es nicht. Wenn Sie veraltete Informationen benötigen, können Sie jederzeit die Wayback-Maschine verwenden oder in den aktuellen Dokumenten nach dem entsprechenden Abschnitt suchen.
abarnert

Antworten:

91

Hinweis: Die folgende Lösung funktioniert nur, wenn Sie eine Quellverteilungs-Zip oder einen Tarball installieren oder im bearbeitbaren Modus über einen Quellbaum installieren. Es funktioniert nicht bei der Installation von einem binären Rad ( .whl)


Diese Lösung ist transparenter:

Sie werden einige Ergänzungen vornehmen setup.pyund es ist keine zusätzliche Datei erforderlich.

Außerdem müssen Sie zwei verschiedene Nachinstallationen berücksichtigen. eine für den Entwicklungs- / Bearbeitungsmodus und die andere für den Installationsmodus.

Fügen Sie diese beiden Klassen hinzu, die Ihr Skript nach der Installation enthaltensetup.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

und fügen Sie ein cmdclassArgument ein, um setup()in zu funktionieren setup.py:

setup(
    ...

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

    ...
)

Sie können sogar Shell-Befehle während der Installation aufrufen, wie in diesem Beispiel, in dem die Vorbereitung vor der Installation durchgeführt wird:

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: Auf setuptools sind keine Einstiegspunkte vor der Installation verfügbar. Lesen Sie diese Diskussion, wenn Sie sich fragen, warum es keine gibt.

Mertyildiran
quelle
Sieht sauberer aus als die anderen, aber führt dies nicht den benutzerdefinierten Code vor dem eigentlichen installBefehl aus?
Raphinesse
6
Es liegt an Ihnen: Wenn Sie zuerstrun das übergeordnete Element aufrufen, ist Ihr Befehl eine Nachinstallation, andernfalls eine Vorinstallation. Ich habe die Antwort aktualisiert, um dies widerzuspiegeln.
Kynan
1
Mit dieser Lösung scheinen install_requiresAbhängigkeiten ignoriert zu werden
ealfonso
6
Das hat bei mir nicht funktioniert pip3. Das Installationsskript wurde beim Veröffentlichen des Pakets ausgeführt, jedoch nicht bei der Installation.
Eric Wiener
1
@JuanAntonioOrozco Ich habe den defekten Link mit Wayback Machine aktualisiert. Ich weiß nicht, warum es in diesem Moment kaputt ist. Vielleicht stimmt gerade etwas mit bugs.python.org nicht .
Mertyildiran
13

Hinweis: Die folgende Lösung funktioniert nur, wenn Sie eine Quellverteilungs-Zip oder einen Tarball installieren oder im bearbeitbaren Modus über einen Quellbaum installieren. Es funktioniert nicht bei der Installation von einem binären Rad ( .whl)


Dies ist die einzige Strategie, die für mich funktioniert hat, wenn das Skript nach der Installation erfordert, dass die Paketabhängigkeiten bereits installiert wurden:

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
quelle
Warum registrieren Sie einen atexitHandler, anstatt nach dem Installationsschritt einfach die Post-Install-Funktion aufzurufen?
Kynan
1
@kynan Weil setuptoolsist ziemlich unterdokumentiert . Andere haben ihre Antworten auf diese Fragen und Antworten bereits mit den richtigen Lösungen geändert.
Apalala
3
Nun, die anderen Antworten funktionieren bei mir nicht: Entweder wird das Skript nach der Installation nicht ausgeführt oder die Abhängigkeiten werden nicht mehr behandelt. Bisher bleibe ich bei atexitund definiere nicht neuinstall.run() (dies ist der Grund, warum die Abhängigkeiten nicht mehr behandelt werden). Um das Installationsverzeichnis zu kennen, habe ich _post_install()außerdem eine Methode angegeben new_install, mit der ich auf self.install_purelibund zugreifen kann self.install_platlib(ich weiß nicht, welches ich verwenden soll, aber es self.install_libist seltsamerweise falsch).
Zezollo
2
Ich hatte auch Probleme mit Abhängigkeiten und atexit funktioniert für mich
ealfonso
6
Keine der hier beschriebenen Methoden scheint mit Rädern zu funktionieren. Die Räder führen nicht setup.py aus, daher werden die Meldungen nur beim Erstellen angezeigt, nicht beim Installieren des Pakets.
JCGB
7

Hinweis: Die folgende Lösung funktioniert nur, wenn Sie eine Quellverteilungs-Zip oder einen Tarball installieren oder im bearbeitbaren Modus über einen Quellbaum installieren. Es funktioniert nicht bei der Installation von einem binären Rad ( .whl)


Eine Lösung könnte darin bestehen, ein post_setup.pyIn setup.py-Verzeichnis aufzunehmen. post_setup.pyenthält eine Funktion, die die Nachinstallation ausführt und setup.pydiese nur zum richtigen Zeitpunkt importiert und startet.

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

In post_setup.py:

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

if __name__ == '__main__':
    main()

Mit der allgemeinen Idee, setup.pyaus dem Verzeichnis zu starten , können Sie importieren, post_setup.pyandernfalls wird eine leere Funktion gestartet.

In post_setup.pyder if __name__ == '__main__':Anweisung können Sie die Nachinstallation manuell über die Befehlszeile starten.

Zulu-
quelle
4
In meinem Fall führt das Überschreiben run()dazu , dass die Paketabhängigkeiten nicht installiert werden.
Apalala
1
@Apalala das war, weil das falsche cmdclassersetzt wurde, ich habe das behoben.
Kynan
1
Ah, endlich finden wir die richtige Antwort. Wie kommt es, dass falsche Antworten so viele Stimmen bei StackOverflow erhalten? In der Tat müssen Sie Ihre post_install() nach dem laufen, install_data.run(self)sonst werden Sie einige Sachen vermissen. Wie data_fileszumindest. Vielen Dank, Kynan.
personal_cloud
1
Funktioniert bei mir nicht Ich denke, aus irgendeinem Grund wird der Befehl install_datain meinem Fall nicht ausgeführt. Hat es nicht atexitden Vorteil, dass das Skript nach der Installation in jeder Situation am Ende ausgeführt wird?
Zezollo
3

Kombinieren der Antworten von @Apalala, @Zulu und @mertyildiran; Dies funktionierte für mich in einer Python 3.5-Umgebung:

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

Auf diese Weise können Sie auch auf den Installationspfad des Pakets zugreifen install_path, um einige Shell-Arbeiten auszuführen.

Ezbob
quelle
2

Ich denke, der einfachste Weg, die Nachinstallation durchzuführen und die Anforderungen beizubehalten, besteht darin, den Aufruf zu dekorieren 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=['...
    )
)

Dies wird setup()beim Deklarieren ausgeführt setup. Sobald die Installation der Anforderungen abgeschlossen ist, wird die _post_install()Funktion ausgeführt, die die innere Funktion ausführt _post_actions().

Mbm
quelle
1
Hast du das versucht? Ich versuche es mit Python 3.4 und die Installation funktioniert wie gewohnt, aber die post_actions werden nicht ausgeführt ...
dojuba
1

Wenn Sie atexit verwenden, müssen Sie keine neue cmdclass erstellen. Sie können einfach Ihr atexit-Register direkt vor dem Aufruf von setup () erstellen. Es macht das Gleiche.

Wenn Abhängigkeiten zuerst installiert werden müssen, funktioniert dies bei der Pip-Installation nicht, da Ihr atexit-Handler aufgerufen wird, bevor pip die Pakete an ihren Platz verschiebt.

myjay610
quelle
Wie einige hier veröffentlichte Vorschläge berücksichtigt dieser nicht, ob Sie im "Installations" -Modus ausgeführt werden oder nicht. Aus diesem Grund werden benutzerdefinierte "Befehls" -Klassen verwendet.
BuvinJ
0

Ich konnte ein Problem mit den vorgelegten Empfehlungen nicht lösen. Deshalb hat mir Folgendes geholfen.

Sie können Funktion aufrufen, dass Sie nach der Installation ausgeführt werden soll kurz nach setup()in setup.py, wie folgt aus:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()
sdrenn00
quelle