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?
źródło
Odpowiedzi:
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.py
i 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
cmdclass
argument dosetup()
funkcji wsetup.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.
źródło
install
poleceniem?run
na rodzica pierwszy wówczas komenda jest po instalacji, w przeciwnym razie jest to pre-install. Zaktualizowałem odpowiedź, aby to odzwierciedlić.install_requires
zależności są ignorowanepip3
. Skrypt instalacyjny był uruchamiany podczas publikowania pakietu, ale nie podczas jego instalowania.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},
źródło
atexit
obsługi zamiast po prostu wywoływać funkcję po instalacji po kroku instalacji?setuptools
jest dość niedokumentowane . Inni już poprawili swoje odpowiedzi w tym pytaniu i odpowiedzi, podając poprawne rozwiązania.atexit
i 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 doself.install_purelib
iself.install_platlib
(nie wiem, którego użyć, aleself.install_lib
jest źle, dziwnie).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.py
wsetup.py
katalogu „s.post_setup.py
będzie zawierał funkcję, która wykonuje instalację po instalacji isetup.py
tylko 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.py
z jego katalogu, będziesz mógł zaimportowaćpost_setup.py
, w przeciwnym razie uruchomi pustą funkcję.W
post_setup.py
programieif __name__ == '__main__':
instrukcja umożliwia ręczne uruchomienie po instalacji z wiersza poleceń.źródło
run()
powoduje, że zależności pakietów nie są instalowane.cmdclass
zastąpienia zła , naprawiłem to.post_install()
po, winstall_data.run(self)
przeciwnym razie stracisz trochę rzeczy. Jakdata_files
przynajmniej. Dziękuję kynan.install_data
nie jest wykonywane w moim przypadku. Czy zatem nie maatexit
takiej przewagi w postaci upewnienia się, że skrypt po instalacji zostanie na końcu wykonany w jakiejkolwiek sytuacji?Łą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ą.źródło
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 deklarowaniasetup
. Po zakończeniu instalacji wymagań uruchomi_post_install()
funkcję, która uruchomi funkcję wewnętrzną_post_actions()
.źródło
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.
źródło
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()
wsetup.py
, tak:from setuptools import setup def _post_install(): <your code> setup(...) _post_install()
źródło