ForcePilot/backend/package/yuxi/external_systems/integrations/operation_registry.py

125 lines
5.3 KiB
Python
Raw Normal View History

"""IntegrationOperationRegistry厂商操作分派注册表。
实现 ``core/contracts.py::IntegrationOperationRegistry`` 端口结构化子类型
``(operation, source_type)`` 二元组分派 handler支持 discover / preview_tools /
create_tools 三类操作
handler 签名``OperationHandler````(source_type: str, system_config: dict[str, Any]) -> Awaitable[Any]``
2 参数纯 dict不依赖 ORM/DBToken/解密/DB use_cases 层预处理后注入 ``system_config``
source_type 解析职责由 use_cases 层完成端口注释 L842-L844本注册表只接收
已解析的 source_type草稿持久化职责由 use_cases 层统一处理端口注释 L846-L847
键冲突策略同一 ``(operation, source_type)`` 重复注册且 handler 不同时抛
``DomainValidationError`` framework 层注册表策略一致
"""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from typing import Any
from yuxi.external_systems.exceptions import (
DomainValidationError,
IntegrationOperationNotRegisteredError,
)
from yuxi.external_systems.integrations.schemas import GeneratedToolsDraft
# handler 类型别名与端口签名一致2 参数 + Awaitable 返回
OperationHandler = Callable[
[str, dict[str, Any]],
Awaitable[Any],
]
# 允许的 operation 集合
_ALLOWED_OPERATIONS = frozenset({"discover", "preview_tools", "create_tools"})
class IntegrationOperationRegistry:
"""厂商操作分派注册表,实现 core/contracts.py::IntegrationOperationRegistry 端口。
实现 Protocol 3 个分派方法discover / preview_tools / create_tools
并额外提供 register / get / list_operations / list_source_types / clear
供框架内部使用 AdapterRegistry 模式一致
"""
_handlers: dict[tuple[str, str], OperationHandler] = {}
@classmethod
def register(cls, operation: str, source_type: str, handler: OperationHandler) -> None:
"""注册 (operation, source_type) 对应的 handler。
键冲突抛 DomainValidationError
operation 限定为 discover / preview_tools / create_tools 三者之一
"""
if operation not in _ALLOWED_OPERATIONS:
raise DomainValidationError(f"不支持的集成操作: {operation},仅允许 {_ALLOWED_OPERATIONS}")
key = (operation, source_type)
existing = cls._handlers.get(key)
if existing is not None and existing is not handler:
raise DomainValidationError(
f"集成操作 handler 冲突: (operation={operation}, source_type={source_type}) "
f"已注册 {getattr(existing, '__name__', existing)}"
f"尝试注册 {getattr(handler, '__name__', handler)}"
)
cls._handlers[key] = handler
@classmethod
def get(cls, operation: str, source_type: str) -> OperationHandler:
"""获取 handler未注册抛 IntegrationOperationNotRegisteredError。"""
handler = cls._handlers.get((operation, source_type))
if handler is None:
raise IntegrationOperationNotRegisteredError(
f"集成操作未注册: (operation={operation}, source_type={source_type})"
)
return handler
@classmethod
async def discover(cls, source_type: str, system_config: dict[str, Any]) -> list[dict[str, Any]]:
"""分派 discover 操作(实现端口)。"""
handler = cls.get("discover", source_type)
return await handler(source_type, system_config)
@classmethod
async def preview_tools(cls, source_type: str, system_config: dict[str, Any]) -> list[dict[str, Any]]:
"""分派 preview_tools 操作(实现端口)。"""
handler = cls.get("preview_tools", source_type)
return await handler(source_type, system_config)
@classmethod
async def create_tools(cls, source_type: str, system_config: dict[str, Any]) -> GeneratedToolsDraft:
"""分派 create_tools 操作(实现端口)。
返回 GeneratedToolsDraft use_cases 层统一持久化
"""
handler = cls.get("create_tools", source_type)
result = await handler(source_type, system_config)
if isinstance(result, GeneratedToolsDraft):
return result
# 容错handler 返回 dict 时构造 GeneratedToolsDraft便于渐进迁移
if isinstance(result, dict):
return GeneratedToolsDraft.model_validate(result)
raise DomainValidationError(
f"create_tools handler 返回类型非法: 期望 GeneratedToolsDraft / dict实际 {type(result).__name__}"
)
@classmethod
def list_operations(cls, source_type: str) -> list[str]:
"""列出指定 source_type 已注册的操作(供调试与前端展示)。"""
return sorted(op for (op, st) in cls._handlers if st == source_type)
@classmethod
def list_source_types(cls) -> list[str]:
"""列出所有已注册 handler 涉及的 source_type去重"""
seen: dict[str, None] = {}
for _op, st in cls._handlers:
if st not in seen:
seen[st] = None
return list(seen.keys())
@classmethod
def clear(cls) -> None:
"""清空注册表,仅供测试使用。"""
cls._handlers.clear()