Skrypt poinstalacyjny z setuptools Pythona

100

Czy można określić plik skryptowy języka Python po instalacji jako część pliku setuptools setup.py, aby użytkownik mógł uruchomić polecenie:

python setup.py install

w lokalnym archiwum plików projektu lub

pip install <name>

dla projektu PyPI, a skrypt zostanie uruchomiony po zakończeniu standardowej instalacji setuptools? Chcę wykonać zadania poinstalacyjne, które można zakodować w jednym pliku skryptu Pythona (np. Dostarczyć użytkownikowi niestandardową wiadomość po instalacji, pobrać dodatkowe pliki danych z innego repozytorium zdalnego źródła).

Natknąłem się na tę odpowiedź SO z kilku lat temu, która dotyczy tego tematu i wygląda na to, że w tamtym czasie konsensus był taki, że musisz utworzyć komendę instalacji. Jeśli nadal tak jest, czy ktoś mógłby podać przykład, jak to zrobić, aby użytkownik nie musiał wprowadzać drugiego polecenia, aby uruchomić skrypt?

Chris Simpkins
źródło
4
Mam nadzieję, że zautomatyzuję uruchamianie skryptu, zamiast wymagać od użytkownika wprowadzenia drugiego polecenia. jakieś pomysły?
Chris Simpkins
1
To może być to, czego szukasz: stackoverflow.com/questions/17806485/…
limp_chimp
1
Dziękuję Ci! Sprawdzę to
Chris Simpkins
1
Jeśli tego potrzebujesz, ten post na blogu, który znalazłem przez szybkie Google, wygląda na przydatny. (Zobacz także Rozszerzanie i ponowne używanie Setuptools w dokumentacji)
abarnert
1
@Simon Cóż, patrzysz na komentarz sprzed 4 lat na temat czegoś, co prawdopodobnie nie jest tym, czego chce ktoś z tym problemem, więc nie możesz oczekiwać, że będzie to monitorowane i aktualizowane. Gdyby to była odpowiedź, warto byłoby znaleźć nowe zasoby, aby je zastąpić, ale tak nie jest. Jeśli potrzebujesz nieaktualnych informacji, zawsze możesz skorzystać z Wayback Machine lub wyszukać odpowiednią sekcję w bieżących dokumentach.
abarnert

Odpowiedzi:

93

Uwaga: Poniższe rozwiązanie działa tylko podczas instalowania archiwum źródłowego ZIP lub TARBULL albo instalowania w trybie edytowalnym z drzewa źródłowego. To będzie nie działa podczas instalacji z kołem binarnym ( .whl)


To rozwiązanie jest bardziej przejrzyste:

Dodasz kilka dodatków setup.pyi nie potrzebujesz dodatkowego pliku.

Musisz także wziąć pod uwagę dwie różne czynności po instalacji; jeden dla trybu deweloperskiego / edytowalnego, a drugi dla trybu instalacji.

Dodaj te dwie klasy, które zawierają twój skrypt poinstalacyjny, do 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

i wstaw cmdclassargument do setup()funkcji w setup.py:

setup(
    ...

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

    ...
)

Możesz nawet wywołać polecenia powłoki podczas instalacji, jak w tym przykładzie, który wykonuje przygotowanie przed instalacją:

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 w setuptools nie ma żadnych punktów wejścia do instalacji wstępnej. Przeczytaj tę dyskusję, jeśli zastanawiasz się, dlaczego nie ma.

mertyildiran
źródło
Wygląda czyściej niż inne, ale czy to nie powoduje wykonania kodu niestandardowego przed faktycznym installpoleceniem?
raphinesse
7
To do ciebie: jeśli zadzwonisz runna rodzica pierwszy wówczas komenda jest po instalacji, w przeciwnym razie jest to pre-install. Zaktualizowałem odpowiedź, aby to odzwierciedlić.
kynan
1
przy użyciu tego rozwiązania wydaje się, że install_requireszależności są ignorowane
ealfonso
7
To nie zadziałało dla mnie pip3. Skrypt instalacyjny był uruchamiany podczas publikowania pakietu, ale nie podczas jego instalowania.
Eric Wiener
1
@JuanAntonioOrozco Zaktualizowałem uszkodzony link za pomocą Wayback Machine. Nie wiem, dlaczego w tej chwili się zepsuło. Może teraz coś jest nie tak z bugs.python.org .
mertyildiran
14

Uwaga: Poniższe rozwiązanie działa tylko podczas instalowania archiwum źródłowego ZIP lub TARBULL albo instalowania w trybie edytowalnym z drzewa źródłowego. To będzie nie działa podczas instalacji z kołem binarnym ( .whl)


To jedyna strategia, która zadziałała, gdy skrypt poinstalacyjny wymaga, aby zależności pakietów zostały już zainstalowane:

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
źródło
Dlaczego rejestrujesz program atexitobsługi zamiast po prostu wywoływać funkcję po instalacji po kroku instalacji?
kynan
1
@kynan Ponieważ setuptoolsjest dość niedokumentowane . Inni już poprawili swoje odpowiedzi w tym pytaniu i odpowiedzi, podając poprawne rozwiązania.
Apalala
3
Cóż, inne odpowiedzi nie działają dla mnie: albo skrypt poinstalacyjny nie jest wykonywany, albo zależności nie są już obsługiwane. Na razie będę się trzymać atexiti nie redefiniować install.run()(jest to powód, dla którego zależności nie są już obsługiwane). Ponadto, aby poznać katalog instalacyjny, podałem _post_install()jako metodę new_install, która umożliwia mi dostęp do self.install_purelibi self.install_platlib(nie wiem, którego użyć, ale self.install_libjest źle, dziwnie).
zezollo
2
Miałem też problemy z zależnościami i atexit działa dla mnie
ealfonso
7
Żadna z opisanych tutaj metod nie działa z kołami. Wheels nie uruchamiają setup.py, więc komunikaty są wyświetlane tylko podczas budowania, a nie podczas instalowania pakietu.
JCGB
7

Uwaga: Poniższe rozwiązanie działa tylko podczas instalowania archiwum źródłowego ZIP lub TARBULL albo instalowania w trybie edytowalnym z drzewa źródłowego. To będzie nie działa podczas instalacji z kołem binarnym ( .whl)


Rozwiązaniem może być obejmują post_setup.pyw setup.pykatalogu „s. post_setup.pybędzie zawierał funkcję, która wykonuje instalację po instalacji i setup.pytylko zaimportuje ją i uruchomi w odpowiednim czasie.

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

W post_setup.py:

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

if __name__ == '__main__':
    main()

Przy typowym pomyśle uruchamiania setup.pyz jego katalogu, będziesz mógł zaimportować post_setup.py, w przeciwnym razie uruchomi pustą funkcję.

W post_setup.pyprogramie if __name__ == '__main__':instrukcja umożliwia ręczne uruchomienie po instalacji z wiersza poleceń.

Zulus
źródło
4
W moim przypadku przesłanianie run()powoduje, że zależności pakietów nie są instalowane.
Apalala,
1
@Apalala to było z powodu cmdclasszastąpienia zła , naprawiłem to.
kynan
1
Ach, w końcu znajdujemy właściwą odpowiedź. Jak to się stało, że złe odpowiedzi zdobywają tyle głosów w StackOverflow? Rzeczywiście, musisz biegać post_install() po, w install_data.run(self)przeciwnym razie stracisz trochę rzeczy. Jak data_filesprzynajmniej. Dziękuję kynan.
personal_cloud
1
Nie działa na mnie. Myślę, że z jakiegokolwiek powodu polecenie install_datanie jest wykonywane w moim przypadku. Czy zatem nie ma atexittakiej przewagi w postaci upewnienia się, że skrypt po instalacji zostanie na końcu wykonany w jakiejkolwiek sytuacji?
zezollo
3

Łącząc odpowiedzi z @Apalala, @Zulu i @mertyildiran; to działało dla mnie w środowisku 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},
...

Daje to również dostęp do ścieżki instalacyjnej pakietu w programie install_path, aby wykonać trochę pracy z powłoką.

Ezbob
źródło
2

Myślę, że najłatwiejszym sposobem na wykonanie post-instalacji i zachowanie wymagań jest udekorowanie wezwania do 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=['...
    )
)

Będzie to działać setup()podczas deklarowania setup. Po zakończeniu instalacji wymagań uruchomi _post_install()funkcję, która uruchomi funkcję wewnętrzną _post_actions().

Mbm
źródło
1
Próbowałeś tego? Próbuję z Pythonem 3.4 i instalacja działa normalnie, ale post_actions nie są wykonywane ...
dojuba
1

Jeśli używasz atexit, nie ma potrzeby tworzenia nowej klasy cmdclass. Możesz po prostu utworzyć swój rejestr atexit tuż przed wywołaniem setup (). Robi to samo.

Ponadto, jeśli potrzebujesz najpierw zainstalować zależności, nie działa to z instalacją pip, ponieważ twoja procedura obsługi atexit zostanie wywołana, zanim pip przeniesie pakiety na miejsce.

myjay610
źródło
Podobnie jak kilka sugestii zamieszczonych tutaj, ta nie uwzględnia tego, czy pracujesz w trybie „instalacji”, czy nie. To jest powód, dla którego stosowane są niestandardowe klasy „poleceń”.
BuvinJ
1

Nie udało mi się rozwiązać problemu z żadnymi przedstawionymi zaleceniami, więc oto co mi pomogło.

Funkcję można nazwać, że chcesz uruchomić po instalacji po prostu setup()w setup.py, tak:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()
sdrenn00
źródło