标签归档:software-distribution

使用Python setuptools的安装后脚本

问题:使用Python setuptools的安装后脚本

是否可以在setuptools setup.py文件中指定安装后的Python脚本文件,以便用户可以运行以下命令:

python setup.py install

在本地项目文件存档上,或

pip install <name>

对于PyPI项目,该脚本将在标准setuptools安装完成时运行吗?我希望执行可以在单个Python脚本文件中编码的安装后任务(例如,向用户传递自定义安装后消息,从其他远程源存储库中提取其他数据文件)。

几年前,我遇到了这个SO答案,它回答了该主题,听起来好像当时的共识是您需要创建一个install子命令。如果仍然是这种情况,是否可以有人提供如何执行此操作的示例,以便用户不必输入第二条命令来运行脚本?

Is it possible to specify a post-install Python script file as part of the setuptools setup.py file so that a user can run the command:

python setup.py install

on a local project file archive, or

pip install <name>

for a PyPI project and the script will be run at the completion of the standard setuptools install? I am looking to perform post-install tasks that can be coded in a single Python script file (e.g. deliver a custom post-install message to the user, pull additional data files from a different remote source repository).

I came across this SO answer from several years ago that addresses the topic and it sounds as though the consensus at that time was that you need to create an install subcommand. If that is still the case, would it be possible for someone to provide an example of how to do this so that it is not necessary for the user to enter a second command to run the script?


回答 0

注意:以下解决方案仅在安装源分发zip或tarball或从源树以可编辑模式安装时有效。从二元轮()安装时将无法使用.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

cmdclasssetup()函数中插入参数setup.py

setup(
    ...

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

    ...
)

您甚至可以在安装过程中调用shell命令,例如在本示例中进行安装前准备工作:

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上没有任何预安装入口点。如果您想知道为什么没有,请阅读此讨论

Note: The solution below only works when installing a source distribution zip or tarball, or installing in editable mode from a source tree. It will not work when installing from a binary wheel (.whl)


This solution is more transparent:

You will make a few additions to setup.py and there is no need for an extra file.

Also you need to consider two different post-installations; one for development/editable mode and the other one for install mode.

Add these two classes that includes your post-install script to 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

and insert cmdclass argument to setup() function in setup.py:

setup(
    ...

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

    ...
)

You can even call shell commands during installation, like in this example which does pre-installation preparation:

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

P.S. there are no any pre-install entry points available on setuptools. Read this discussion if you are wondering why there is none.


回答 1

注意:以下解决方案仅在安装源分发zip或tarball或从源树以可编辑模式安装时有效。从二元轮()安装时将无法使用.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},

Note: The solution below only works when installing a source distribution zip or tarball, or installing in editable mode from a source tree. It will not work when installing from a binary wheel (.whl)


This is the only strategy that has worked for me when the post-install script requires that the package dependencies have already been installed:

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

回答 2

注意:以下解决方案仅在安装源分发zip或tarball或从源树以可编辑模式安装时有效。从二元轮()安装时将无法使用.whl


一个解决方案可能是post_setup.py在in 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__':语句允许您从命令行手动启动安装后。

Note: The solution below only works when installing a source distribution zip or tarball, or installing in editable mode from a source tree. It will not work when installing from a binary wheel (.whl)


A solution could be to include a post_setup.py in setup.py‘s directory. post_setup.py will contain a function which does the post-install and setup.py will only import and launch it at the appropriate time.

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

In post_setup.py:

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

if __name__ == '__main__':
    main()

With the common idea of launching setup.py from its directory, you will be able to import post_setup.py else it will launch an empty function.

In post_setup.py, the if __name__ == '__main__': statement allows you to manually launch post-install from command line.


回答 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,以进行一些shell工作。

Combining the answers from @Apalala, @Zulu and @mertyildiran; this worked for me in a Python 3.5 environment:

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

This also gives you access the to the installation path of the package in install_path, to do some shell work on.


回答 4

我认为执行后安装并保持要求的最简单方法是装饰对的调用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()

I think the easiest way to perform the post-install, and keep the requirements, is to decorate the call to 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=['...
    )
)

This will run setup() when declaring setup. Once done with the requirements installation, it will run the _post_install() function, which will run the inner function _post_actions().


回答 5

如果使用atexit,则无需创建新的cmdclass。您可以直接在setup()调用之前创建atexit寄存器。它做同样的事情。

另外,如果你需要依赖先安装,但这不是用PIP工作进行安装,因为你的atexit处理程序之前PIP移动套餐到位调用。

If using atexit, there is no need to create a new cmdclass. You can simply create your atexit register right before the setup() call. It does the same thing.

Also, if you need dependencies to be installed first, this does not work with pip install since your atexit handler will be called before pip moves the packages into place.


回答 6

我无法通过提出的任何建议来解决问题,因此这对我有所帮助。

你可以调用功能,你想刚过安装之后运行setup()setup.py,这样的:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()

I wasn’t able to solve a problem with any presented recommendations, so here is what helped me.

You can call function, that you want to run after installation just after setup() in setup.py, like that:

from setuptools import setup

def _post_install():
    <your code>

setup(...)

_post_install()