Script di post-installazione con strumenti di installazione Python


97

È possibile specificare un file di script Python post-installazione come parte del file setuptools setup.py in modo che un utente possa eseguire il comando:

python setup.py install

su un archivio di file di progetto locale o

pip install <name>

per un progetto PyPI e lo script verrà eseguito al completamento dell'installazione di setuptools standard? Sto cercando di eseguire attività di post-installazione che possono essere codificate in un singolo file di script Python (ad esempio, fornire un messaggio post-installazione personalizzato all'utente, estrarre file di dati aggiuntivi da un repository di origine remota diverso).

Mi sono imbattuto in questa risposta SO di diversi anni fa che affronta l'argomento e sembra che il consenso in quel momento fosse che è necessario creare un sottocomando di installazione. Se è ancora così, sarebbe possibile per qualcuno fornire un esempio di come farlo in modo che non sia necessario che l'utente inserisca un secondo comando per eseguire lo script?


4
Spero di automatizzare l'esecuzione dello script piuttosto che richiedere all'utente di immettere un secondo comando. qualche idea?
Chris Simpkins,

1
Questo potrebbe essere quello che stai cercando: stackoverflow.com/questions/17806485/...
limp_chimp

1
Grazie! Lo controllerò
Chris Simpkins

1
Se ne hai bisogno, questo post sul blog che ho trovato da un veloce google sembra che sarebbe utile. (Vedi anche Extending and Reusing Setuptools nei documenti.)
abarnert

1
@ Simon Beh, stai guardando un commento di 4 anni fa su qualcosa che probabilmente non è quello che vuole qualcuno con questo problema, quindi non puoi davvero aspettarti che venga monitorato e tenuto aggiornato. Se questa fosse una risposta varrebbe la pena di trovare nuove risorse per sostituirle, ma non lo è. Se hai bisogno di informazioni obsolete, puoi sempre utilizzare la Wayback Machine, oppure puoi cercare la sezione equivalente nei documenti correnti.
abarnert

Risposte:


92

Nota: la soluzione seguente funziona solo quando si installa uno zip di distribuzione dei sorgenti o un tarball, oppure si installa in modalità modificabile da un albero dei sorgenti. Esso non funziona durante l'installazione da una ruota binario ( .whl)


Questa soluzione è più trasparente:

Farai alcune aggiunte a setup.pye non è necessario un file aggiuntivo.

Inoltre è necessario considerare due diverse post-installazioni; uno per la modalità di sviluppo / modificabile e l'altro per la modalità di installazione.

Aggiungi queste due classi che includono lo script di post-installazione a 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 inserisci l' cmdclassargomento per setup()funzionare in setup.py:

setup(
    ...

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

    ...
)

Puoi anche chiamare i comandi della shell durante l'installazione, come in questo esempio che fa la preparazione pre-installazione:

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 non ci sono punti di ingresso pre-installazione disponibili su setuptools. Leggi questa discussione se ti stai chiedendo perché non ce n'è.


Sembra più pulito degli altri, ma questo non esegue il codice personalizzato prima del installcomando effettivo ?
raphinesse

7
Dipende da te: se chiami primarun il genitore, il tuo comando è una post-installazione, altrimenti è una pre-installazione. Ho aggiornato la risposta per riflettere questo.
kynan

1
utilizzando questa soluzione sembra che le install_requiresdipendenze vengano ignorate
ealfonso

6
Questo non ha funzionato per me con pip3. Lo script di installazione è stato eseguito durante la pubblicazione del pacchetto, ma non durante l'installazione.
Eric Wiener

1
@JuanAntonioOrozco Ho aggiornato il collegamento interrotto utilizzando Wayback Machine. Non so perché sia ​​rotto in questo preciso momento. Forse in questo momento c'è qualcosa che non va con bugs.python.org .
mertyildiran

13

Nota: la soluzione seguente funziona solo quando si installa uno zip di distribuzione dei sorgenti o un tarball, oppure si installa in modalità modificabile da un albero dei sorgenti. Esso non funziona durante l'installazione da una ruota binario ( .whl)


Questa è l'unica strategia che ha funzionato per me quando lo script di post-installazione richiede che le dipendenze del pacchetto siano già state installate:

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

Perché si registra un atexitgestore invece di chiamare semplicemente la funzione di post-installazione dopo la fase di installazione?
kynan

1
@kynan Perché setuptoolsè abbastanza poco documentato. Altri hanno già modificato le loro risposte su questa domanda e risposta con le soluzioni corrette.
Apalala

3
Ebbene, le altre risposte non funzionano per me: o lo script di post installazione non viene eseguito, oppure le dipendenze non vengono più gestite. Finora, mi atterrò atexite non ridefinirò install.run()(questo è il motivo per cui le dipendenze non vengono più gestite). Inoltre, per conoscere la directory di installazione, ho messo _post_install()come metodonew_install , cosa mi permette di accedere a self.install_purelibe self.install_platlib(non so quale usare, ma self.install_libè sbagliato, stranamente).
zezollo

2
Avevo anche problemi con le dipendenze e atexit funziona per me
ealfonso

7
Nessuno dei metodi qui sembra funzionare con le ruote. Le ruote non eseguono setup.py, quindi i messaggi vengono visualizzati solo durante la compilazione, non durante l'installazione del pacchetto.
JCGB

7

Nota: la soluzione seguente funziona solo quando si installa uno zip di distribuzione dei sorgenti o un tarball, oppure si installa in modalità modificabile da un albero dei sorgenti. Esso non funziona durante l'installazione da una ruota binario ( .whl)


Una soluzione potrebbe essere quella di includere una post_setup.pynella setup.pydirectory 's. post_setup.pyconterrà una funzione che esegue la post-installazione e setup.pyla importerà e la avvierà solo al momento opportuno.

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

Con l'idea comune di lanciare setup.pydalla sua directory, sarai in grado di importare post_setup.pyaltrimenti lancerà una funzione vuota.

In post_setup.py, ilif __name__ == '__main__': istruzione consente di avviare manualmente la post-installazione dalla riga di comando.


4
Nel mio caso, l'override run()fa sì che le dipendenze del pacchetto non vengano installate.
Apalala

1
@Apalala era perché il torto è cmdclassstato sostituito, ho risolto questo problema.
kynan

1
Ah, finalmente, troviamo la risposta giusta. Come mai le risposte sbagliate ottengono così tanti voti su StackOverflow? In effetti, è necessario eseguire il vostro post_install() dopo l' install_data.run(self)altrimenti ti mancherà un po 'di roba. Come data_filesalmeno. Grazie Kynan.
personal_cloud

1
Non funziona per me. Immagino che, per qualsiasi motivo, il comando install_datanon venga eseguito nel mio caso. Quindi, non ha atexitil vantaggio di garantire che lo script di post-installazione verrà eseguito alla fine, in qualsiasi situazione?
zezollo

3

Combinando le risposte di @Apalala, @Zulu e @mertyildiran; questo ha funzionato per me in un ambiente 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},
...

Questo ti dà anche accesso al percorso di installazione del pacchetto in install_path, su cui lavorare sulla shell.


2

Penso che il modo più semplice per eseguire la post-installazione e mantenere i requisiti sia decorare la chiamata 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=['...
    )
)

Questo verrà eseguito setup()durante la dichiarazione setup. Una volta completata l'installazione dei requisiti, verrà eseguita la _post_install()funzione, che eseguirà la funzione interna _post_actions().


1
Hai provato questo? Sto provando con Python 3.4 e l'installazione funziona normalmente ma le post_azioni non vengono eseguite ...
dojuba

1

Se si utilizza atexit, non è necessario creare una nuova cmdclass. Puoi semplicemente creare il tuo registro atexit subito prima della chiamata setup (). Fa la stessa cosa.

Inoltre, se è necessario installare prima le dipendenze, ciò non funziona con pip install poiché il gestore atexit verrà chiamato prima che pip sposti i pacchetti in posizione.


Come alcuni suggerimenti pubblicati qui, questo non tiene conto del fatto che tu stia eseguendo o meno in modalità "installazione". Questo è il motivo per cui vengono impiegate classi di "comando" personalizzate.
BuvinJ

0

Non sono stato in grado di risolvere un problema con i consigli presentati, quindi ecco cosa mi ha aiutato.

È possibile chiamare la funzione, che si desidera eseguire dopo l'installazione subito dopo setup()in setup.py, così:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.