Сценарий после установки с Python setuptools

100

Можно ли указать файл сценария Python после установки как часть файла setuptools setup.py, чтобы пользователь мог выполнить команду:

python setup.py install

в локальном файловом архиве проекта или

pip install <name>

для проекта PyPI, и сценарий будет запущен после завершения стандартной установки setuptools? Я хочу выполнить задачи после установки, которые можно закодировать в одном файле сценария Python (например, доставить пользователю настраиваемое сообщение после установки, получить дополнительные файлы данных из другого репозитория удаленного источника).

Я наткнулся на этот SO-ответ несколько лет назад, который затрагивает эту тему, и похоже, что в то время консенсус заключался в том, что вам нужно создать подкоманду установки. Если это все еще так, может ли кто-нибудь предоставить пример того, как это сделать, чтобы пользователю не нужно было вводить вторую команду для запуска сценария?

Крис Симпкинс
источник
4
Я надеюсь автоматизировать запуск скрипта, а не требовать от пользователя ввода второй команды. есть идеи?
Крис Симпкинс
1
Это может быть то, что вы ищете: stackoverflow.com/questions/17806485/…
limp_chimp
1
Спасибо! Я проверю
Крис Симпкинс
1
Если вам это действительно нужно, это сообщение в блоге, которое я нашел с помощью быстрого Google, похоже, будет полезно. (Также см. Расширение и повторное использование
инструментов настройки
1
@Simon Ну, вы смотрите комментарий 4-летней давности о том, что, вероятно, не то, чего хочет кто-то с этой проблемой, поэтому вы не можете ожидать, что он будет отслеживаться и обновляться. Если бы это был ответ, стоило бы усилий найти новые ресурсы, чтобы заменить их, но это не так. Если вам нужна устаревшая информация, вы всегда можете использовать Wayback Machine или поискать эквивалентный раздел в текущей документации.
abarnert

Ответы:

93

Примечание. Приведенное ниже решение работает только при установке zip- или tar-архива исходного дистрибутива или установке в редактируемом режиме из дерева исходных текстов. Это не сработает при установке с бинарным колеса ( .whl)


Это решение более прозрачное:

Вы сделаете несколько дополнений, setup.pyи нет необходимости в дополнительном файле.

Также вам нужно рассмотреть две разные пост-установки; один для режима разработки / редактирования, а другой - для режима установки.

Добавьте эти два класса, которые включают ваш сценарий после установки, в 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

и вставьте cmdclassаргумент setup()функции в setup.py:

setup(
    ...

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

    ...
)

Вы даже можете вызывать команды оболочки во время установки, как в этом примере, который выполняет предустановочную подготовку:

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 в setuptools нет предустановленных точек входа. Прочтите это обсуждение, если вам интересно, почему его нет.

Mertyildiran
источник
Выглядит чище, чем другие, но разве при этом не выполняется пользовательский код перед самой installкомандой?
raphinesse
7
Это зависит от вас: если вы сначала вызываете runродительский объект, тогда ваша команда выполняется после установки, в противном случае это предварительная установка. Я обновил ответ, чтобы отразить это.
kynan
1
при использовании этого решения кажется, что install_requiresзависимости игнорируются
ealfonso
7
У меня это не сработало с pip3. Сценарий установки запускался при публикации пакета, но не при его установке.
Эрик Винер
1
@JuanAntonioOrozco Я обновил неработающую ссылку с помощью Wayback Machine. Я не знаю, почему он сломан именно сейчас. Возможно, сейчас что-то не так с bugs.python.org .
mertyildiran
14

Примечание. Приведенное ниже решение работает только при установке zip- или tar-архива исходного дистрибутива или установке в редактируемом режиме из дерева исходных текстов. Это не сработает при установке с бинарным колеса ( .whl)


Это единственная стратегия, которая сработала для меня, когда сценарий после установки требует, чтобы зависимости пакета уже были установлены:

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},
Апалала
источник
Почему вы регистрируете atexitобработчик, а не просто вызываете функцию после установки после этапа установки?
kynan
1
@kynan Потому что setuptoolsэто недостаточно документировано. Другие уже добавили в свои ответы на этот вопрос и ответ правильные решения.
апалала
3
Что ж, другие ответы для меня не работают: либо сценарий пост-установки не выполняется, либо зависимости больше не обрабатываются. Пока что я буду придерживаться atexitи не переопределять install.run()(это причина, по которой зависимости больше не обрабатываются). Кроме того, чтобы узнать каталог установки, я _post_install()использовал метод new_install, позволяющий мне получить доступ к self.install_purelibи self.install_platlib(не знаю, какой из них использовать, но self.install_libэто неправильно, как ни странно).
zezollo
2
У меня тоже были проблемы с зависимостями, и у меня работает atexit
ealfonso
7
Кажется, что ни один из методов здесь не работает с колесами. Колеса не запускают setup.py, поэтому сообщения отображаются только при сборке, а не при установке пакета.
JCGB 04
7

Примечание. Приведенное ниже решение работает только при установке zip- или tar-архива исходного дистрибутива или установке в редактируемом режиме из дерева исходных текстов. Это не сработает при установке с бинарным колеса ( .whl)


Решением может быть включение post_setup.pyв setup.pyкаталог. post_setup.pyбудет содержать функцию, которая выполняет пост-установку и setup.pyтолько импортирует и запускает ее в нужное время.

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

В post_setup.py:

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

if __name__ == '__main__':
    main()

При обычной идее запуска setup.pyиз его каталога вы сможете импортировать, post_setup.pyиначе он запустит пустую функцию.

В post_setup.py, то if __name__ == '__main__':оператор позволяет вручную запускать после установки из командной строки.

Зулусский
источник
4
В моем случае переопределение run()приводит к тому, что зависимости пакета не устанавливаются.
Apalala
1
@Apalala, потому что ошибка cmdclassбыла заменена, я исправил это.
kynan
1
Ах, наконец, мы нашли правильный ответ. Почему неправильные ответы набирают столько голосов в StackOverflow? В самом деле, вы должны запустить свой post_install() после в install_data.run(self)противном случае вы будете отсутствовать некоторые вещи. По data_filesкрайней мере. Спасибо, kynan.
personal_cloud
1
У меня не работает. Думаю, install_dataв моем случае по какой-то причине команда не выполняется. Итак, разве нет atexitпреимущества в том, что сценарий после установки будет выполнен в конце в любой ситуации?
zezollo
3

Объединяя ответы @Apalala, @Zulu и @mertyildiran; это сработало для меня в среде 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},
...

Это также дает вам доступ к пути установки пакета в install_path, чтобы выполнить некоторую работу с оболочкой.

Эзбоб
источник
2

Я думаю, что самый простой способ выполнить пост-установку и соблюсти требования - это украсить вызов 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=['...
    )
)

Это будет выполняться setup()при объявлении setup. После завершения установки требований он запустит _post_install()функцию, которая запустит внутреннюю функцию _post_actions().

Мбм
источник
1
Вы пробовали это? Я пытаюсь использовать Python 3.4, и установка работает как обычно, но post_actions не выполняется ...
дожуба,
1

При использовании atexit нет необходимости создавать новый cmdclass. Вы можете просто создать свой регистр atexit прямо перед вызовом setup (). То же самое.

Кроме того, если вам нужно сначала установить зависимости, это не работает с установкой pip, поскольку ваш обработчик atexit будет вызван до того, как pip переместит пакеты на место.

myjay610
источник
Как и несколько предложений, размещенных здесь, это не учитывает, работаете вы в режиме «установки» или нет. Вот почему используются специальные «командные» классы.
BuvinJ,
1

Я не смог решить проблему ни с одним из представленных рекомендаций, поэтому вот что мне помогло.

Вы можете вызвать функцию, которую хотите запустить после установки сразу после setup()входа setup.py, вот так:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()
sdrenn00
источник