【问题标题】:How to make setup.py for standalone python application in a right way?如何以正确的方式为独立的 python 应用程序制作 setup.py?
【发布时间】:2020-12-22 11:35:16
【问题描述】:

我已经阅读了几个类似的主题,但还没有成功。我觉得我错过或误解了一些基本的东西,这就是我失败的原因。
我有一个用 python 编写的“应用程序”,我想在标准 setup.py 的帮助下进行部署。由于功能复杂,它由不同的 python 模块组成。但是这个模块单独发布没有意义,因为它们太具体了。
预期的结果是在pip install 的帮助下将软件包安装在系统中,并且可以通过简单的app 命令从操作系统命令行获得。
将长篇故事简化为可重现的示例 - 我有以下目录结构:

<root>
  ├─ app
  |   ├─ aaa
  |   |   └── module_a.py
  |   ├─ bbb
  |   |   └── module_b.py
  |   └── app.py
  ├─ docs
  |   └── .....
  ├─ tests
  |   └── .....
  └─ setup.py

以下是模块代码:
app.py

#!/usr/bin/python
from aaa.module_a import method1
from bbb.module_b import method2

def main():
    print("APP main executed")
    method1()
    method2()

if __name__ == '__main__':
    main()

module_a.py

def method1():
    print("A1 executed")

module_b.py

def method2():
    print("B2 executed")

当我从控制台运行 app.py 时,它可以正常工作并给出预期的输出:

APP main executed
A1 executed
B2 executed

所以,这个简单的“应用程序”运行良好,我想通过以下方式分发它
setup.py

from setuptools import setup

setup(
    name="app",
    version="1.0",
    packages=['app', 'app.aaa', 'app.bbb'],
    package_dir={'app': 'app'},
    entry_points={
        'console_scripts': ['app=app.app:main', ]
    }
)

再次,一切看起来都不错,测试安装看起来也不错:

(venv) [user@test]$ pip install <root>
Processing /home/user/<root>
Using legacy 'setup.py install' for app, since package 'wheel' is not installed.
Installing collected packages: app
    Running setup.py install for app ... done
Successfully installed app-1.0
(venv) [user@test]$ 

现在问题来了。使用 setup.py 中的上述 entry_points 我希望能够使用 ./app 命令执行我的应用程序。确实有效。但应用程序本身失败并显示错误消息:

File "/test/venv/lib/python3.9/site-packages/app/app.py", line 3, in <module>
    from aaa.module_a import method1
ModuleNotFoundError: No module named 'aaa'

我理解错误的原因 - 这是因为pip install 将目录aaabbbapp.py 放在一个目录app 中。 IE。从这个角度来看,app.py 应该使用import app.aaa 而不是import aaa。但是如果我这样做了,那么我的应用程序在开发过程中会出现错误:

ModuleNotFoundError: No module named 'app.aaa'; 'app' is not a package

这也是合乎逻辑的,因为当时没有可用的app 包......(它正在开发中,尚未安装在系统中......)

最后。问题是 - 为包含几个自己的模块的独立 python 应用程序创建目录结构和 setup.py 的正确方法是什么?

UPD
最有希望的结果(但在评论中讨论后证明是错误的)是我在进行了以下更改后得出的:

  1. 将 app.py 从 &lt;root&gt;/app 移动到 &lt;root&gt; 本身
  2. 我在setup.py py_modules=['app'] 中引用了它
  3. 我将导入从 import aaa.method1 更改为 import app.aaa.method1 等。

这种方式包在我的开发环境和安装后都可以工作。
但是我遇到了entry_points 的问题 - 我看不到如何配置入口点以使用来自app.pymain(),这不是app 包的一部分,而是一个单独的模块....
IE。新结构是

<root>
  ├─ app
  |   ├─ aaa
  |   |   └── module_a.py
  |   ├─ bbb
  |   |   └── module_b.py
  |   └──__init__.py
  ├─ docs
  |   └── .....
  ├─ tests
  |   └── .....
  ├─ app.py
  └─ setup.py

即这里的逻辑 - 拥有 2 个独立的实体:

  1. 一个空包app(仅包含init.py)和子包aaabbb等。
  2. 一个脚本app.py,它使用来自子包app.aaaapp.bbb的函数

但正如我所写 - 我看不出如何为 app.py 定义入口点以允许它直接从操作系统命令行运行。

【问题讨论】:

  • 看起来您不需要 setup.py 中的 package_dir 参数。不要认为它会解决任何问题,但仍然。 -- 你说你用./app 调用应用程序,这对我来说似乎很奇怪。当前目录中不应有任何app 可执行文件。但是您应该可以直接致电app。 ——确实,进口似乎是错误的。也许看看这个:sinoroc.gitlab.io/kb/python/python_imports.html
  • 我试图删除 package_dir - 没有任何改变。
  • 很抱歉与./appapp 混淆。我在安装后将其称为app,因为它在entry_points 中被引用,但是当我在开发环境中时-我从IDE 或直接从命令行运行它作为./app.py 在正确的目录中
  • 关于您的最新编辑:您绝对不能同时拥有名为 app 的顶级包和名为 app 的顶级模块。我看不出这怎么可能奏效。即使它确实有效,我也不建议这样做。
  • 实际上它可以工作并且似乎是一个很好的前进方式,因为它将主题分成空包app(这就是我在那里添加__init__.py的原因)和子包app.aaa,@987654376 @ 和单个模块 app.py 导入和使用这些子包。所以,它背后有一个逻辑,我没有看到矛盾。我只是看不到如何为不属于包的单个 python 文件设置入口点(命令的语法需要包名....)

标签: python setuptools setup.py


【解决方案1】:

使用该目录(包)结构,在您的 app.py 中,您应该导入以下内容之一:

from app.aaa.module_a import method1
from .aaa.module_a import method1

然后确保调用您的应用程序,如下所示:

app

(这应该归功于控制台入口点)

python -m app.app

(即使没有控制台入口点也应该可以工作)


我尝试在这里重新创建完整的项目

目录结构:

.
├── app
│   ├── aaa
│   │   └── module_a.py
│   ├── app.py
│   └── bbb
│       └── module_b.py
└── setup.py

setup.py

import setuptools

setuptools.setup(
    name="app",
    version="1.0",
    packages=['app', 'app.aaa', 'app.bbb'],
    entry_points={
        'console_scripts': ['app=app.app:main', ]
    },
)

app/app.py

#!/usr/bin/python

from .aaa.module_a import method1
from .bbb.module_b import method2

def main():
    print("APP main executed")
    method1()
    method2()

if __name__ == '__main__':
    main()

app/aaa/module_a.py

def method1():
    print("A1 executed")

app/bbb/module_b.py

def method2():
    print("B2 executed")

然后我运行以下命令:

$ python3 -V
Python 3.6.9
$ python3 -m venv .venv
$ .venv/bin/python -m pip install -U pip setuptools wheel
# [...]
$ .venv/bin/python -m pip list
Package       Version
------------- -------------------
pip           20.3.3
pkg-resources 0.0.0
setuptools    51.1.0.post20201221
wheel         0.36.2
$ .venv/bin/python -m pip install .
# [...]
$ .venv/bin/python -m app.app
APP main executed
A1 executed
B2 executed
$ .venv/bin/app
APP main executed
A1 executed
B2 executed
$ .venv/bin/python -m pip uninstall app
# [...]
$ .venv/bin/python -m pip install --editable .
# [...]
$ .venv/bin/python -m app.app
APP main executed
A1 executed
B2 executed
$ .venv/bin/app
APP main executed
A1 executed
B2 executed

【讨论】:

  • 正如我所写 - 如果我创建 from app.aaa.module_a import method1 则会导致错误 ModuleNotFoundError: No module named 'app.aaa'; 'app' is not a package
  • 据我所知app绝对是项目的顶级包,所以它应该可以工作。安装完这个项目肯定可以import app,如果不行,那就是其他问题了。
  • 是的,可以制作import app,您完全就在这里。但我不需要它。我需要直接从操作系统命令行运行它的可能性。这就是重点。在这里我有一个问题 - 1)我可以根据定义的入口点使用app 运行它,但它不能导入底层app.aaa 或2)我无法为单独的app.py 设置正确的入口点模块(请参阅有问题的 UPD 部分)
  • 这看起来一点也不正常。你确定东西被完全卸载然后重新安装正确吗?您可能需要确保在两次安装尝试之间删除 egg-info 目录。
  • 嗨@sinoroc 感谢您的努力-从您共享的代码中我看到了一些差异,但我需要仔细检查。今天在这里太晚了 - 我明天再做并分享结果。
【解决方案2】:

来自 sinoroc 的答案大部分是正确的 - 他执行了一个正确的示例,突出了所有选项。它显示了构建 python 包的正确方法。但是在任何运行之前,这个包应该首先安装到 venv 中。然后需要pip install --editable 选项才能继续在 IDE 中开发/调试包(它将包安装在 venv 中,但将源文件保留在原始位置)。

在 cmets 中经过长时间的讨论后,我来到了 An Overview of Packaging for Python 页面,该页面解释了所有选项并强调了 Python 的本机打包主要是为分发可重用代码(称为库)而构建的。我认为这是我的误解和最初的问题的原因。

作为替代解决方案,我计划使用PEP 441 -- Improving Python ZIP Application Support,这对于我的情况来说似乎是正确的方法。

【讨论】:

    猜你喜欢
    • 2021-04-04
    • 2015-02-03
    • 2017-12-24
    • 2018-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-28
    相关资源
    最近更新 更多