在 Python 中使用入口点时在 CLI 中区分导入包和 运行

Differentiating between an imported package and one run at the CLI when using Entrypoints in Python

我有一个 python 包,最常用作 CLI 工具,但有时我 运行 将它作为一个库用于我自己的目的。 (例如,将其变成网络应用程序或用于单元测试)

例如,我想在错误时 sys.exit(1) 在用作 CLI 命令时为最终用户提供一个很好的异常错误消息,但在使用时 raise 一个异常作为导入库。

我在 setup.py 中使用 entry_points:

entry_points={
        'console_scripts': [
            'jello=jello.cli:main'
        ]
    }

这很好用,但我无法轻易区分包在 CLI 中是 运行 还是导入的,因为 __name__ 始终是 jello.cli。这是因为入口点基本上是正常导入包。

我尝试创建一个 __main__.py 文件并将我的入口点指向那里,但这似乎没有什么不同。

我正在考虑检查 sys.argv[0] 以查看我的程序名称是否存在,但这似乎是一个脆弱的 hack。 (以防用户给命令起别名或其他什么)还有其他想法还是我做错了?现在我一直在向我的函数传递一个 as_lib 参数,因此它们的行为会根据它们是作为模块加载还是从 CLI 加载 运行 而有所不同,但我想摆脱它.

我相信你想要的是Python中的'main guard'。模块在任何给定时间都知道它的名字。如果该模块是从 CLI 执行的,则其名称为 __main__。因此,您可以做的是将您想要的所有功能放在模块级别的某个函数中,然后您可以在 'main guard' 中以不同于库的方式调用它。这是 'main guard' 的样子:

def myfunc(thing, stuff):
   if not stuff:
      raise MyExc

if __name__ == '__main__':
    try:
       myfunc(x, y)
    except MyExc:
       sys.exit(1)

此处,sys.exit 仅在模块作为脚本从命令行执行时才会在出错时调用。

这是可同时用作 cli 和库的包结构的最小示例。

这是目录结构:

egpkg/
├── setup.py
└── egpkg/
   ├── __init__.py
   ├── lib.py
   └── cli.py

这是setup.py中的entry_points。它与你的相同:

    entry_points={
        "console_scripts": [
            "egpkg_cli=egpkg.cli:main",
        ],
    },

__init__.py:

from .lib import func

cli.py
在这里您将定义 CLI 并处理您在其他 python 文件中定义的函数引发的任何问题。

import sys
import argparse

from egpkg import func


def main():
    p = argparse.ArgumentParser()
    p.add_argument("a", type=int)
    args = vars(p.parse_args())

    try:
        result = func(**args)
    except Exception as e:
        sys.exit(str(e))

    print(f"Got result: {result}", file=sys.stdout)

lib.py
这是定义库核心的地方,您应该按照您希望用户使用它的方式使用库。当你得到 values/input 不起作用时,你可以 raise 它。

def func(a):
    if a == 0:
        raise ValueError("Supplied value can't be 0")
    return 10 / a

然后在 python 控制台或脚本中,您可以:

In [1]: from egpkg import func
In [2]: func(2)
Out[2]: 5.0
In [3]: func(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/egpkg/egpkg/lib.py", line 3, in func
    raise ValueError("Supplied value can't be 0")
ValueError: Supplied value can't be 0

Supplied value can't be 0

并且来自 CLI:

(venv) ~ egpkg_cli 2
Got result: 5.0
(venv) ~ egpkg_cli 0
Supplied value can't be 0