Featured image of post 用Typer快速开发python命令行应用

用Typer快速开发python命令行应用

想快速开发Python命令行程序?试试Typer

在Python语言中,开发命令行程序,最基本的方式是通过Python标准库argparse来进行命令行参数和各种交互定义。但相对来说还比较复杂,一些常用的场景也需要我们单独封装。

本文我们介绍另一个非常强大,也非常容易上手使用的python命令行工具库typer,看看如何利用它来帮我们快速完成一个命令行应用的实现。

安装和基本使用

安装

typer 是一个由 热门开源web接口框架 FastAPI推出的命令行实现工具。和其他 python 第三方库类似,安装很方便。

pip instal typer

基本用法

姿势一

编写一个最基本的hello world程序user_typer.py

1
2
3
4
5
def main():  
    print("hello world!")  
  
if __name__ == "__main__":  
    main()

直接通过typer命令调用 运行:

Demo>typer use_typer.py run

hello world!

姿势二

也可以在代码中引入typer,然后用它的run方法直接在程序中调用

1
2
3
4
5
6
7
import typer  

def main():  
    print("hello world!")  
  
if __name__ == "__main__":  
    typer.run(main)

运行:

Demo>python use_typer.py

hello world!

姿势三

或者将程序声明为一个应用,然后用注解指定应用的每一个支持的命令

1
2
3
4
5
6
7
8
9
import typer  
app = typer.Typer()  
  
@app.command()  
def main(name):  
    print("hello world!")  
  
if __name__ == "__main__":  
    app()

运行:

Demo>python use_typer.py

hello world!

而且运行 python user_typer.py --help, 可以看到已经自动生成了必要的帮助说明。

这里我们在main函数中加了一个参数,可以看到typer 其实还会自动将对应的函数携带的参数解析为命令参数,无需重新定义。

命令帮助和默认值

当然作为命令行程序,命令的帮助信息定义是一个重要部分。

typer也提供了很方便的添加命令和参数帮助的途径。 比如我们再添加一个命令

1
2
3
4
5
6
@app.command(help="问候指定的小伙伴")  
def hello(name:Annotated[str,typer.Argument(help="被问候的伙伴姓名")]="城下秋草"):  
    """  
    这里也可以直接作为命令帮助,和command的help参数作用一样,  
    但优先级没有command注解高  
    """    print(f"你好,{name}")

命令本身的帮助信息可以直接在command注解中通过help参数说明,或者也可以通过函数本身的docstring,typer也会把它解析为命令帮助。但如果command注解中已经指定,则会以command注解中指定的帮助信息为准

而指定的参数name, 这里是通过python的类型扩展库,再通过type.Argument的help参数来指定,并且同时还可以指定参数的默认值。

可选参数和参数名

这里我们通过type.Argument指定的参数,其实是必填参数。那如果我们希望某一个参数是可选的呢? 这里typer中是根据两个不同类型来区分。定义上来说,

  • typer.Argument 是必选
  • typer.Option 是可选

对应到帮助信息中的 ArgumentsOptions 两个不同的pannel。 但是否必须输入,其实是根据是否指定了对应参数的默认值来确认的,如果没有指定默认值,那么就代表这个参数是必须输入的。

另外 Option 和 Argument 的不同还在于 Option 是需要指定option名称, 也就是形如 --name, --help 这用带 -- 或者 - 短参数标记

默认 typer.Option是直接将参数名转化为长标记,也可以自行指定其他别名。 同时也可以指定指定短标记

比如我们可以再增加一个参数 last_name

1
2
3
4
5
6
7
@app.command(help="问候指定的小伙伴")  
def hello(last_name:Annotated[str,typer.Option("--last","-l",help="被问候的人员姓什么")],  
          name:Annotated[str,typer.Argument(help="被问候的伙伴名称")]="城下秋草"):  
    """  
    这里也可以直接作为命令帮助,和command的help参数作用一样,  
    但优先级没有command注解高  
    """    print(f"你好,{name} {last_name}")

更丰富的界面增强

typer 除了在参数定义上的灵活简便外,对于交互界面也提供了很多增强

颜色和结构化数据的显示支持

比如针对命令行,不同颜色的信息区分,这里它利用了另一个第三方库 rich, 可以简单地用rich库中的print方法替代掉python官方的简单打印语句,实现更丰富的颜色控制和结构化数据展示等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def main():  
    print("显示结构数据:")  
    print(data)  
  
    print("不同颜色和emoji:")  
    print("[bold red]警告![/bold red] [green]数据data[/green] 不匹配! :boom:")  
  
  
if __name__ == "__main__":  
    typer.run(main)

执行效果

提示信息

typer.Option 可以支持转为提示模式,也就是对于需要的参数,可以提示输入

比如上面的例子,加上prompt=True就会在没有输入参数时提示输入

1
2
@app.command(help="问候指定的小伙伴")  
def hello(last_name:Annotated[str,typer.Option("--last","-l",help="被问候的人员姓什么", prompt=True)]

运行:

密码和信息隐藏

对于一些密码类的参数,还支持隐藏和确认的选项,比如我们增加一个passwd参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@app.command(help="问候指定的小伙伴")  
def hello(last_name:Annotated[str,  
typer.Option("--last",  
             "-l",  
             help="被问候的人员姓什么",  
             prompt=True)]="黄",  
passwd:Annotated[str,typer.Option(prompt=True, confirmation_prompt=True, hide_input=True)]=None,  
name:Annotated[str,typer.Argument(help="被问候的伙伴名称")]="城下秋草"):  
    """  
    这里也可以直接作为命令帮助,和command的help参数作用一样,  
    但优先级没有command注解高  
    """    if passwd == "123":  
        print(f"你好,{name} {last_name}")  
    else:  
        print("[bold red]密码输入错误,不能展示![/bold red]")

进度条实现

typer还能比较方便地支持进度条类的信息交互,同样可以利用rich中progress的增强

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from rich.progress import track  
def running():  
    total = 0  
    for value in track(range(100), description="正在执行中...."):  
        # Fake processing time  
        time.sleep(0.01)  
        total += 1  
    print(f"已处理 {total} 任务")  
  
if __name__ == "__main__":  
    typer.run(running)

多层命令嵌套

对于多层的命令嵌套,typer当然也提供了良好的支持。这里是通过声明不同的应用,再利用add_typer方法完成层级的关联。

官方给出的示例体现的很清楚

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import typer

app = typer.Typer()
items_app = typer.Typer()
app.add_typer(items_app, name="items")
users_app = typer.Typer()
app.add_typer(users_app, name="users")


@items_app.command("create")
def items_create(item: str):
    print(f"Creating item: {item}")


@items_app.command("delete")
def items_delete(item: str):
    print(f"Deleting item: {item}")


@items_app.command("sell")
def items_sell(item: str):
    print(f"Selling item: {item}")


@users_app.command("create")
def users_create(user_name: str):
    print(f"Creating user: {user_name}")


@users_app.command("delete")
def users_delete(user_name: str):
    print(f"Deleting user: {user_name}")


if __name__ == "__main__":
    app()

--help看一下这样处理的效果


以上就是关于命令行工具 typer的用法梳理。

使用 Hugo 构建
主题 StackJimmy 设计