214 lines
5.6 KiB
Python
214 lines
5.6 KiB
Python
"""
|
||
插件依赖注入模块
|
||
|
||
提供插件间依赖注入功能:
|
||
- @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',
|
||
]
|