Merge branch 'main' of https://gitea.functen.cn/shihao/WechatHookBot
This commit is contained in:
213
utils/plugin_inject.py
Normal file
213
utils/plugin_inject.py
Normal 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',
|
||||
]
|
||||
Reference in New Issue
Block a user