【问题标题】:Post-install script with Python setuptools使用 Python setuptools 的安装后脚本
【发布时间】:2013-12-15 19:47:14
【问题描述】:

是否可以将安装后 Python 脚本文件指定为 setuptools setup.py 文件的一部分,以便用户可以运行命令:

python setup.py install

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

pip install <name>

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

我遇到了this SO answer from several years ago,它解决了这个主题,听起来当时的共识是您需要创建一个安装子命令。如果情况仍然如此,是否可以提供一个示例说明如何执行此操作,以便用户无需输入第二个命令来运行脚本?

【问题讨论】:

  • 我希望自动化脚本运行,而不是要求用户输入第二个命令。有什么想法吗?
  • 这可能就是你要找的东西:stackoverflow.com/questions/17806485/…
  • 谢谢!我去看看
  • 如果你确实需要这个,我通过快速谷歌找到的this blog post 看起来很有用。 (另请参阅文档中的 Extending and Reusing Setuptools。)
  • @Simon 好吧,您正在查看 4 年前的评论,该评论可能不是有此问题的人想要的,因此您不能真正期望它会受到监控和保留最新。如果这是一个答案,那么寻找新资源来替代它们是值得的,但事实并非如此。如果您需要过时的信息,您可以随时使用 Wayback Machine,也可以在当前文档中搜索相应部分。

标签: python setuptools software-distribution setup.py pypi


【解决方案1】:

注意:以下解决方案仅在安装源代码分发 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

并在setup.py 中将cmdclass 参数插入setup() 函数:

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

附注setuptools 上没有任何预安装入口点。如果您想知道为什么没有,请阅读this discussion

【讨论】:

  • 这取决于你:如果你在父 first 上调用run,那么你的命令是一个后安装,否则它是一个预安装。我已经更新了答案以反映这一点。
  • 使用这个解决方案似乎 install_requires 依赖被忽略了
  • 这对pip3 不起作用。安装脚本在发布包时运行,但在安装时不运行。
  • @JuanAntonioOrozco 我已经使用 Wayback Machine 更新了断开的链接。我不知道为什么它在这一刻坏了。 bugs.python.org 现在可能有问题。
  • 似乎只适用于 setup.py install 而不是 pip installing 包。
【解决方案2】:

注意:以下解决方案仅在安装源代码分发 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},

【讨论】:

  • 为什么要注册atexit处理程序而不是在安装步骤后简单地调用安装后函数?
  • @kynan 因为setuptools 的文档很少。其他人已经用正确的解决方案修改了他们对此问答的答案。
  • 其他答案对我不起作用:要么安装后脚本未执行,要么不再处理依赖项。到目前为止,我将坚持使用atexit 并且not 重新定义install.run()(这就是不再处理依赖关系的原因)。另外,为了知道安装目录,我把_post_install()作为new_install的方法,让我可以访问self.install_purelibself.install_platlib(不知道用哪个,但是@ 987654331@ 是错误的,奇怪)。
  • 我也遇到了依赖问题,atexit 对我有用
  • 这里的方法似乎都不适用于轮子。 Wheels 不运行 setup.py,因此消息仅在构建时显示,而不是在安装包时显示。
【解决方案3】:

注意:以下解决方案仅在安装源代码分发 zip 或 tarball 或从源代码树以可编辑模式安装时有效。从二进制轮子 (.whl) 安装时,它将工作


解决方案可能是在setup.py 的目录中包含post_setup.pypost_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__': 语句允许您从命令行手动启动安装后。

【讨论】:

  • 在我的例子中,覆盖 run() 会导致无法安装包依赖项。
  • @Apalala 那是因为错误的cmdclass 被替换了,我已经解决了这个问题。
  • 啊,终于找到了正确的答案。为什么错误的答案会在 StackOverflow 上获得如此多的选票?事实上,你必须在install_data.run(self) 之后运行post_install() ,否则你会丢失一些东西。至少喜欢data_files。谢谢kynan。
  • 对我不起作用。我想,出于任何原因,命令install_data 在我的情况下没有执行。那么,atexit 在任何情况下都没有确保安装后脚本最终执行的优势吗?
【解决方案4】:

结合@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 工作。

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 你试过了吗?我正在尝试使用 Python 3.4 并正常安装,但 post_actions 未执行...
    【解决方案6】:

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

    另外,如果您需要先安装依赖项,这 与 pip install 一起工作,因为您的 atexit 处理程序将在 pip 将包移动到位之前被调用。

    【讨论】:

    • 就像这里发布的一些建议一样,这并不能说明您是否在“安装”模式下运行。这就是为什么要使用自定义“命令”类的原因。
    【解决方案7】:

    我无法解决任何提出的建议的问题,所以这对我有帮助。

    你可以在setup.py中的setup()之后调用你想在安装后运行的函数,像这样:

    from setuptools import setup
    
    def _post_install():
        <your code>
    
    setup(...)
    
    _post_install()
    

    【讨论】:

      猜你喜欢
      • 2013-07-22
      • 1970-01-01
      • 2011-06-19
      • 2016-06-24
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 2020-05-05
      相关资源
      最近更新 更多