实现了渠道插件的全生命周期管理能力,包括: 1. 插件清单的加载、解析与存储 2. 依赖校验与拓扑排序安装/卸载 3. 插件发现与安全沙箱执行环境 4. 插件注册中心与目录管理能力
106 lines
3.1 KiB
Python
106 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import threading
|
|
from typing import Any, Iterator
|
|
|
|
from yuxi.channel.protocols import MetaProtocol
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class _RegistryMeta(type):
|
|
def __len__(cls) -> int:
|
|
return len(cls._plugins)
|
|
|
|
def __iter__(cls) -> Iterator[Any]:
|
|
return iter(
|
|
sorted(
|
|
cls._plugins.values(),
|
|
key=lambda p: (getattr(p, "order", 0), getattr(p, "id", "")),
|
|
)
|
|
)
|
|
|
|
def __contains__(cls, plugin_id: str) -> bool:
|
|
return plugin_id in cls._plugins
|
|
|
|
def __repr__(cls) -> str:
|
|
if not cls._plugins:
|
|
return "<ChannelPluginRegistry empty>"
|
|
ids = sorted(cls._plugins.keys())
|
|
preview = ", ".join(ids[:5])
|
|
tail = "..." if len(ids) > 5 else ""
|
|
return f"<ChannelPluginRegistry {len(cls._plugins)} plugins: {preview}{tail}>"
|
|
|
|
|
|
class ChannelPluginRegistry(metaclass=_RegistryMeta):
|
|
_plugins: dict[str, Any] = {}
|
|
_lock: threading.Lock | None = None
|
|
|
|
@classmethod
|
|
def _ensure_lock(cls) -> threading.Lock:
|
|
if cls._lock is None:
|
|
cls._lock = threading.Lock()
|
|
return cls._lock
|
|
|
|
@classmethod
|
|
def register(cls, plugin: Any) -> Any:
|
|
if not isinstance(plugin, MetaProtocol):
|
|
raise ValueError("Channel plugin must satisfy MetaProtocol (id, name, order)")
|
|
plugin_id = getattr(plugin, "id", None)
|
|
if not plugin_id or not isinstance(plugin_id, str) or not plugin_id.strip():
|
|
raise ValueError("Channel plugin must have a non-empty string 'id' attribute")
|
|
with cls._ensure_lock():
|
|
if plugin_id in cls._plugins:
|
|
raise ValueError(f"Channel plugin '{plugin_id}' is already registered")
|
|
cls._plugins[plugin_id] = plugin
|
|
logger.info("Registered channel plugin: %s", plugin_id)
|
|
return plugin
|
|
|
|
@classmethod
|
|
def get(cls, plugin_id: str) -> Any:
|
|
return cls._plugins.get(plugin_id)
|
|
|
|
@classmethod
|
|
def list_plugins(cls) -> list[str]:
|
|
return cls.list_ids()
|
|
|
|
@classmethod
|
|
def list_all(cls) -> list[Any]:
|
|
return sorted(
|
|
cls._plugins.values(),
|
|
key=lambda p: (getattr(p, "order", 0), getattr(p, "id", "")),
|
|
)
|
|
|
|
@classmethod
|
|
def list_ids(cls) -> list[str]:
|
|
return sorted(cls._plugins.keys())
|
|
|
|
@classmethod
|
|
def all(cls) -> list[Any]:
|
|
return cls.list_all()
|
|
|
|
@classmethod
|
|
def unregister(cls, plugin_id: str) -> Any | None:
|
|
with cls._ensure_lock():
|
|
plugin = cls._plugins.pop(plugin_id, None)
|
|
if plugin is not None:
|
|
logger.info("Unregistered channel plugin: %s", plugin_id)
|
|
return plugin
|
|
|
|
@classmethod
|
|
def retire(cls, plugin_id: str) -> Any | None:
|
|
return cls.unregister(plugin_id)
|
|
|
|
@classmethod
|
|
def clear(cls) -> None:
|
|
with cls._ensure_lock():
|
|
cls._plugins.clear()
|
|
|
|
@classmethod
|
|
def has(cls, plugin_id: str) -> bool:
|
|
return plugin_id in cls._plugins
|
|
|
|
@classmethod
|
|
def is_registered(cls, plugin_id: str) -> bool:
|
|
return cls.has(plugin_id) |