ForcePilot/backend/package/yuxi/channels/contract/dtos/plugin.py
Kris 00092c818e chore: 批量代码优化与规范完善
本次提交包含多项代码优化与规范修正:
1. 文档与注释优化:修正注释术语、补充注解与FR编号
2. 代码格式调整:统一空格、换行与缩进规范
3. 类型与接口完善:补充__all__导出、修正返回类型注解
4. 错误处理增强:新增领域错误类与校验逻辑
5. 依赖与导入调整:修复路径引用、统一时区导入
6. 协议与契约更新:完善接口文档与一致性注解
2026-07-03 19:18:13 +08:00

426 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""插件 DTO。
定义插件相关的枚举、不可变值对象与 Protocol包括权限枚举、环境变量、
领域事件、事件处理 Protocol、控制 RPC 处理 Protocol、配置加载 Protocol
与配置解密 Protocol。所有枚举继承 ``str, Enum`` 以支持 JSON 序列化,
DTO 均为 ``dataclass(frozen=True)``Protocol 使用 ``@runtime_checkable``
装饰以支持 ``isinstance`` 检查。仅依赖标准库,用于插件生命周期、事件
分发、控制 RPC 与配置加载解密。
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from typing import TYPE_CHECKING, Any, Literal, Protocol, runtime_checkable
from yuxi.channels.contract.dtos.common import BatchOperationFailure, Operator
from yuxi.channels.contract.errors import ValidationError
if TYPE_CHECKING:
from yuxi.channels.contract.dtos.capability import ChannelCapabilities
from yuxi.channels.contract.plugin.manifest import PluginManifest
# 插件生命周期状态字面量类型(对齐 LifecycleState 枚举值)。
# 供 PluginSummary / PluginCatalogItem / PluginDetail 的 state 字段与
# Router 层 state 查询参数共用,避免 11 个取值在多处重复声明。
PluginStateLiteral = Literal[
"discovered",
"resolved",
"loaded",
"initialized",
"started",
"paused",
"stopped",
"unloaded",
"failed",
"not_installed",
"installed",
]
class Permission(StrEnum):
"""权限等级。
标识插件或操作所需的用户权限等级,用于权限校验与审计。继承
``str, Enum`` 以支持 JSON 序列化与字符串比较。
取值:
REQUIRED_USER: 普通需求用户。
ADMIN_USER: 管理员用户。
SUPERADMIN_USER: 超级管理员用户。
"""
REQUIRED_USER = "required_user"
ADMIN_USER = "admin_user"
SUPERADMIN_USER = "superadmin_user"
@dataclass(frozen=True)
class EnvVar:
"""环境变量声明。
描述插件所需的环境变量元信息,包括名称、描述、是否必填、默认值与
是否敏感,用于插件安装向导的环境变量校验与脱敏渲染。
字段:
name: 环境变量名称。
description: 环境变量描述。
required: 是否必填(默认 True
default: 默认值(可选)。
sensitive: 是否敏感(需脱敏,默认 False
"""
name: str
description: str
required: bool = True
default: str | None = None
sensitive: bool = False
@dataclass(frozen=True)
class DomainEvent:
"""领域事件。
描述插件生命周期或业务流程中产生的领域事件,包括事件 ID、事件类型、
负载、时间戳与链路追踪 ID用于事件总线分发与审计。
字段:
event_id: 事件 ID。
event_type: 事件类型PluginLoaded | PairingApproved | ConfigChanged | ...)。
payload: 事件负载。
timestamp: 事件时间戳。
trace_id: 链路追踪 ID可选
"""
event_id: str
event_type: str
payload: dict[str, Any]
timestamp: datetime
trace_id: str | None = None
@runtime_checkable
class EventHandler(Protocol):
"""事件处理 Protocol。
由插件实现,用于订阅并处理领域事件。使用 ``@runtime_checkable``
装饰以支持 ``isinstance`` 检查。事件处理为异步方法,避免阻塞
事件总线分发线程。
"""
async def handle(self, event: DomainEvent) -> None:
"""处理领域事件。
参数:
event: 领域事件。
"""
...
@runtime_checkable
class ConfigLoader(Protocol):
"""配置加载 Protocol。
由插件实现,用于加载插件配置并返回配置字典。使用
``@runtime_checkable`` 装饰以支持 ``isinstance`` 检查。配置加载
为同步方法,适用于启动期一次性加载场景。
"""
def load(self) -> dict[str, Any]:
"""加载插件配置。
返回:
配置字典。
"""
...
@runtime_checkable
class ConfigDecryptor(Protocol):
"""配置解密 Protocol。
由插件实现,用于解密敏感配置值。使用 ``@runtime_checkable`` 装饰
以支持 ``isinstance`` 检查。解密为同步方法,适用于配置加载后
的就地解密场景。
"""
def decrypt(self, value: str) -> str:
"""解密配置值。
参数:
value: 待解密的配置值。
返回:
解密后的明文配置值。
"""
...
@dataclass(frozen=True)
class PluginSummary:
"""插件摘要PLG-QUERY-01 列表项)。
字段:
plugin_id: 插件唯一标识manifest.manifest.id
name: 插件显示名称manifest.manifest.name
version: 插件版本manifest.manifest.version
channel_type: 绑定渠道类型manifest.manifest.channel_type.value
state: 当前生命周期状态LifecycleState.value
"""
plugin_id: str
name: str
version: str
channel_type: str
state: PluginStateLiteral
@dataclass(frozen=True)
class PluginCatalogItem:
"""插件目录项PLG-CATALOG 卡片视图数据)。
与 ``PluginSummary`` 的区别在于携带 ``capabilities`` 字段,供前端
卡片网格展示能力摘要。``capabilities`` 为 ``ChannelCapabilities``
dataclass由 Router 层 ``serialize_control_data`` 递归序列化。
字段:
plugin_id: 插件唯一标识。
name: 插件显示名称。
version: 插件版本。
channel_type: 绑定渠道类型。
state: 当前生命周期状态。
capabilities: 渠道能力集合(静态声明)。
"""
plugin_id: str
name: str
version: str
channel_type: str
state: PluginStateLiteral
capabilities: ChannelCapabilities
@dataclass(frozen=True)
class PluginDetail:
"""插件详情PLG-QUERY-02 响应)。
字段:
plugin_id: 插件唯一标识。
name: 插件显示名称。
version: 插件版本。
channel_type: 绑定渠道类型。
state: 当前生命周期状态。
manifest: 完整 PluginManifest含内嵌 ChannelManifest 与
adapters / stages / event_subscriptions / config_sources
ChannelManifest 含 capabilities / provides / depends /
config_schema / failure_policy / resource_quota 等字段。
"""
plugin_id: str
name: str
version: str
channel_type: str
state: PluginStateLiteral
manifest: PluginManifest
@dataclass(frozen=True)
class InstallPluginCmd:
"""安装插件命令PLG-INSTALL
由 ``PluginManagementPort.installPlugin`` 引用,从来源安装插件,需记录
操作人以满足审计要求(超级管理员)。
字段:
operator: 操作人(审计用)。
source_type: 来源类型(``path`` / ``url`` / ``registry``)。
source: 来源路径或 URL。
version: 指定版本(可选)。
force: 是否强制覆盖已存在插件(默认 False
"""
operator: Operator
source_type: Literal["path", "url", "registry"]
source: str
version: str | None = None
force: bool = False
def __post_init__(self) -> None:
"""校验必填字段非空与 source_type 取值PLG-INSTALL
``source`` 必须非空,``source_type`` 必须为 ``path`` / ``url`` /
``registry`` 之一,在构造时即抛出 ``ValidationError``adapter 不再做
该校验INV-8
"""
if not self.source:
raise ValidationError("source", "must not be empty")
if self.source_type not in ("path", "url", "registry"):
raise ValidationError(
"source_type",
"must be one of: path, url, registry",
)
@dataclass(frozen=True)
class InstallPluginResult:
"""安装插件结果PLG-INSTALL
字段:
plugin_id: 插件 ID。
version: 安装的插件版本。
installed_at: 安装时间戳。
requires_load: 是否需要手动加载(默认 True
"""
plugin_id: str
version: str
installed_at: datetime
requires_load: bool = True
@dataclass(frozen=True)
class UninstallPluginResult:
"""卸载插件结果PLG-UNINSTALL-FILE
字段:
plugin_id: 插件 ID。
uninstalled_at: 卸载时间戳。
files_removed: 已删除的文件列表。
"""
plugin_id: str
uninstalled_at: datetime
files_removed: tuple[str, ...]
@dataclass(frozen=True)
class PluginConfigResult:
"""插件配置查询结果PLG-CONFIG
字段:
plugin_id: 插件 ID。
config: 当前配置值。
schema: 配置 schema从 PluginManifest 获取)。
requires_restart: 是否需要重启生效。
updated_at: 配置最后更新时间(可选)。
"""
plugin_id: str
config: dict[str, Any]
schema: dict[str, Any]
requires_restart: bool
updated_at: datetime | None = None
@dataclass(frozen=True)
class UpdatePluginConfigCmd:
"""更新插件配置命令PLG-CONFIG-PUT
由 ``PluginManagementPort.updatePluginConfig`` 引用,按 ``apply_mode``
判断是否需要重启,需记录操作人以满足审计要求。
字段:
plugin_id: 插件 ID。
operator: 操作人(审计用)。
config: 配置值。
apply_mode: 应用模式(``hot`` / ``restart_required``)。
"""
plugin_id: str
operator: Operator
config: dict[str, Any]
apply_mode: Literal["hot", "restart_required"]
def __post_init__(self) -> None:
"""校验必填字段非空与 apply_mode 取值PLG-CONFIG-PUT
``plugin_id`` 必须非空,``config`` 必须非空字典,``apply_mode``
必须为 ``hot`` / ``restart_required`` 之一,在构造时即抛出
``ValidationError``adapter 不再做该校验INV-8
"""
if not self.plugin_id:
raise ValidationError("plugin_id", "must not be empty")
if not self.config:
raise ValidationError("config", "must not be empty")
if self.apply_mode not in ("hot", "restart_required"):
raise ValidationError(
"apply_mode",
"must be one of: hot, restart_required",
)
@dataclass(frozen=True)
class BatchPluginLifecycleSuccessItem:
"""批量插件生命周期成功条目PLG-BATCH-LIFE
字段:
plugin_id: 插件 ID。
new_state: 新生命周期状态。
"""
plugin_id: str
new_state: str
@dataclass(frozen=True)
class BatchPluginLifecycleCmd:
"""批量插件生命周期命令PLG-BATCH-LIFE
逐条独立事务执行批量启动/停止(模式 D``plugin_ids`` 与 ``filter``
二选一:不可同时为空、不可同时提供(由 ``__post_init__`` 校验)。
字段:
operator: 操作人(审计用)。
plugin_ids: 显式插件 ID 元组(默认空元组)。
filter: 筛选条件(含 channel_type / state可选
"""
operator: Operator
plugin_ids: tuple[str, ...] = ()
filter: dict[str, Any] | None = None
def __post_init__(self) -> None:
"""校验 plugin_ids 与 filter 互斥性与非空PLG-BATCH-LIFE
``plugin_ids`` 与 ``filter`` 二选一:不可同时为空、不可同时提供,
``plugin_ids`` 各项不得为空字符串。在构造时即抛出 ``ValidationError``
使非法输入在 Router 层即被拒绝INV-8
"""
has_ids = bool(self.plugin_ids)
has_filter = self.filter is not None
if not has_ids and not has_filter:
raise ValidationError(
"plugin_ids",
"either plugin_ids or filter must be provided",
)
if has_ids and has_filter:
raise ValidationError(
"plugin_ids",
"plugin_ids and filter are mutually exclusive",
)
if has_ids and not all(pid for pid in self.plugin_ids):
raise ValidationError(
"plugin_ids",
"plugin_ids must not contain empty strings",
)
@dataclass(frozen=True)
class BatchPluginLifecycleResult:
"""批量插件生命周期结果PLG-BATCH-LIFE
描述逐条独立事务执行批量启动/停止的执行结果,``failed`` 使用通用
``BatchOperationFailure````id`` 字段承载 plugin_id
字段:
total: 待操作插件总数。
succeeded: 成功条目元组。
failed: 失败条目元组。
"""
total: int
succeeded: tuple[BatchPluginLifecycleSuccessItem, ...]
failed: tuple[BatchOperationFailure, ...]