Files
WechatHookBot/utils/plugin_inject.py

214 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
插件依赖注入模块
提供插件间依赖注入功能:
- @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',
]