json rpc commander 封装

This commit is contained in:
root
2025-07-11 21:27:17 +08:00
parent 2a16067367
commit 7b50c6e28e
37 changed files with 3518 additions and 2596 deletions

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env python3
"""
Commander模块
"""
from .types import CommandConfig
from .parser import ArgumentParser
from .base import JSONRPCCommander
from .simple import SimpleJSONRPCCommander, create_simple_commander
__all__ = [
"CommandConfig",
"ArgumentParser",
"JSONRPCCommander",
"SimpleJSONRPCCommander",
"create_simple_commander"
]

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""
JSON-RPC Commander基类
"""
import sys
import json
from abc import ABC, abstractmethod
from typing import Dict, Any, List
from .types import CommandConfig
from .parser import ArgumentParser
from ..jsonrpc import create_response_handler
from ..logger import logger
class JSONRPCCommander(ABC):
"""JSON-RPC Commander 基类"""
def __init__(self, service_name: str):
"""
初始化Commander
Args:
service_name: 服务名称
"""
self.service_name = service_name
self.rpc_handler = None
self.rpc_progress_reporter = None
self.commands: Dict[str, CommandConfig] = {}
self.parser = ArgumentParser(self.commands)
self._setup_rpc_handler()
self._register_commands()
# 重新创建parser以包含注册的命令
self.parser = ArgumentParser(self.commands)
def _setup_rpc_handler(self) -> None:
"""设置RPC处理器"""
try:
self.rpc_handler = create_response_handler()
logger.debug(f"JSON-RPC handler initialized for {self.service_name}")
except Exception as e:
logger.warning(f"Failed to initialize JSON-RPC handler: {e}")
self.rpc_handler = None
@abstractmethod
def _register_commands(self) -> None:
"""注册命令 - 子类必须实现"""
pass
def register_command(self,
name: str,
description: str,
required_args: List[str] = None,
optional_args: Dict[str, Dict[str, Any]] = None) -> None:
"""
注册命令
Args:
name: 命令名称
description: 命令描述
required_args: 必需参数列表
optional_args: 可选参数配置
"""
self.commands[name] = CommandConfig(
name=name,
description=description,
required_args=required_args or [],
optional_args=optional_args or {}
)
def parse_arguments(self, args: List[str]) -> tuple:
"""
解析命令行参数
Args:
args: 命令行参数列表
Returns:
(command, parsed_args) 元组
"""
try:
return self.parser.parse_arguments(args)
except ValueError as e:
self._send_error("INVALID_ARGS", str(e))
sys.exit(1)
def _show_usage(self) -> None:
"""显示使用说明"""
usage_info = {
"service": self.service_name,
"usage": f"python -m {self.service_name} <command> [args...]",
"commands": {}
}
for cmd_name, cmd_config in self.commands.items():
cmd_info = {
"description": cmd_config.description,
"required_args": cmd_config.required_args,
"optional_args": {}
}
for arg_name, arg_config in cmd_config.optional_args.items():
cmd_info["optional_args"][arg_name] = {
"type": arg_config.get('type', str).__name__,
"default": arg_config.get('default'),
"choices": arg_config.get('choices'),
"description": arg_config.get('description', '')
}
usage_info["commands"][cmd_name] = cmd_info
self._send_response(usage_info)
def _send_response(self, result: Any) -> None:
"""发送成功响应"""
if self.rpc_handler:
self.rpc_handler.success(result)
else:
print(json.dumps(result, indent=2, ensure_ascii=False))
def _send_error(self, error_code: str, message: str) -> None:
"""发送错误响应"""
if self.rpc_handler:
self.rpc_handler.error(error_code, message)
else:
error_response = {
"error": {
"code": error_code,
"message": message
}
}
print(json.dumps(error_response, indent=2, ensure_ascii=False))
@abstractmethod
def execute_command(self, command: str, args: Dict[str, Any]) -> Any:
"""
执行命令 - 子类必须实现
Args:
command: 命令名称
args: 解析后的参数
Returns:
命令执行结果
"""
pass
def run(self, argv: List[str] = None) -> None:
"""
运行Commander
Args:
argv: 命令行参数默认使用sys.argv[1:]
"""
if argv is None:
argv = sys.argv[1:]
if len(argv) == 0:
self._show_usage()
return
try:
# 解析参数
command, args = self.parse_arguments(argv)
# 执行命令
result = self.execute_command(command, args)
# 发送响应
self._send_response(result)
except KeyboardInterrupt:
self._send_error("INTERRUPTED", "Command interrupted by user")
sys.exit(1)
except Exception as e:
logger.error(f"Command execution failed: {e}")
self._send_error("INTERNAL_ERROR", str(e))
sys.exit(1)

View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
参数解析器
"""
import sys
from typing import List, Dict, Any, Tuple
from .types import CommandConfig
from ..logger import logger
class ArgumentParser:
"""命令行参数解析器"""
def __init__(self, commands: Dict[str, CommandConfig]):
self.commands = commands
def parse_arguments(self, args: List[str]) -> Tuple[str, Dict[str, Any]]:
"""
解析命令行参数
Args:
args: 命令行参数列表
Returns:
(command, parsed_args) 元组
"""
if len(args) < 1:
raise ValueError("No command provided")
command = args[0]
if command not in self.commands:
raise ValueError(f"Unknown command: {command}")
command_config = self.commands[command]
# 解析参数
parsed_args = {}
remaining_args = args[1:]
# 处理必需参数
if len(remaining_args) < len(command_config.required_args):
missing_args = command_config.required_args[len(remaining_args):]
raise ValueError(f"Missing required arguments: {missing_args}")
# 设置必需参数
for i, arg_name in enumerate(command_config.required_args):
parsed_args[arg_name] = remaining_args[i]
# 处理可选参数
optional_start = len(command_config.required_args)
i = optional_start
while i < len(remaining_args):
arg = remaining_args[i]
if arg.startswith('--'):
arg_name = arg[2:]
if arg_name in command_config.optional_args:
arg_config = command_config.optional_args[arg_name]
# 检查是否需要值
if arg_config.get('type') == bool:
parsed_args[arg_name] = True
i += 1
elif i + 1 < len(remaining_args) and not remaining_args[i + 1].startswith('--'):
value_str = remaining_args[i + 1]
# 类型转换
try:
arg_type = arg_config.get('type', str)
if arg_type == bool:
value = value_str.lower() in ('true', '1', 'yes', 'on')
else:
value = arg_type(value_str)
# 检查选择范围
choices = arg_config.get('choices')
if choices and value not in choices:
raise ValueError(f"Invalid value for {arg_name}: {value}. Choices: {choices}")
parsed_args[arg_name] = value
i += 2
except (ValueError, TypeError) as e:
raise ValueError(f"Invalid value for {arg_name}: {value_str}. {e}")
else:
raise ValueError(f"Missing value for argument: {arg_name}")
else:
logger.warning(f"Unknown optional argument: {arg_name}")
i += 1
else:
i += 1
# 设置默认值
for arg_name, arg_config in command_config.optional_args.items():
if arg_name not in parsed_args:
default_value = arg_config.get('default')
if default_value is not None:
parsed_args[arg_name] = default_value
return command, parsed_args

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""
简化的JSON-RPC Commander
"""
from typing import Dict, Any, List, Callable
from .base import JSONRPCCommander
class SimpleJSONRPCCommander(JSONRPCCommander):
"""简化的JSON-RPC Commander用于快速创建命令行工具"""
def __init__(self, service_name: str):
self.command_handlers: Dict[str, Callable] = {}
super().__init__(service_name)
def _register_commands(self) -> None:
"""默认不注册任何命令"""
pass
def add_command(self,
name: str,
handler: Callable,
description: str,
required_args: List[str] = None,
optional_args: Dict[str, Dict[str, Any]] = None) -> None:
"""
添加命令处理器
Args:
name: 命令名称
handler: 命令处理函数
description: 命令描述
required_args: 必需参数列表
optional_args: 可选参数配置
"""
self.register_command(name, description, required_args, optional_args)
self.command_handlers[name] = handler
# 重新创建parser以包含新命令
from .parser import ArgumentParser
self.parser = ArgumentParser(self.commands)
def execute_command(self, command: str, args: Dict[str, Any]) -> Any:
"""执行命令"""
if command not in self.command_handlers:
raise ValueError(f"No handler for command: {command}")
handler = self.command_handlers[command]
return handler(**args)
# 便捷函数
def create_simple_commander(service_name: str) -> SimpleJSONRPCCommander:
"""创建简单的JSON-RPC Commander"""
return SimpleJSONRPCCommander(service_name)

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python3
"""
Commander相关的数据类型定义
"""
from dataclasses import dataclass
from typing import Dict, Any, List
@dataclass
class CommandConfig:
"""命令配置"""
name: str
description: str
required_args: List[str]
optional_args: Dict[str, Dict[str, Any]]

View File

@@ -2,7 +2,6 @@
Helper utilities for MixVideo V2
"""
import os
from pathlib import Path
from typing import Dict, Any, Union
import ffmpeg

View File

@@ -191,42 +191,3 @@ def parse_request(request_str: str) -> Dict[str, Any]:
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON: {e}")
def example_video_generation():
"""Example of how to use JSON-RPC for video generation"""
rpc = create_response_handler("video_gen_001")
progress = create_progress_reporter()
try:
# Report progress steps
progress.step("upload", "[1/4] 正在上传图片到云存储...")
# ... upload logic ...
progress.step("submit", "[2/4] 正在提交视频生成任务...")
# ... submit logic ...
progress.step("wait", "[3/4] 正在等待视频生成完成...")
# ... wait logic ...
progress.step("download", "[4/4] 正在下载视频到本地...")
# ... download logic ...
progress.complete("[完成] 视频生成并下载成功")
# Send final result
result = {
"status": True,
"video_path": "/path/to/video.mp4",
"video_url": "https://example.com/video.mp4",
"message": "视频生成并下载成功"
}
rpc.success(result)
except Exception as e:
progress.error(f"生成失败: {str(e)}")
rpc.error(JSONRPCError.GENERATION_FAILED, "Video generation failed", str(e))
if __name__ == "__main__":
# Test the JSON-RPC module
example_video_generation()

View File

@@ -7,7 +7,6 @@ from pathlib import Path
from loguru import logger
import sys
import os
from ..config import settings

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""
进度管理模块
"""
from .types import ProgressInfo, TaskResult
from .task import ProgressiveTask
from .reporter import ProgressReporter
from .generator import ProgressGenerator
from .decorators import with_progress
from .commander import ProgressJSONRPCCommander, create_progress_commander
__all__ = [
"ProgressInfo",
"TaskResult",
"ProgressiveTask",
"ProgressReporter",
"ProgressGenerator",
"with_progress",
"ProgressJSONRPCCommander",
"create_progress_commander"
]

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""
带进度的JSON-RPC Commander
"""
import time
from abc import abstractmethod
from typing import Dict, Any, Callable
from contextlib import contextmanager
from .types import ProgressInfo, TaskResult
from .task import ProgressiveTask
from .reporter import ProgressReporter
from ..commander import JSONRPCCommander
from ..logger import logger
class ProgressJSONRPCCommander(JSONRPCCommander):
"""带进度条的JSON-RPC Commander基类"""
def __init__(self, service_name: str):
super().__init__(service_name)
self.progress_reporter = ProgressReporter(service_name)
@contextmanager
def create_task(self, task_name: str, total_steps: int = 100):
"""创建带进度的任务上下文"""
task = ProgressiveTask(task_name, total_steps)
task.set_progress_callback(self.progress_reporter.report_progress)
try:
task.start()
yield task
task.finish()
except Exception as e:
task._report_progress(f"任务失败: {str(e)}")
raise
def execute_progressive_command(self, command: str, args: Dict[str, Any]) -> TaskResult:
"""
执行带进度的命令
Args:
command: 命令名称
args: 命令参数
Returns:
任务结果
"""
start_time = time.time()
try:
# 调用子类实现的进度命令执行
result = self._execute_with_progress(command, args)
total_time = time.time() - start_time
return TaskResult(
success=True,
result=result,
total_time=total_time
)
except Exception as e:
total_time = time.time() - start_time
logger.error(f"Progressive command failed: {e}")
return TaskResult(
success=False,
error=str(e),
total_time=total_time
)
@abstractmethod
def _execute_with_progress(self, command: str, args: Dict[str, Any]) -> Any:
"""
执行带进度的命令 - 子类必须实现
Args:
command: 命令名称
args: 命令参数
Returns:
命令执行结果
"""
pass
def execute_command(self, command: str, args: Dict[str, Any]) -> Any:
"""
执行命令(重写基类方法以支持进度)
Args:
command: 命令名称
args: 命令参数
Returns:
命令执行结果
"""
# 先进行参数类型转换
converted_args = self._convert_args_types(command, args)
# 检查是否是需要进度报告的命令
if self._is_progressive_command(command):
task_result = self.execute_progressive_command(command, converted_args)
if task_result.success:
return task_result.result
else:
raise Exception(task_result.error)
else:
# 普通命令,调用子类实现
return self._execute_simple_command(command, converted_args)
def _convert_args_types(self, command: str, args: Dict[str, Any]) -> Dict[str, Any]:
"""
转换参数类型
Args:
command: 命令名称
args: 原始参数
Returns:
转换后的参数
"""
if command not in self.commands:
return args
command_config = self.commands[command]
converted_args = args.copy()
# 转换可选参数的类型
for arg_name, arg_config in command_config.optional_args.items():
if arg_name in converted_args:
arg_type = arg_config.get('type', str)
try:
if arg_type == bool:
# 布尔类型特殊处理
value = converted_args[arg_name]
if isinstance(value, str):
converted_args[arg_name] = value.lower() in ('true', '1', 'yes', 'on')
elif arg_type != str and isinstance(converted_args[arg_name], str):
# 其他类型从字符串转换
converted_args[arg_name] = arg_type(converted_args[arg_name])
except (ValueError, TypeError) as e:
logger.warning(f"Failed to convert argument {arg_name}: {e}")
return converted_args
def _is_progressive_command(self, command: str) -> bool:
"""
判断是否是需要进度报告的命令
子类可以重写此方法来指定哪些命令需要进度报告
Args:
command: 命令名称
Returns:
是否需要进度报告
"""
# 默认所有命令都需要进度报告
return True
def _execute_simple_command(self, command: str, args: Dict[str, Any]) -> Any:
"""
执行简单命令(不需要进度报告)
子类可以重写此方法来处理不需要进度的命令
Args:
command: 命令名称
args: 命令参数
Returns:
命令执行结果
"""
# 默认调用带进度的执行方法
return self._execute_with_progress(command, args)
# 便捷函数
def create_progress_commander(service_name: str):
"""创建带进度的JSON-RPC Commander"""
class SimpleProgressCommander(ProgressJSONRPCCommander):
def __init__(self):
super().__init__(service_name)
self.command_handlers: Dict[str, Callable] = {}
self._progressive_commands = set()
def _register_commands(self):
pass
def add_command(self, name: str, handler: Callable, description: str,
required_args: list = None, optional_args: dict = None,
progressive: bool = True):
"""添加命令"""
self.register_command(name, description, required_args, optional_args)
self.command_handlers[name] = handler
# 标记是否需要进度报告
if progressive:
self._progressive_commands.add(name)
def _is_progressive_command(self, command: str) -> bool:
return command in self._progressive_commands
def _execute_with_progress(self, command: str, args: Dict[str, Any]) -> Any:
if command in self.command_handlers:
return self.command_handlers[command](**args)
else:
raise ValueError(f"No handler for command: {command}")
def _execute_simple_command(self, command: str, args: Dict[str, Any]) -> Any:
if command in self.command_handlers:
return self.command_handlers[command](**args)
else:
raise ValueError(f"No handler for command: {command}")
return SimpleProgressCommander()

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""
进度装饰器
"""
def with_progress(total_steps: int = 100, task_name: str = None):
"""
为函数添加进度报告的装饰器
Args:
total_steps: 总步数
task_name: 任务名称
"""
def decorator(func):
def wrapper(self, *args, **kwargs):
name = task_name or func.__name__
if hasattr(self, 'create_task'):
with self.create_task(name, total_steps) as task:
# 将task对象传递给函数
return func(self, task, *args, **kwargs)
else:
# 如果不是ProgressJSONRPCCommander直接执行
return func(self, *args, **kwargs)
return wrapper
return decorator

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""
进度生成器工具
"""
from .task import ProgressiveTask
class ProgressGenerator:
"""进度生成器工具类"""
@staticmethod
def for_iterable(iterable, task: ProgressiveTask, description: str = "处理中"):
"""为可迭代对象添加进度报告"""
total = len(iterable) if hasattr(iterable, '__len__') else 100
task.total_steps = total
for i, item in enumerate(iterable):
task.update(i, f"{description} {i+1}/{total}")
yield item
task.finish(f"{description}完成")
@staticmethod
def for_range(start: int, end: int, task: ProgressiveTask, description: str = "处理中"):
"""为范围添加进度报告"""
total = end - start
task.total_steps = total
for i in range(start, end):
task.update(i - start, f"{description} {i+1}/{total}")
yield i
task.finish(f"{description}完成")
@staticmethod
def for_steps(steps: int, task: ProgressiveTask, description: str = "处理中"):
"""为步数添加进度报告"""
task.total_steps = steps
for i in range(steps):
task.update(i, f"{description} {i+1}/{steps}")
yield i
task.finish(f"{description}完成")

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
进度报告器
"""
from .types import ProgressInfo
from ..jsonrpc import create_progress_reporter
from ..logger import logger
class ProgressReporter:
"""进度报告器"""
def __init__(self, service_name: str):
self.service_name = service_name
self.rpc_progress_reporter = None
self._setup_progress_reporter()
def _setup_progress_reporter(self) -> None:
"""设置进度报告器"""
try:
self.rpc_progress_reporter = create_progress_reporter()
logger.debug(f"Progress reporter initialized for {self.service_name}")
except Exception as e:
logger.warning(f"Failed to initialize progress reporter: {e}")
self.rpc_progress_reporter = None
def report_progress(self, progress: ProgressInfo) -> None:
"""报告进度"""
if self.rpc_progress_reporter:
# JSON-RPC进度报告
self.rpc_progress_reporter.report(
step=self.service_name,
progress=progress.percentage / 100.0, # 转换为0-1范围
message=progress.message,
details={
"current": progress.current,
"total": progress.total,
"elapsed_time": progress.elapsed_time,
"estimated_remaining": progress.estimated_remaining
}
)
else:
# 简单的控制台输出
print(f"Progress: {progress.percentage:.1f}% - {progress.message}")
def report_step(self, step_name: str, message: str) -> None:
"""报告步骤"""
if self.rpc_progress_reporter:
self.rpc_progress_reporter.step(step_name, message)
else:
print(f"Step: {step_name} - {message}")
def report_complete(self, message: str = "完成") -> None:
"""报告完成"""
if self.rpc_progress_reporter:
self.rpc_progress_reporter.complete(message)
else:
print(f"Complete: {message}")

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
渐进式任务管理
"""
import time
from typing import Optional, Callable
from .types import ProgressInfo
from ..logger import logger
class ProgressiveTask:
"""渐进式任务包装器"""
def __init__(self, task_name: str, total_steps: int = 100):
self.task_name = task_name
self.total_steps = total_steps
self.current_step = 0
self.start_time = None
self.progress_callback: Optional[Callable[[ProgressInfo], None]] = None
def set_progress_callback(self, callback: Callable[[ProgressInfo], None]):
"""设置进度回调"""
self.progress_callback = callback
def start(self):
"""开始任务"""
self.start_time = time.time()
self.current_step = 0
self._report_progress("任务开始")
logger.debug(f"Task started: {self.task_name}")
def update(self, step: int = None, message: str = ""):
"""更新进度"""
if step is not None:
self.current_step = step
else:
self.current_step += 1
self._report_progress(message)
def finish(self, message: str = "任务完成"):
"""完成任务"""
self.current_step = self.total_steps
self._report_progress(message)
logger.debug(f"Task finished: {self.task_name}")
def _report_progress(self, message: str):
"""报告进度"""
if self.progress_callback and self.start_time:
elapsed = time.time() - self.start_time
# 估算剩余时间
if self.current_step > 0:
avg_time_per_step = elapsed / self.current_step
remaining_steps = self.total_steps - self.current_step
estimated_remaining = avg_time_per_step * remaining_steps
else:
estimated_remaining = 0.0
progress = ProgressInfo(
current=self.current_step,
total=self.total_steps,
message=message,
elapsed_time=elapsed,
estimated_remaining=estimated_remaining
)
self.progress_callback(progress)

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""
进度相关的数据类型定义
"""
from dataclasses import dataclass
from typing import Any, Optional
@dataclass
class ProgressInfo:
"""进度信息"""
current: int
total: int
message: str = ""
percentage: float = 0.0
elapsed_time: float = 0.0
estimated_remaining: float = 0.0
def __post_init__(self):
"""计算百分比"""
if self.total > 0:
self.percentage = (self.current / self.total) * 100
@dataclass
class TaskResult:
"""任务结果"""
success: bool
result: Any = None
error: str = None
total_time: float = 0.0
final_progress: Optional[ProgressInfo] = None

View File

@@ -5,9 +5,6 @@ File validation utilities for MixVideo V2
from pathlib import Path
from typing import Union
import sys
import os
from ..config import settings