具有多个python入口点和内部依赖关系的存储库的最佳目录结构



我正在处理一个具有以下目录结构的项目:

project/
package1/
module1.py
module2.py
package2/
module1.py
module2.py
main1.py
main2.py
main3.py
...
mainN.py

其中,每个mainX.py文件都是从package1package2或两者导入模块的可执行Python脚本。package1package2是与项目的其余部分一起分发的子包(不是独立的(。

标准做法是将入口点放在顶级目录中。我有N个入口点,所以我把它们都放在顶级目录中。问题是N一直在增长,所以我的顶级目录被入口点淹没了。

我可以将mainX.py文件移动到一个子目录(比如project/run(,但随后所有的package1package2导入都会中断。我可以将package1package2提取到一个单独的存储库中,只希望它安装在系统上(即系统/用户python路径中(,但这会使安装复杂化。我可以作为先决条件或在运行时修改Python路径,但这很混乱,可能会带来意想不到的后果。我可以编写一个main.py入口点脚本,其中参数子分析器分别指向run/main1.py, ..., run/mainN.py,但这会在main.py和每个run/mainX.py文件之间引入耦合。

标准是什么;Pythonic";这个问题的解决方案?

标准解决方案是使用console_scripts封装作为入口点-请阅读此处的入口点规范。此功能可用于生成类似main1.py的脚本包装器。。。安装时的mainN.py

由于这些脚本包装器是生成的代码,它们根本不存在于项目源目录中,因此混乱的问题(">顶级目录中充斥着入口点"(就消失了。

脚本的实际代码将在包中的某个地方定义,main*.py脚本实际挂接到包中代码的位置在包元数据中定义。您可以将控制台脚本入口点挂接到包中的任何可调用对象,前提是它可以在没有参数的情况下调用(可选参数,即具有默认值的参数,可以(。

project
├── package1
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
├── package2
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
├── pyproject.toml
└── scripts
└── __init__.py

这是新的目录结构。注意添加了__init__.py文件,这表明package1package2是包,而不仅仅是子目录。

对于添加的新文件,这里是scripts/__init__.py:

# these imports should work
#   from package1 import ...
#   from package2.module1 import ...
def myscript1():
# put whatever main1.py did here
print("hello")
def myscript2():
# put whatever main2.py did here
print("world")

这些不需要都在同一个文件中,实际上,只要更新包装定义的[project.scripts]部分中的挂钩,就可以将它们放在包中任何需要的地方。

这是包装的定义:

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "mypackage"
version = "0.0.1"
[project.scripts]
"main1.py" = "scripts:myscript1"
"main2.py" = "scripts:myscript2"
[tool.setuptools]
packages = ["package1", "package2", "scripts"]

现在,当安装包时,将生成控制台脚本:

$ pip install --editable .
...
Successfully installed mypackage-0.0.1
$ main1.py
hello
$ main2.py
world

如前所述,这些可执行文件不在项目目录中,而是在站点的脚本目录中,该目录将出现在$PATH上。脚本由pip生成,使用distlib的ScriptMaker中的vendored代码。如果你仔细查看生成的脚本文件,你会发现它们是简单的包装器,它们只会从包中导入可调用文件,然后调用它。任何参数解析、日志记录配置等都必须在包代码中处理。

$ ls
mypackage.egg-info  package1  package2  pyproject.toml  scripts
$ which main2.py
/tmp/project/.venv/bin/main2.py

脚本目录的确切位置取决于您的平台,但在Python:中可以这样检查

>>> import sysconfig
>>> sysconfig.get_path("scripts")
'/tmp/project/.venv/bin'

您的解决方案是对附加包中的入口点进行排序,但将它们作为模块运行,而不是直接按文件运行。

project/
package1/
module1.py
module2.py
package2/
module1.py
module2.py
run/
main1.py
main2.py
main3.py
...
mainN.py
python -m run.main3

这样,您当前的目录(希望是项目根目录(仍然是sys.path的预处理目录,而不是包含脚本的目录。

更规范的解决方案将包括

  • 配置export PYTHONPATH=path/to/your/project
  • 在virtualenv的sitepackages文件夹内的foobar.pth文件中写入path/to/your/project
  • 使用具有子命令功能的单个入口点,例如https://click.palletsprojects.com/en/latest/api/#click.Group

最新更新