Typer,一个现代化CLI应用创建的Python库!

文摘   2024-11-03 07:50   上海  

哈喽,大家好!我是风哥,一个资深Python工程师。今天给大家介绍一个超强的命令行工具库——Typer!想写出高大上的命令行程序但是被 argparse 搞得头大?来试试 Typer 吧,它能让你轻松打造出专业级的命令行应用!

安装配置

先把环境搞起来:

1pip install "typer[all]"  # 安装完整版,包含所有依赖

基本项目结构建议这样:

 1project_root/
2
3├── cli/
4│   ├── __init__.py
5│   ├── main.py
6│   └── commands/
7│       ├── __init__.py
8│       ├── user.py
9│       └── config.py
10
11└── setup.py

基础命令创建

来个简单的例子热热身:

 1# main.py
2import typer
3from typing import Optional
4from pathlib import Path
5
6app = typer.Typer(help="超强的文件处理工具")
7
8@app.command()
9def process_file(
10    file_path: Path = typer.Argument(..., help="要处理的文件路径"),
11    output: Optional[Path] = typer.Option(None, "--output""-o", help="输出文件路径"),
12    force: bool = typer.Option(False, "--force""-f", help="是否强制覆盖")
13)
:

14    """处理指定文件"""
15    typer.echo(f"处理文件: {file_path}")
16    if not file_path.exists():
17        typer.secho(f"文件不存在!", fg=typer.colors.RED)
18        raise typer.Exit(code=1)
19
20    # 处理逻辑...
21
22if __name__ == "__main__":
23    app()

多命令管理

实际项目中常用的多命令结构:

 1# commands/user.py
2import typer
3from typing import List, Optional
4from pydantic import BaseModel
5
6user_app = typer.Typer()
7
8class User(BaseModel):
9    name: str
10    age: int
11    roles: List[str]
12
13@user_app.command()
14def create(
15    name: str = typer.Option(..., prompt=True),
16    age: int = typer.Option(..., prompt=True),
17    roles: List[str] = typer.Option(["user"], "--role""-r"),
18)
:

19    """创建新用户"""
20    user = User(name=name, age=age, roles=roles)
21    typer.secho(f"创建用户成功:{user.json()}", fg=typer.colors.GREEN)
22
23@user_app.command()
24def list_users(
25    role: Optional[str] = typer.Option(None, "--role""-r"),
26    sort_by: str = typer.Option("name""--sort-by""-s")
27)
:

28    """列出所有用户"""
29    # 实现用户列表逻辑...

高级特性

子命令嵌套

 1# main.py
2import typer
3from commands import user, config
4
5app = typer.Typer()
6app.add_typer(user.user_app, name="user", help="用户管理相关命令")
7app.add_typer(config.config_app, name="config", help="配置管理相关命令")
8
9@app.callback()
10def callback():
11    """
12    超强的命令行工具
13    """

14    pass

进度条和加载动画

 1import time
2from rich.progress import track
3
4@app.command()
5def process_items(count: int = typer.Option(10, help="处理项目数量")):
6    """带进度条的处理"""
7    with typer.progressbar(range(count)) as progress:
8        for value in progress:
9            time.sleep(0.1)
10
11    # 或者用 rich 的进度条
12    for _ in track(range(count), description="处理中..."):
13        time.sleep(0.1)

交互式提示

 1@app.command()
2def configure():
3    """交互式配置"""
4    if not typer.confirm("确定要开始配置吗?"):
5        raise typer.Abort()
6
7    username = typer.prompt("请输入用户名")
8    password = typer.prompt("请输入密码", hide_input=True)
9
10    choices = ["开发环境""测试环境""生产环境"]
11    env = typer.prompt(
12        "选择环境",
13        type=typer.Choice(choices),
14        show_choices=True
15    )

自定义补全

 1def get_completion_items():
2    return ["选项1""选项2""选项3"]
3
4@app.command()
5def complete(
6    item: str = typer.Option(
7        ...,
8        autocompletion=get_completion_items
9    )

10)
:

11    """带自动补全的命令"""
12    typer.echo(f"选择了: {item}")

错误处理和日志

专业的错误处理方案:

 1import logging
2from typing import Optional
3from pathlib import Path
4
5# 配置日志
6logging.basicConfig(
7    level=logging.INFO,
8    format="%(asctime)s [%(levelname)s] %(message)s",
9    handlers=[
10        logging.FileHandler("app.log"),
11        logging.StreamHandler()
12    ]
13)
14
15class AppError(Exception):
16    """自定义错误"""
17    def __init__(self, message: str, code: int = 1):
18        self.message = message
19        self.code = code
20
21def handle_error(func):
22    """错误处理装饰器"""
23    def wrapper(*args, **kwargs):
24        try:
25            return func(*args, **kwargs)
26        except AppError as e:
27            typer.secho(f"错误: {e.message}", fg=typer.colors.RED)
28            raise typer.Exit(code=e.code)
29        except Exception as e:
30            logging.exception("未知错误")
31            typer.secho(f"系统错误!", fg=typer.colors.RED)
32            raise typer.Exit(code=1)
33    return wrapper
34
35@app.command()
36@handle_error
37def risky_command():
38    """可能出错的命令"""
39    raise AppError("出错啦!")

测试支持

写测试更方便:

 1from typer.testing import CliRunner
2import pytest
3
4runner = CliRunner()
5
6def test_hello():
7    result = runner.invoke(app, ["hello""--name""测试"])
8    assert result.exit_code == 0
9    assert "测试" in result.stdout
10
11@pytest.fixture
12def mock_db(mocker):
13    """模拟数据库"""
14    return mocker.patch("your_app.db")
15
16def test_create_user(mock_db):
17    result = runner.invoke(app, [
18        "user""create",
19        "--name""张三",
20        "--age""25"
21    ])
22    assert result.exit_code == 0
23    mock_db.create_user.assert_called_once()

项目打包发布

 1# setup.py
2from setuptools import setup, find_packages
3
4setup(
5    name="your-cli-app",
6    version="0.1.0",
7    packages=find_packages(),
8    install_requires=[
9        "typer[all]>=0.4.0",
10        "pydantic>=1.8.0",
11    ],
12    entry_points={
13        "console_scripts": [
14            "your-app=cli.main:app",
15        ],
16    },
17)

温馨提示:记得在 README.md 里写清楚安装和使用方法!

今天的Python学习之旅就到这里啦!记得动手敲代码,有问题随时在评论区问风哥哦。祝大家学习愉快,收获满满!

py学习基地ai
分享生活百态,情感故事,了解不一样的人生
 最新文章