base_agent/uas/comm/base.py

122 lines
3.9 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.

# -*- coding: utf-8 -*-
"""
通信适配器抽象基类
所有中间件适配器必须继承此类并实现全部抽象方法
遵循 Factory + Strategy 模式
"""
import asyncio
import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Callable, Awaitable, List, Optional
from uas.comm.message import Message
logger = logging.getLogger(__name__)
# 消息处理器类型:同步 or 异步均可
MessageHandler = Callable[[Message], Optional[Awaitable[None]]]
@dataclass
class ConnectionConfig:
"""通用连接配置(由 URL 解析填充)"""
scheme: str # redis / kafka / dds
host: str = "localhost"
port: int = 0
username: str = ""
password: str = ""
database: str = "" # redis db index / kafka group id / dds domain
options: dict = None # URL query 参数
def __post_init__(self):
if self.options is None:
self.options = {}
class CommBaseAdapter(ABC):
"""
所有通信适配器的抽象基类
子类只需实现以下 5 个核心方法
"""
def __init__(self, config: ConnectionConfig):
self.config = config
self.logger = logging.getLogger(self.__class__.__name__)
self._running = False
self._handlers: dict[str, List[MessageHandler]] = {}
# ── 生命周期 ──────────────────────────────────────────────────
@abstractmethod
async def connect(self) -> None:
"""建立连接"""
...
@abstractmethod
async def disconnect(self) -> None:
"""断开连接"""
...
@property
def is_connected(self) -> bool:
return self._running
# ── 核心功能 ──────────────────────────────────────────────────
@abstractmethod
async def publish(self, message: Message) -> bool:
"""
发布消息到指定 topic
:return: True 表示发送成功
"""
...
@abstractmethod
async def subscribe(self, topic: str, handler: MessageHandler) -> None:
"""
订阅 topic注册消息处理回调
支持通配符(由各适配器自行实现)
"""
...
@abstractmethod
async def unsubscribe(self, topic: str) -> None:
"""取消订阅"""
...
# ── 可选扩展 ──────────────────────────────────────────────────
async def request(self, message: Message, timeout: float = 5.0) -> Optional[Message]:
"""
Request/Reply 模式(默认未实现,子类可覆盖)
"""
raise NotImplementedError(f"{self.__class__.__name__} 不支持 Request/Reply 模式")
async def health_check(self) -> bool:
"""健康检查,默认返回连接状态"""
return self._running
# ── 内部工具 ──────────────────────────────────────────────────
def _register_handler(self, topic: str, handler: MessageHandler):
self._handlers.setdefault(topic, []).append(handler)
async def _dispatch(self, message: Message):
"""将消息分发给所有匹配的 handler"""
handlers = self._handlers.get(message.topic, [])
for handler in handlers:
try:
result = handler(message)
if asyncio.iscoroutine(result):
await result
except Exception as e:
self.logger.error(f"Handler 执行异常 topic={message.topic}: {e}", exc_info=True)
async def __aenter__(self):
await self.connect()
return self
async def __aexit__(self, *args):
await self.disconnect()