base_agent/uas/comm/factory.py

125 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
"""
通信中间件工厂
─────────────────────────────────────────────────────────────────
支持通过 URL 自动路由到对应适配器:
redis://[:password@]host[:port][/db][?options]
kafka://[user:password@]host[:port][/group_id][?options]
dds://[domain_id@]host[:port][/topic_prefix][?options]
扩展新协议只需:
1. 实现 CommBaseAdapter 子类
2. 调用 CommAdapterFactory.register("scheme", YourAdapter)
─────────────────────────────────────────────────────────────────
"""
import logging
from typing import Type, Dict
from urllib.parse import urlparse, parse_qs, unquote
from uas.comm.base import CommBaseAdapter, ConnectionConfig
logger = logging.getLogger(__name__)
class CommAdapterFactory:
"""
适配器工厂(注册表模式)
线程安全,支持运行时动态注册新协议
"""
_registry: Dict[str, Type[CommBaseAdapter]] = {}
# ── 注册 ──────────────────────────────────────────────────────
@classmethod
def register(cls, scheme: str, adapter_cls: Type[CommBaseAdapter]) -> None:
"""
注册新的协议适配器
:param scheme: 协议名,如 "redis""kafka""mqtt"
:param adapter_cls: CommBaseAdapter 子类
"""
scheme = scheme.lower()
if not issubclass(adapter_cls, CommBaseAdapter):
raise TypeError(f"{adapter_cls} 必须继承自 CommBaseAdapter")
cls._registry[scheme] = adapter_cls
logger.info(f"✅ 已注册适配器: {scheme} -> {adapter_cls.__name__}")
@classmethod
def unregister(cls, scheme: str) -> None:
cls._registry.pop(scheme.lower(), None)
@classmethod
def available_schemes(cls) -> list:
return list(cls._registry.keys())
# ── 创建 ──────────────────────────────────────────────────────
@classmethod
def create(cls, url: str) -> CommBaseAdapter:
"""
根据 URL 自动创建对应适配器实例
示例:
redis://:mypassword@127.0.0.1:6379/0?socket_timeout=5
kafka://broker1:9092/my-group?auto_offset_reset=earliest
dds://0@localhost:7400/rt?reliability=RELIABLE
"""
config = cls._parse_url(url)
scheme = config.scheme
if scheme not in cls._registry:
raise ValueError(
f"未知协议: '{scheme}',已注册: {cls.available_schemes()}"
)
adapter_cls = cls._registry[scheme]
logger.info(f"🔧 创建适配器: {adapter_cls.__name__} url={url!r}")
return adapter_cls(config)
# ── URL 解析 ──────────────────────────────────────────────────
@classmethod
def _parse_url(cls, url: str) -> ConnectionConfig:
"""
解析通用 URL 为 ConnectionConfig
兼容以下格式:
redis://[:password@]host[:port][/db]
kafka://[user:pass@]host[:port][/group]
dds://[domain@]host[:port][/prefix]
"""
parsed = urlparse(url)
scheme = parsed.scheme.lower()
# 默认端口映射
default_ports = {
"redis": 6379,
"kafka": 9092,
"dds": 7400,
"mqtt": 1883,
"nats": 4222,
"amqp": 5672,
}
host = parsed.hostname or "localhost"
port = parsed.port or default_ports.get(scheme, 0)
username = unquote(parsed.username or "")
password = unquote(parsed.password or "")
database = parsed.path.lstrip("/") if parsed.path else ""
# 解析 query 参数(所有值展开为单值)
raw_opts = parse_qs(parsed.query, keep_blank_values=True)
options = {k: v[0] if len(v) == 1 else v for k, v in raw_opts.items()}
config = ConnectionConfig(
scheme = scheme,
host = host,
port = port,
username = username,
password = password,
database = database,
options = options,
)
logger.debug(f"URL 解析结果: {config}")
return config