Featured image of post Build Python Command-Line Applications Quickly with Typer

Build Python Command-Line Applications Quickly with Typer

Want to quickly develop Python command-line programs? Give Typer a try

In Python, the most basic way to develop command-line programs is to use the Python standard library argparse to define command-line arguments and various interactions. It is, however, relatively complex, and some common scenarios still need us to wrap them separately.

In this article we introduce another Python command-line tool library, typer, which is both very powerful and very easy to get started with, and see how to use it to help us quickly implement a command-line application.

Installation and Basic Usage

Installation

typer is a command-line implementation tool launched by the popular open source web API framework FastAPI. Like other Python third-party libraries, it is very easy to install.

pip instal typer

Basic Usage

Approach One

Write the most basic hello world program, user_typer.py

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

Call it directly via the typer command Run:

Demo>typer use_typer.py run

hello world!

Approach Two

You can also import typer in your code and then call it directly from within the program using its run method

1
2
3
4
5
6
7
import typer  

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

Run:

Demo>python use_typer.py

hello world!

Approach Three

Or declare the program as an application and use annotations to specify each command the application supports

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()

Run:

Demo>python use_typer.py

hello world!

Moreover, running python user_typer.py --help shows that the necessary help instructions have already been generated automatically.

Here we added a parameter to the main function, and you can see that typer in fact also automatically parses the parameters carried by the corresponding function into command parameters, with no need to redefine them.

Command Help and Default Values

Of course, as a command-line program, defining command help information is an important part.

typer also provides a very convenient way to add help for commands and parameters. For example, let’s add another command

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

The help information for the command itself can be specified directly in the command annotation via the help parameter, or it can come from the function’s own docstring, which typer also parses as command help. But if it has already been specified in the command annotation, the help information specified in the command annotation takes precedence

For the specified parameter name, here it is specified through Python’s type extension library and then through the type.Argument help parameter, and at the same time the parameter’s default value can also be specified.

Optional Parameters and Parameter Names

The parameter we specified here through type.Argument is actually a required parameter. So what if we want a certain parameter to be optional? Here typer distinguishes them using two different types. By definition,

  • typer.Argument is required
  • typer.Option is optional

These correspond to the two different panels Arguments and Options in the help information. But whether it must actually be entered is determined by whether a default value has been specified for the corresponding parameter; if no default value is specified, it means this parameter must be entered.

Another difference between Option and Argument is that an Option needs an option name specified, that is, a marker carrying -- or the short parameter marker -, such as --name, --help

By default typer.Option directly converts the parameter name into a long marker, and you can also specify other aliases yourself. At the same time you can also specify a short marker

For example, we can add another parameter 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}")

Richer Interface Enhancements

Besides being flexible and simple in parameter definition, typer also provides many enhancements for the interactive interface

Color and Structured Data Display Support

For example, for the command line, to distinguish information with different colors, here it uses another third-party library, rich; you can simply replace Python’s official plain print statement with the print method from the rich library to achieve richer color control and structured data display, and so on

 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)

Execution effect

Prompt Messages

typer.Option can be switched to prompt mode, that is, for required parameters it can prompt for input

For example, in the above example, adding prompt=True will prompt for input when no parameter is entered

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

Run:

Password and Information Hiding

For some password-type parameters, hiding and confirmation options are also supported. For example, let’s add a passwd parameter

 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]")

Progress Bar Implementation

typer also supports progress-bar-type information interaction quite conveniently, again using the progress enhancements in rich

 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)

Multi-level Command Nesting

For multi-level command nesting, typer of course also provides good support. This is done by declaring different applications and then using the add_typer method to complete the hierarchical association.

The official example demonstrates this very clearly

 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()

Use --help to see the effect of this arrangement


The above is a rundown of the usage of the command-line tool typer.

Built with Hugo
Theme Stack designed by Jimmy