ShiErFeng/shierfeng-fastapi-backend/cli/core/execution.py
2026-07-24 09:43:10 +08:00

138 lines
4.6 KiB
Python

import asyncio
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any
from cli.context import CliContext
from cli.exit_codes import RUNTIME_ERROR
from cli.output import CommandResult, OutputRenderer
@dataclass
class CliExecutionService:
"""
CLI 执行与结果收口服务。
:param output_renderer: 输出渲染器
"""
output_renderer: OutputRenderer = field(default_factory=OutputRenderer)
def run_async(self, coroutine: Any) -> Any:
"""
执行异步协程并返回结果。
:param coroutine: 待执行协程
:return: 协程执行结果
"""
return asyncio.run(coroutine)
def complete_payload(
self,
ctx: CliContext,
payload: dict[str, Any],
*,
default_exit_code: int = 0,
) -> None:
"""
输出标准字典负载并结束命令。
:param ctx: CLI 上下文
:param payload: 标准结果字典
:param default_exit_code: 默认退出码
:return: None
"""
self.output_renderer.complete_command(self.build_result(payload, default_exit_code=default_exit_code), ctx)
def complete_result(
self,
ctx: CliContext,
result: CommandResult,
) -> None:
"""
输出命令结果对象并结束命令。
:param ctx: CLI 上下文
:param result: 命令执行结果对象
:return: None
"""
self.output_renderer.complete_command(result, ctx)
def complete_payload_with_text(
self,
ctx: CliContext,
payload: dict[str, Any],
*,
text_builder: Callable[[dict[str, Any]], Any],
default_exit_code: int = 0,
text_condition: Callable[[dict[str, Any]], bool] | None = None,
) -> None:
"""
按输出格式收口标准负载,必要时先转换为文本结果。
:param ctx: CLI 上下文
:param payload: 标准结果字典
:param text_builder: 文本结果构建函数
:param default_exit_code: 默认退出码
:param text_condition: 文本模式下是否应用构建函数的判定函数
:return: None
"""
if ctx.output != 'text':
self.complete_payload(ctx, payload, default_exit_code=default_exit_code)
return
result = self.build_result(payload, default_exit_code=default_exit_code)
should_build_text = True
if callable(text_condition) and isinstance(result.data, dict):
should_build_text = bool(text_condition(result.data))
if should_build_text and isinstance(result.data, dict):
result.data = text_builder(result.data)
self.complete_result(ctx, result)
def complete_payload_result(
self,
ctx: CliContext,
payload: dict[str, Any],
*,
text_builder: Callable[[dict[str, Any]], Any] | None = None,
default_exit_code: int = 0,
text_condition: Callable[[dict[str, Any]], bool] | None = None,
) -> None:
"""
统一收口标准负载,按输出格式决定是否渲染为文本结果。
:param ctx: CLI 上下文
:param payload: 标准结果字典
:param text_builder: 文本结果构建函数
:param default_exit_code: 默认退出码
:param text_condition: 文本模式下是否应用构建函数的判定函数
:return: None
"""
if text_builder is None:
self.complete_payload(ctx, payload, default_exit_code=default_exit_code)
return
self.complete_payload_with_text(
ctx,
payload,
text_builder=text_builder,
default_exit_code=default_exit_code,
text_condition=text_condition,
)
@staticmethod
def build_result(payload: dict[str, Any], *, default_exit_code: int = 0) -> CommandResult:
"""
将标准字典负载转换为命令结果对象。
:param payload: 标准结果字典
:param default_exit_code: 默认退出码;当 payload 未显式提供 exit_code 且
该值仍为 0 时,失败结果会统一回退到 `RUNTIME_ERROR`
:return: 命令结果对象
"""
result_payload = dict(payload)
exit_code = result_payload.pop('exit_code', None)
if exit_code is None:
is_ok = bool(result_payload.get('ok', True))
exit_code = default_exit_code if is_ok or default_exit_code != 0 else RUNTIME_ERROR
return CommandResult(data=result_payload, exit_code=exit_code)