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?


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.


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

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.


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.


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


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.


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()
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.