跳转至

Typer CLI 框架

为什么要学 Typer

Typer 是一个用于构建 CLI 应用的 Python 库,由 FastAPI 的作者开发。它利用 Python 类型注解自动生成命令行参数解析、帮助文档和 Shell 自动补全。与 Click 和 argparse 相比,Typer 的代码更简洁、类型安全性更强,开发体验更接近现代 Python 风格。


核心概念

概念白话解释用途
App应用CLI 应用的主对象
Command命令子命令(如 git commit)
Argument参数必填的位置参数
Option选项可选的 --flag 参数
Callback回调应用级别的处理(全局选项)
Completion补全Shell Tab 补全

安装配置

pip install "typer[all]"  # 包含 rich 和 shellingham

快速上手

最简 CLI

import typer

def main(name: str):
    """向用户打招呼"""
    print(f"Hello {name}")

if __name__ == "__main__":
    typer.run(main)
python app.py World       # Hello World
python app.py --help       # 自动生成帮助文档

带选项的命令

import typer
from typing import Optional
from typing_extensions import Annotated

app = typer.Typer()

@app.command()
def greet(
    name: Annotated[str, typer.Argument(help="用户名")],
    greeting: Annotated[str, typer.Option(help="问候语")] = "Hello",
    count: Annotated[int, typer.Option("--count", "-c", help="重复次数")] = 1,
    uppercase: Annotated[bool, typer.Option(help="大写输出")] = False,
):
    """向用户打招呼"""
    message = f"{greeting} {name}"
    if uppercase:
        message = message.upper()
    for _ in range(count):
        typer.echo(message)

if __name__ == "__main__":
    app()
python app.py Alice                          # Hello Alice
python app.py Alice --greeting "你好" -c 3    # 你好 Alice (3次)
python app.py Alice --uppercase              # HELLO ALICE

子命令

import typer

app = typer.Typer(help="项目管理工具")

@app.command()
def init(name: str):
    """初始化项目"""
    typer.echo(f"初始化项目: {name}")

@app.command()
def build(release: bool = False):
    """构建项目"""
    mode = "release" if release else "debug"
    typer.echo(f"构建模式: {mode}")

@app.command()
def deploy(env: str = "staging"):
    """部署项目"""
    typer.echo(f"部署到: {env}")

if __name__ == "__main__":
    app()
python app.py init myproject
python app.py build --release
python app.py deploy --env production

进阶用法

交互式输入

@app.command()
def configure():
    name = typer.prompt("项目名称")
    password = typer.prompt("密码", hide_input=True)
    confirm = typer.confirm("确认创建?")

    if confirm:
        typer.echo(f"创建项目 {name}")
    else:
        typer.echo("已取消")
        raise typer.Abort()

枚举类型

from enum import Enum

class Environment(str, Enum):
    dev = "dev"
    staging = "staging"
    prod = "prod"

@app.command()
def deploy(env: Environment = Environment.staging):
    typer.echo(f"部署到 {env.value}")

Rich 集成

from rich.console import Console
from rich.table import Table
from rich.progress import track

console = Console()

@app.command()
def status():
    table = Table(title="服务状态")
    table.add_column("服务", style="cyan")
    table.add_column("状态", style="green")
    table.add_row("API", "✓ 运行中")
    table.add_row("数据库", "✓ 运行中")
    table.add_row("缓存", "✗ 已停止")
    console.print(table)

@app.command()
def process(files: list[str]):
    for file in track(files, description="处理中..."):
        import time; time.sleep(0.5)
    typer.echo("完成")

命令组嵌套

app = typer.Typer()
user_app = typer.Typer(help="用户管理")
app.add_typer(user_app, name="user")

@user_app.command("list")
def user_list():
    typer.echo("用户列表")

@user_app.command("create")
def user_create(name: str, email: str):
    typer.echo(f"创建用户: {name} ({email})")
python app.py user list
python app.py user create Alice alice@example.com

版本和回调

def version_callback(value: bool):
    if value:
        typer.echo("v1.0.0")
        raise typer.Exit()

@app.callback()
def main(
    version: Annotated[
        Optional[bool],
        typer.Option("--version", "-v", callback=version_callback)
    ] = None,
    verbose: bool = False,
):
    """我的 CLI 工具"""
    if verbose:
        typer.echo("详细模式已开启")

自动补全安装

# 安装 shell 补全
python app.py --install-completion

# 显示补全脚本
python app.py --show-completion

常见问题

Q: 与 Click 的关系?

Typer 基于 Click 构建,但用类型注解替代了装饰器参数。如果需要 Click 的高级功能,可以混合使用。

Q: 如何打包为独立命令?

# pyproject.toml
[project.scripts]
mycli = "my_package.main:app"

Q: 如何处理文件参数?

from pathlib import Path

@app.command()
def process(
    input_file: Path = typer.Argument(..., exists=True, readable=True),
    output_file: Path = typer.Option("output.txt"),
):
    content = input_file.read_text()
    output_file.write_text(content.upper())

参考资源

  • GitHub:https://github.com/tiangolo/typer
  • 文档:https://typer.tiangolo.com/
  • 教程:https://typer.tiangolo.com/tutorial/
  • Click 文档:https://click.palletsprojects.com/