# 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. 基础结构模板** ```python #!/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. 类型定义规范** #### **枚举类型** ```python 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" ``` #### **数据类型** ```python 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. 命令定义规范** #### **基础命令模板** ```python @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) ``` #### **进度命令模板** ```python @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. 错误处理规范** ```python # 输入验证 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输出样式** ```python 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. 服务基类继承** ```python 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. 存储集成** ```python 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. 命令测试** ```python 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. 进度测试** ```python 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. 命令文档** ```python @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. 帮助信息** ```python # 应用级帮助 app = typer.Typer( name="media_manager", help=""" 🎬 媒体管理器 功能强大的视频处理和管理工具,支持: • 视频上传和元数据提取 • 自动场景检测和分割 • 批量处理和进度跟踪 • 多种输出格式 使用 --help 查看具体命令帮助。 """, rich_markup_mode="rich" ) ``` ## 🎯 最佳实践 ### **1. 性能优化** - 使用类型提示提高IDE支持 - 合理使用Rich组件,避免过度渲染 - 大文件处理时显示进度条 - 异步操作使用适当的进度反馈 ### **2. 用户体验** - 提供清晰的错误信息 - 使用emoji和颜色增强可读性 - 重要操作前进行确认 - 提供详细的帮助文档 ### **3. 代码质量** - 所有函数添加类型提示 - 使用dataclass定义数据结构 - 遵循PEP 8代码风格 - 编写完整的单元测试 这套规范确保了Typer实现与您现有架构的完美集成,同时提供了现代化、类型安全的开发体验!