创建和打包命令行工具

本指南将引导您创建和打包一个独立的命令行应用程序,该应用程序可以使用 pipx 进行安装。pipx 是一个用于创建和管理 Python 虚拟环境 并将包的可执行脚本(以及可用的手册页)暴露给命令行使用的工具。

创建包

首先,为 项目 创建一个源目录。为了举例,我们将构建一个简单的工具,根据命令行参数为一个人输出问候语(一个字符串)。

待办事项

在另一篇指南或讨论中提供关于 Python 包最佳结构的建议,并在此处链接。

此项目将遵循 src-layout,最终像这样的文件树,顶层文件夹和包名是 greetings

.
├── pyproject.toml
└── src
    └── greetings
        ├── cli.py
        ├── greet.py
        ├── __init__.py
        └── __main__.py

负责工具功能的实际代码将存储在 greet.py 文件中,以主模块命名。

import typer
from typing_extensions import Annotated


def greet(
    name: Annotated[str, typer.Argument(help="The (last, if --title is given) name of the person to greet")] = "",
    title: Annotated[str, typer.Option(help="The preferred title of the person to greet")] = "",
    doctor: Annotated[bool, typer.Option(help="Whether the person is a doctor (MD or PhD)")] = False,
    count: Annotated[int, typer.Option(help="Number of times to greet the person")] = 1
):
    greeting = "Greetings, "
    if doctor and not title:
        title = "Dr."
    if not name:
        if title:
            name = title.lower().rstrip(".")
        else:
            name = "friend"
    if title:
        greeting += f"{title} "
    greeting += f"{name}!"
    for i in range(0, count):
        print(greeting)

上述函数接收几个关键字参数,这些参数决定了要输出的问候语如何构造。现在,构造命令行界面以提供相同的参数,这在 cli.py 中完成。

import typer

from .greet import greet


app = typer.Typer()
app.command()(greet)


if __name__ == "__main__":
    app()

命令行界面使用 typer 构建,这是一个基于 Python 类型提示的易于使用的 CLI 解析器。它开箱即用地提供了自动补全和样式美观的命令行帮助。另一个选择是 argparse,一个包含在 Python 标准库中的命令行解析器。它足以满足大多数需求,但需要大量的代码(通常在 cli.py 中)才能正常运行。另外,docopt 可以仅基于 docstring 创建 CLI 接口;鼓励高级用户使用 clicktyper 即基于此)。

现在,添加一个空的 __init__.py 文件,将项目定义为一个常规的 导入包

__main__.py 文件标志着应用程序通过 runpy 运行时(即 python -m greetings,这对于扁平布局可以直接工作,但对于 src 布局需要安装包)的主要入口点,因此在此处初始化命令行界面。

if __name__ == "__main__":
    from greetings.cli import app
    app()

注意

为了能够直接从 项目源目录 调用命令行接口,即作为 python src/greetings,可以在此文件中放置一个特定的“技巧”;请参阅 从带有 src-layout 的源运行命令行接口 以了解更多信息。

pyproject.toml

项目的 元数据 放置在 pyproject.toml 中。pyproject 元数据键[build-system] 表格可以按照 编写你的 pyproject.toml 中描述的方式填写,添加对 typer 的依赖(本教程使用版本 0.12.3)。

为了让项目被识别为命令行工具,还需要添加一个 console_scripts 入口点(参见 创建可执行脚本)作为 子键

[project.scripts]
greet = "greetings.cli:app"

现在,项目的源目录已准备好转换为 分发包,使其可安装。

使用 pipx 安装包

按照 安装独立命令行工具 中描述的步骤安装 pipx 后,安装你的项目。

$ cd path/to/greetings/
$ pipx install .

这将暴露我们定义为入口点的可执行脚本,并使 greet 命令可用。让我们测试一下。

$ greet
Greetings, friend!
$ greet --doctor Brennan
Greetings, Dr. Brennan!
$ greet --title Ms. Parks
Greetings, Ms. Parks!
$ greet --title Mr.
Greetings, Mr. mr!

由于此示例使用 typer,您现在也可以通过使用 --help 选项调用程序来获取程序使用概述,或者通过 --install-completion 选项配置自动补全。

如果只是运行程序而不永久安装它,可以使用 pipx run,它将为其创建一个临时(但已缓存)的虚拟环境。

$ pipx run --spec . greet --doctor

然而,这种语法有点不切实际;由于我们上面定义的入口点名称与包名不匹配,我们需要明确说明要运行哪个可执行脚本(即使只存在一个)。

然而,针对此问题有一个更实际的解决方案,即 pipx run 特有的入口点。可以在 pyproject.toml 中定义如下:

[project.entry-points."pipx.run"]
greetings = "greetings.cli:app"

多亏了这个入口点(它 必须 与包名匹配),pipx 会将该可执行脚本选作默认脚本并运行它,从而使此命令成为可能:

$ pipx run . --doctor

结论

你现在知道如何打包用 Python 编写的命令行应用程序了。下一步可以是分发你的包,这意味着将其上传到 包索引,最常见的是 PyPI。要做到这一点,请遵循 打包你的项目 中的说明。完成之后,别忘了 研究一下 你的包是如何被接收的!