如何以正确的方式为独立 python 应用程序制作 setup.py?
How to make setup.py for standalone python application in a right way?
我已经阅读了几个类似的主题,但还没有成功。我觉得我错过或误解了一些基本的东西,这就是我失败的原因。
我有一个 'application' 写在 python 中,我想在标准 setup.py 的帮助下部署它。由于功能复杂,它由不同的 python 模块组成。但是单独发布这些模块是没有意义的,因为它们太具体了。
预期结果是在 pip install
的帮助下将软件包安装在系统中,并且可以从 OS 命令行使用简单的 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
所以,这个简单的 'application' 工作正常,我想在下面的帮助下分发它
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]$
问题来了。使用上述 entry_points 来自 setup.py 我希望能够使用 ./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
将目录 aaa
和 bbb
与 app.py
放在一个目录 app
中。 IE。从这个角度来看app.py
应该使用import app.aaa
而不是import aaa
。但是,如果我这样做,那么我的应用程序在开发过程中 运行s 出现错误:
ModuleNotFoundError: No module named 'app.aaa'; 'app' is not a package
这也是合乎逻辑的,因为当时没有 app
可用的软件包...(它正在开发中并且未安装在系统中...)
终于。问题是 - 为包含多个自己的模块的独立 python 应用程序创建目录结构和 setup.py 的正确方法是什么?
UPD
经过以下修改,我现在得到的最有希望的结果(但在评论中讨论后证明是错误的):
- 将 app.py 从
<root>/app
移动到 <root>
本身
- 我在
setup.py
中引用了 py_modules=['app']
- 我将导入从
import aaa.method1
更改为 import app.aaa.method1
等
这种方式包在我的开发环境和安装后都有效。
但是我遇到了 entry_points
的问题 - 我看不到如何配置入口点以使用 app.py
中的 main()
,它不是 app
包的一部分,而是一个单独的包模块....
IE。新结构是
<root>
├─ app
| ├─ aaa
| | └── module_a.py
| ├─ bbb
| | └── module_b.py
| └──__init__.py
├─ docs
| └── .....
├─ tests
| └── .....
├─ app.py
└─ setup.py
即这里的逻辑 - 有 2 个独立的实体:
- 一个空包
app
(只包含init.py)和子包aaa
、bbb
等
- 使用子包
app.aaa
、app.bbb
中函数的脚本 app.py
但正如我所写 - 我看不出如何定义 app.py
的入口点以允许它直接来自 OS 命令行的 运行。
使用该目录(包)结构,在您的 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
sinoroc 的回答大部分是正确的 - 他执行了一个突出显示所有选项的正确示例。它显示了构建 python 包的正确方法。但是在任何 运行 之前,应该首先将这个包安装到 venv 中。然后需要 pip install --editable
选项才能继续 develop/debug IDE 中的包(它在 venv 中安装包但将源文件保留在原始位置)。
经过长时间的评论讨论,我来到了 An Overview of Packaging for Python 页面,该页面解释了所有选项并强调 Python 的本机打包主要用于分发可重用代码,称为图书馆。我认为这是我误解和最初问题的原因。
作为替代解决方案,我计划使用 PEP 441 -- Improving Python ZIP Application Support,这对于我的情况来说似乎是一种正确的方法。
我已经阅读了几个类似的主题,但还没有成功。我觉得我错过或误解了一些基本的东西,这就是我失败的原因。
我有一个 'application' 写在 python 中,我想在标准 setup.py 的帮助下部署它。由于功能复杂,它由不同的 python 模块组成。但是单独发布这些模块是没有意义的,因为它们太具体了。
预期结果是在 pip install
的帮助下将软件包安装在系统中,并且可以从 OS 命令行使用简单的 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
所以,这个简单的 'application' 工作正常,我想在下面的帮助下分发它
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]$
问题来了。使用上述 entry_points 来自 setup.py 我希望能够使用 ./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
将目录 aaa
和 bbb
与 app.py
放在一个目录 app
中。 IE。从这个角度来看app.py
应该使用import app.aaa
而不是import aaa
。但是,如果我这样做,那么我的应用程序在开发过程中 运行s 出现错误:
ModuleNotFoundError: No module named 'app.aaa'; 'app' is not a package
这也是合乎逻辑的,因为当时没有 app
可用的软件包...(它正在开发中并且未安装在系统中...)
终于。问题是 - 为包含多个自己的模块的独立 python 应用程序创建目录结构和 setup.py 的正确方法是什么?
UPD
经过以下修改,我现在得到的最有希望的结果(但在评论中讨论后证明是错误的):
- 将 app.py 从
<root>/app
移动到<root>
本身 - 我在
setup.py
中引用了py_modules=['app']
- 我将导入从
import aaa.method1
更改为import app.aaa.method1
等
这种方式包在我的开发环境和安装后都有效。
但是我遇到了 entry_points
的问题 - 我看不到如何配置入口点以使用 app.py
中的 main()
,它不是 app
包的一部分,而是一个单独的包模块....
IE。新结构是
<root>
├─ app
| ├─ aaa
| | └── module_a.py
| ├─ bbb
| | └── module_b.py
| └──__init__.py
├─ docs
| └── .....
├─ tests
| └── .....
├─ app.py
└─ setup.py
即这里的逻辑 - 有 2 个独立的实体:
- 一个空包
app
(只包含init.py)和子包aaa
、bbb
等 - 使用子包
app.aaa
、app.bbb
中函数的脚本
app.py
但正如我所写 - 我看不出如何定义 app.py
的入口点以允许它直接来自 OS 命令行的 运行。
使用该目录(包)结构,在您的 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
sinoroc 的回答大部分是正确的 - 他执行了一个突出显示所有选项的正确示例。它显示了构建 python 包的正确方法。但是在任何 运行 之前,应该首先将这个包安装到 venv 中。然后需要 pip install --editable
选项才能继续 develop/debug IDE 中的包(它在 venv 中安装包但将源文件保留在原始位置)。
经过长时间的评论讨论,我来到了 An Overview of Packaging for Python 页面,该页面解释了所有选项并强调 Python 的本机打包主要用于分发可重用代码,称为图书馆。我认为这是我误解和最初问题的原因。
作为替代解决方案,我计划使用 PEP 441 -- Improving Python ZIP Application Support,这对于我的情况来说似乎是一种正确的方法。