Script de pós-instalação com ferramentas de configuração Python

97

É possível especificar um arquivo de script Python pós-instalação como parte do arquivo setuptools setup.py para que um usuário possa executar o comando:

python setup.py install

em um arquivo de projeto local, ou

pip install <name>

para um projeto PyPI e o script será executado na conclusão da instalação do setuptools padrão? Estou procurando realizar tarefas pós-instalação que podem ser codificadas em um único arquivo de script Python (por exemplo, entregar uma mensagem pós-instalação personalizada ao usuário, extrair arquivos de dados adicionais de um repositório de origem remoto diferente).

Eu encontrei esta resposta SO de vários anos atrás que aborda o tópico e parece que o consenso na época era que você precisa criar um subcomando de instalação. Se esse ainda for o caso, seria possível alguém fornecer um exemplo de como fazer isso para que não seja necessário que o usuário digite um segundo comando para executar o script?

Chris Simpkins
fonte
4
Espero automatizar a execução do script em vez de exigir que o usuário insira um segundo comando. Alguma ideia?
Chris Simpkins
1
Pode ser o que você está procurando: stackoverflow.com/questions/17806485/…
limp_chimp
1
Obrigado! Vou dar uma olhada
Chris Simpkins
1
Se você precisar disso, esta postagem do blog que encontrei por um rápido google parece ser útil. (Consulte também Estendendo e reutilizando ferramentas de configuração nos documentos.)
abarnert
1
@Simon Bem, você está vendo um comentário de 4 anos atrás sobre algo que provavelmente não é o que alguém com esse problema deseja, então você não pode realmente esperar que seja monitorado e mantido atualizado. Se isso fosse uma resposta, valeria a pena o esforço de encontrar novos recursos para substituí-los, mas não é. Se precisar de informações desatualizadas, você sempre pode usar o Wayback Machine, ou você pode pesquisar a seção equivalente nos documentos atuais.
abarnert de

Respostas:

92

Nota: A solução abaixo só funciona ao instalar um zip ou tarball de distribuição de código-fonte, ou instalar em modo editável a partir de uma árvore de código-fonte. Ele vai não funciona quando instalando a partir de uma roda de binário ( .whl)


Esta solução é mais transparente:

Você fará alguns acréscimos setup.pye não há necessidade de um arquivo extra.

Além disso, você precisa considerar duas pós-instalações diferentes; um para o modo de desenvolvimento / editável e o outro para o modo de instalação.

Adicione essas duas classes que incluem seu script pós-instalação para 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 insira o cmdclassargumento para setup()funcionar em setup.py:

setup(
    ...

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

    ...
)

Você pode até chamar comandos shell durante a instalação, como neste exemplo, que faz a preparação de pré-instalação:

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, não há nenhum ponto de entrada de pré-instalação disponível nas ferramentas de configuração. Leia esta discussão se você está se perguntando por que não há nenhum.

mertyildiran
fonte
Parece mais limpo do que os outros, mas isso não executa o código personalizado antes do installcomando real ?
raphinesse
7
Depende de você: se você chamar runo pai primeiro , seu comando é uma pós-instalação, caso contrário, é uma pré-instalação. Eu atualizei a resposta para refletir isso.
kynan
1
usando esta solução parece que as install_requiresdependências são ignoradas
ealfonso
6
Isso não funcionou para mim com pip3. O script de instalação foi executado durante a publicação do pacote, mas não durante a instalação.
Eric Wiener
1
@JuanAntonioOrozco Eu atualizei o link quebrado usando o Wayback Machine. Não sei por que está quebrado neste momento. Talvez algo esteja errado com o bugs.python.org agora.
mertyildiran
13

Nota: A solução abaixo só funciona ao instalar um zip ou tarball de distribuição de código-fonte, ou instalar em modo editável a partir de uma árvore de código-fonte. Ele vai não funciona quando instalando a partir de uma roda de binário ( .whl)


Esta é a única estratégia que funcionou para mim quando o script pós-instalação exige que as dependências do pacote já tenham sido instaladas:

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
fonte
Por que você registra um atexitmanipulador em vez de simplesmente chamar a função pós-instalação após a etapa de instalação?
kynan
1
@kynan Porque setuptoolsé muito pouco documentado. Outros já alteraram suas respostas a estas perguntas e respostas com as soluções corretas.
Apalala de
3
Bem, as outras respostas não funcionam para mim: ou o script de pós-instalação não é executado, ou as dependências não são mais tratadas. Até agora, vou ficar atexite não redefinir install.run()(esse é o motivo pelo qual as dependências não são mais tratadas). Além disso, para saber o diretório de instalação, coloquei _post_install()como método de new_install, o que me permite acessar self.install_purelibe self.install_platlib(não sei qual usar, mas self.install_libestá errado, estranhamente).
zezollo
2
Eu também estava tendo problemas com dependências e atexit funciona para mim
ealfonso
7
Nenhum dos métodos aqui parece funcionar com rodas. As rodas não executam setup.py, portanto, as mensagens são exibidas apenas durante a construção, não durante a instalação do pacote.
JCGB
7

Nota: A solução abaixo só funciona ao instalar um zip ou tarball de distribuição de código-fonte, ou instalar em modo editável a partir de uma árvore de código-fonte. Ele vai não funciona quando instalando a partir de uma roda de binário ( .whl)


Uma solução poderia ser a de incluir um post_setup.pyno setup.pydiretório 's. post_setup.pyconterá uma função que faz a pós-instalação e setup.pysó irá importá-la e iniciá-la no momento apropriado.

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

Em post_setup.py:

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

if __name__ == '__main__':
    main()

Com a ideia comum de iniciar a setup.pypartir de seu diretório, você será capaz de importar, caso post_setup.pycontrário, ele iniciará uma função vazia.

Em post_setup.py, a if __name__ == '__main__':instrução permite que você inicie manualmente a pós-instalação a partir da linha de comando.

zulu
fonte
4
No meu caso, substituir run()faz com que as dependências do pacote não sejam instaladas.
Apalala
1
@Apalala foi porque o erro cmdclassfoi substituído, eu consertei isso.
kynan
1
Ah, finalmente, encontramos a resposta certa. Como as respostas erradas recebem tantos votos no StackOverflow? Na verdade, você tem que executar o seu post_install() depois do install_data.run(self)contrário, você estará perdendo algumas coisas. Como data_filespelo menos. Obrigado kynan.
personal_cloud
1
Nao funciona para mim. Acho que, por algum motivo, o comando install_datanão é executado no meu caso. Então, não tem atexita vantagem de garantir que o script de pós-instalação seja executado no final, em qualquer situação?
zezollo
3

Combinando as respostas de @Apalala, @Zulu e @mertyildiran; isso funcionou para mim em um 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},
...

Isso também dá acesso ao caminho de instalação do pacote em install_path, para fazer algum trabalho no shell.

Ezbob
fonte
2

Acho que a maneira mais fácil de realizar a pós-instalação e manter os requisitos é decorar a chamada para 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=['...
    )
)

Isso será executado setup()ao declarar setup. Depois de concluída a instalação dos requisitos, ele executará a _post_install()função, que executará a função interna _post_actions().

Mbm
fonte
1
Você tentou isso? Estou tentando com Python 3.4 e install funciona normalmente, mas as post_actions não são executadas ...
dojuba
1

Se estiver usando atexit, não há necessidade de criar um novo cmdclass. Você pode simplesmente criar seu registro atexit antes da chamada setup (). Ele faz a mesma coisa.

Além disso, se você precisa que as dependências sejam instaladas primeiro, isso não funciona com a instalação do pip, pois seu manipulador atexit será chamado antes que o pip mova os pacotes para o lugar.

myjay610
fonte
Como algumas sugestões postadas aqui, esta não leva em consideração se você está ou não executando no modo "instalar". Esse é o motivo pelo qual as classes de "comando" personalizadas são empregadas.
BuvinJ
0

Não consegui resolver um problema com nenhuma das recomendações apresentadas, então aqui está o que me ajudou.

Você pode chamar a função, que deseja executar após a instalação apenas depois setup()em setup.py, assim:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()
sdrenn00
fonte