mxivideo/docs/typer-development-standards.md

13 KiB
Raw Permalink Blame History

Typer 开发规范

🎯 核心原则

基于您选择的方案2: 纯Typer实现制定以下开发规范确保与现有JSON-RPC进度条架构完美集成。

📋 开发规范总览

1. 项目结构规范

python_core/services/{service_name}/
├── __init__.py          # 统一导入
├── types.py             # 数据类型定义dataclass + enum
├── service.py           # 业务逻辑服务
├── cli.py               # Typer命令行接口
└── __main__.py          # 模块入口

2. 命名规范

  • 服务名: snake_case (如: media_manager, scene_detection)
  • 类名: PascalCase (如: MediaManagerCommander, SceneDetectionService)
  • 函数名: snake_case (如: upload_video, batch_detect)
  • 常量: UPPER_SNAKE_CASE (如: DEFAULT_THRESHOLD, MAX_FILE_SIZE)

🔧 Typer CLI 开发规范

1. 基础结构模板

#!/usr/bin/env python3
"""
{服务名}命令行接口
"""

import typer
from typing import Optional, List
from pathlib import Path
from enum import Enum
from rich.console import Console

from .types import {相关类型}
from .service import {服务类}
from python_core.utils.progress import ProgressJSONRPCCommander

console = Console()

class {服务名}Commander(ProgressJSONRPCCommander):
    """基于Typer的{服务名}命令行接口"""
    
    def __init__(self):
        super().__init__("{service_name}")
        self.app = typer.Typer(
            name="{service_name}",
            help="🎯 {服务描述}",
            rich_markup_mode="rich",
            no_args_is_help=True
        )
        self.service = {服务类}()
        self._setup_commands()
    
    def _register_commands(self):
        """注册命令(继承要求)"""
        pass  # Typer通过装饰器自动注册
    
    def _is_progressive_command(self, command: str) -> bool:
        """判断是否需要进度报告"""
        return command in ["batch_*", "compare_*", "analyze_*"]
    
    def _execute_with_progress(self, command: str, args: dict):
        """执行带进度的命令"""
        # 通过Typer命令直接处理
        pass
    
    def _execute_simple_command(self, command: str, args: dict):
        """执行简单命令"""
        # 通过Typer命令直接处理
        pass
    
    def _setup_commands(self):
        """设置Typer命令"""
        # 在这里定义所有命令
        pass
    
    def run(self):
        """运行CLI"""
        self.app()

def main():
    """主入口函数"""
    commander = {服务名}Commander()
    commander.run()

if __name__ == "__main__":
    main()

2. 类型定义规范

枚举类型

from enum import Enum

class OutputFormat(str, Enum):
    """输出格式枚举"""
    JSON = "json"
    CSV = "csv"
    TXT = "txt"
    
    def __str__(self) -> str:
        return self.value

class ProcessingMode(str, Enum):
    """处理模式枚举"""
    FAST = "fast"
    BALANCED = "balanced"
    QUALITY = "quality"

数据类型

from dataclasses import dataclass
from typing import Optional, List
from pathlib import Path

@dataclass
class ProcessConfig:
    """处理配置"""
    input_path: Path
    output_path: Optional[Path] = None
    format: OutputFormat = OutputFormat.JSON
    mode: ProcessingMode = ProcessingMode.BALANCED
    tags: List[str] = None
    
    def __post_init__(self):
        if self.tags is None:
            self.tags = []

3. 命令定义规范

基础命令模板

@self.app.command()
def {command_name}(
    # 必需参数 - 使用Argument
    input_path: Path = typer.Argument(
        ..., 
        help="📁 输入文件/目录路径",
        exists=True
    ),
    
    # 可选参数 - 使用Option
    output_format: OutputFormat = typer.Option(
        OutputFormat.JSON,
        "--format", "-f",
        help="📄 输出格式"
    ),
    
    tags: Optional[str] = typer.Option(
        None,
        "--tags", "-t", 
        help="🏷️ 标签列表(逗号分隔)"
    ),
    
    # 布尔选项
    verbose: bool = typer.Option(
        False,
        "--verbose", "-v",
        help="📝 详细输出"
    ),
    
    # 数值选项
    threshold: float = typer.Option(
        30.0,
        "--threshold",
        min=0.0,
        max=100.0,
        help="🎚️ 处理阈值 (0-100)"
    )
):
    """
    📋 命令描述
    
    详细说明命令的功能和用法。
    """
    # 参数验证
    if not input_path.exists():
        console.print("❌ [red]输入路径不存在[/red]")
        raise typer.Exit(1)
    
    # 解析标签
    tag_list = []
    if tags:
        tag_list = [tag.strip() for tag in tags.split(",")]
    
    # 显示开始信息
    console.print(f"🚀 开始处理: [bold blue]{input_path}[/bold blue]")
    
    try:
        # 调用服务逻辑
        result = self.service.process(
            input_path=input_path,
            output_format=output_format,
            tags=tag_list,
            verbose=verbose,
            threshold=threshold
        )
        
        # 显示结果
        console.print("✅ [bold green]处理完成[/bold green]")
        if verbose:
            console.print(f"📊 结果: {result}")
            
    except Exception as e:
        console.print(f"❌ [red]处理失败: {e}[/red]")
        raise typer.Exit(1)

进度命令模板

@self.app.command()
def batch_process(
    input_directory: Path = typer.Argument(..., help="📁 输入目录"),
    output_path: Optional[Path] = typer.Option(None, help="📄 输出文件路径"),
    recursive: bool = typer.Option(False, "--recursive", "-r", help="🔄 递归处理子目录")
):
    """
    📦 批量处理命令(带进度条)
    """
    console.print(f"📦 批量处理目录: [bold blue]{input_directory}[/bold blue]")
    
    # 扫描文件
    files = self._scan_files(input_directory, recursive)
    
    if not files:
        console.print("⚠️ [yellow]未找到可处理的文件[/yellow]")
        return
    
    # 使用进度任务
    with self.create_task("批量处理", len(files)) as task:
        results = []
        
        for i, file_path in enumerate(files):
            task.update(i, f"处理文件: {file_path.name} ({i+1}/{len(files)})")
            
            try:
                result = self.service.process_single(file_path)
                results.append(result)
            except Exception as e:
                console.print(f"❌ 处理失败 {file_path.name}: {e}")
        
        task.finish(f"批量处理完成: {len(results)}/{len(files)} 成功")
    
    # 保存结果
    if output_path:
        self._save_results(results, output_path)
        console.print(f"📄 结果已保存到: {output_path}")

4. 错误处理规范

# 输入验证
def validate_input(self, input_path: Path) -> None:
    """验证输入参数"""
    if not input_path.exists():
        console.print(f"❌ [red]文件不存在: {input_path}[/red]")
        raise typer.Exit(1)
    
    if input_path.is_dir() and not any(input_path.iterdir()):
        console.print(f"⚠️ [yellow]目录为空: {input_path}[/yellow]")
        raise typer.Exit(1)

# 异常处理
try:
    result = self.service.process(input_path)
except FileNotFoundError:
    console.print("❌ [red]文件未找到[/red]")
    raise typer.Exit(1)
except PermissionError:
    console.print("❌ [red]权限不足[/red]")
    raise typer.Exit(1)
except Exception as e:
    console.print(f"❌ [red]处理失败: {e}[/red]")
    if verbose:
        console.print_exception()
    raise typer.Exit(1)

5. 输出规范

Rich输出样式

from rich.table import Table
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn

# 状态表格
def show_status(self, data: dict):
    """显示状态表格"""
    table = Table(title="📊 处理状态")
    table.add_column("项目", style="cyan")
    table.add_column("状态", style="green")
    table.add_column("详情", style="yellow")
    
    for key, value in data.items():
        table.add_row(key, "✅ 完成", str(value))
    
    console.print(table)

# 信息面板
def show_summary(self, message: str, title: str = "📋 摘要"):
    """显示信息面板"""
    panel = Panel(
        f"[bold blue]{message}[/bold blue]",
        title=title,
        border_style="green"
    )
    console.print(panel)

# 进度条非JSON-RPC场景
def show_local_progress(self, items: list, description: str):
    """显示本地进度条"""
    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        console=console
    ) as progress:
        task = progress.add_task(description, total=len(items))
        
        for item in items:
            # 处理逻辑
            progress.advance(task)

📦 服务集成规范

1. 服务基类继承

from python_core.services.base import ProgressServiceBase

class MediaManagerService(ProgressServiceBase):
    """媒体管理服务"""
    
    def get_service_name(self) -> str:
        return "media_manager"
    
    def process_with_progress(self, items: list, operation_name: str):
        """带进度的处理"""
        self.report_progress(f"开始{operation_name}")
        
        for i, item in enumerate(items):
            self.report_progress(f"{operation_name}: {item} ({i+1}/{len(items)})")
            # 处理逻辑
        
        self.report_progress(f"完成{operation_name}")

2. 存储集成

def save_results(self, results: list, collection_type: str = "results"):
    """保存结果到存储"""
    for i, result in enumerate(results):
        key = f"result_{i}_{int(time.time())}"
        self.save_data(collection_type, key, result)
    
    return len(results)

def load_recent_results(self, collection_type: str = "results", limit: int = 10):
    """加载最近的结果"""
    keys = self.list_keys(collection_type)
    recent_keys = sorted(keys)[-limit:]
    
    return self.load_batch_data(collection_type, recent_keys)

🧪 测试规范

1. 命令测试

import pytest
from typer.testing import CliRunner

def test_upload_command():
    """测试上传命令"""
    runner = CliRunner()
    result = runner.invoke(app, ["upload", "test.mp4", "--tags", "test"])
    
    assert result.exit_code == 0
    assert "上传完成" in result.stdout

def test_batch_command():
    """测试批量命令"""
    runner = CliRunner()
    result = runner.invoke(app, ["batch-upload", "/test/dir"])
    
    assert result.exit_code == 0
    assert "批量处理完成" in result.stdout

2. 进度测试

def test_progress_integration():
    """测试进度集成"""
    commander = MediaManagerCommander()
    
    # 模拟进度回调
    progress_messages = []
    def mock_callback(message):
        progress_messages.append(message)
    
    commander.set_progress_callback(mock_callback)
    
    # 执行带进度的操作
    commander.service.process_with_progress(["item1", "item2"], "测试")
    
    assert len(progress_messages) > 0
    assert "开始测试" in progress_messages[0]

📚 文档规范

1. 命令文档

@app.command()
def upload(
    video_path: Path = typer.Argument(..., help="📹 视频文件路径")
):
    """
    📤 上传视频文件
    
    上传单个视频文件到媒体库,自动进行场景检测和元数据提取。
    
    示例:
        python -m media_manager upload video.mp4 --tags "demo,test"
        python -m media_manager upload /path/to/video.mp4 --format json
    
    注意:
        - 支持的格式: MP4, AVI, MOV, MKV
        - 文件大小限制: 2GB
        - 自动检测重复文件
    """

2. 帮助信息

# 应用级帮助
app = typer.Typer(
    name="media_manager",
    help="""
    🎬 媒体管理器
    
    功能强大的视频处理和管理工具,支持:
    • 视频上传和元数据提取
    • 自动场景检测和分割
    • 批量处理和进度跟踪
    • 多种输出格式
    
    使用 --help 查看具体命令帮助。
    """,
    rich_markup_mode="rich"
)

🎯 最佳实践

1. 性能优化

  • 使用类型提示提高IDE支持
  • 合理使用Rich组件避免过度渲染
  • 大文件处理时显示进度条
  • 异步操作使用适当的进度反馈

2. 用户体验

  • 提供清晰的错误信息
  • 使用emoji和颜色增强可读性
  • 重要操作前进行确认
  • 提供详细的帮助文档

3. 代码质量

  • 所有函数添加类型提示
  • 使用dataclass定义数据结构
  • 遵循PEP 8代码风格
  • 编写完整的单元测试

这套规范确保了Typer实现与您现有架构的完美集成同时提供了现代化、类型安全的开发体验