This commit is contained in:
2025-12-31 17:47:39 +08:00
38 changed files with 4435 additions and 1343 deletions

213
utils/plugin_inject.py Normal file
View File

@@ -0,0 +1,213 @@
"""
插件依赖注入模块
提供插件间依赖注入功能:
- @inject 装饰器自动注入依赖
- 延迟注入lazy injection避免循环依赖
- 类型安全的依赖获取
使用示例:
from utils.plugin_inject import inject, require_plugin
class MyPlugin(PluginBase):
# 方式1: 使用装饰器注入
@inject("AIChat")
def get_aichat(self) -> "AIChat":
pass # 自动注入,无需实现
# 方式2: 使用 require_plugin
async def some_method(self):
aichat = require_plugin("AIChat")
await aichat.do_something()
# 方式3: 使用基类的 get_plugin
async def another_method(self):
aichat = self.get_plugin("AIChat")
if aichat:
await aichat.do_something()
"""
from functools import wraps
from typing import Any, Callable, Optional, Type, TypeVar, TYPE_CHECKING
from loguru import logger
if TYPE_CHECKING:
from utils.plugin_base import PluginBase
T = TypeVar('T')
class PluginNotAvailableError(Exception):
"""插件不可用错误"""
pass
def _get_plugin_manager():
"""延迟获取 PluginManager 避免循环导入"""
from utils.plugin_manager import PluginManager
return PluginManager()
def require_plugin(plugin_name: str) -> "PluginBase":
"""
获取必需的插件(不存在则抛出异常)
Args:
plugin_name: 插件类名
Returns:
插件实例
Raises:
PluginNotAvailableError: 插件不存在或未启用
"""
pm = _get_plugin_manager()
plugin = pm.plugins.get(plugin_name)
if plugin is None:
raise PluginNotAvailableError(f"插件 {plugin_name} 不可用")
return plugin
def get_plugin(plugin_name: str) -> Optional["PluginBase"]:
"""
获取插件(不存在返回 None
Args:
plugin_name: 插件类名
Returns:
插件实例或 None
"""
pm = _get_plugin_manager()
return pm.plugins.get(plugin_name)
def inject(plugin_name: str) -> Callable:
"""
插件注入装饰器
将方法转换为属性 getter自动返回指定插件实例。
Args:
plugin_name: 要注入的插件类名
Usage:
class MyPlugin(PluginBase):
@inject("AIChat")
def aichat(self) -> "AIChat":
pass # 无需实现
async def handle(self, bot, message):
# 直接使用
await self.aichat.process(message)
"""
def decorator(method: Callable) -> property:
@wraps(method)
def getter(self) -> Optional["PluginBase"]:
# 优先使用插件自身的 _plugin_manager
if hasattr(self, '_plugin_manager') and self._plugin_manager:
return self._plugin_manager.plugins.get(plugin_name)
# 回退到全局 PluginManager
return get_plugin(plugin_name)
return property(getter)
return decorator
def inject_required(plugin_name: str) -> Callable:
"""
必需插件注入装饰器
与 inject 类似,但如果插件不存在会抛出异常。
Args:
plugin_name: 要注入的插件类名
Raises:
PluginNotAvailableError: 插件不存在
"""
def decorator(method: Callable) -> property:
@wraps(method)
def getter(self) -> "PluginBase":
plugin = None
if hasattr(self, '_plugin_manager') and self._plugin_manager:
plugin = self._plugin_manager.plugins.get(plugin_name)
else:
plugin = get_plugin(plugin_name)
if plugin is None:
raise PluginNotAvailableError(
f"插件 {self.__class__.__name__} 依赖的 {plugin_name} 不可用"
)
return plugin
return property(getter)
return decorator
class PluginProxy:
"""
插件代理
延迟获取插件,避免初始化时的循环依赖问题。
Usage:
class MyPlugin(PluginBase):
def __init__(self):
super().__init__()
self._aichat = PluginProxy("AIChat")
async def handle(self):
# 首次访问时才获取插件
if self._aichat.available:
await self._aichat.instance.process()
"""
def __init__(self, plugin_name: str):
self._plugin_name = plugin_name
self._cached_instance: Optional["PluginBase"] = None
self._checked = False
@property
def instance(self) -> Optional["PluginBase"]:
"""获取插件实例(带缓存)"""
if not self._checked:
self._cached_instance = get_plugin(self._plugin_name)
self._checked = True
return self._cached_instance
@property
def available(self) -> bool:
"""检查插件是否可用"""
return self.instance is not None
def require(self) -> "PluginBase":
"""获取插件,不存在则抛出异常"""
inst = self.instance
if inst is None:
raise PluginNotAvailableError(f"插件 {self._plugin_name} 不可用")
return inst
def invalidate(self):
"""清除缓存,下次访问重新获取"""
self._cached_instance = None
self._checked = False
def __repr__(self) -> str:
status = "available" if self.available else "unavailable"
return f"<PluginProxy({self._plugin_name}) {status}>"
# ==================== 导出 ====================
__all__ = [
'PluginNotAvailableError',
'require_plugin',
'get_plugin',
'inject',
'inject_required',
'PluginProxy',
]