mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-24 00:55:07 +08:00
♿ improve plugin matcher system
This commit is contained in:
parent
f8ad9ef278
commit
41c5ac0ac7
@ -6,6 +6,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from types import ModuleType
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -36,6 +37,9 @@ current_event: ContextVar = ContextVar("current_event")
|
|||||||
class MatcherMeta(type):
|
class MatcherMeta(type):
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
module: Optional[str]
|
module: Optional[str]
|
||||||
|
plugin_name: Optional[str]
|
||||||
|
module_name: Optional[str]
|
||||||
|
module_prefix: Optional[str]
|
||||||
type: str
|
type: str
|
||||||
rule: Rule
|
rule: Rule
|
||||||
permission: Permission
|
permission: Permission
|
||||||
@ -46,7 +50,7 @@ class MatcherMeta(type):
|
|||||||
expire_time: Optional[datetime]
|
expire_time: Optional[datetime]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (f"<Matcher from {self.module or 'unknow'}, "
|
return (f"<Matcher from {self.module_name or 'unknow'}, "
|
||||||
f"type={self.type}, priority={self.priority}, "
|
f"type={self.type}, priority={self.priority}, "
|
||||||
f"temp={self.temp}>")
|
f"temp={self.temp}>")
|
||||||
|
|
||||||
@ -56,11 +60,17 @@ class MatcherMeta(type):
|
|||||||
|
|
||||||
class Matcher(metaclass=MatcherMeta):
|
class Matcher(metaclass=MatcherMeta):
|
||||||
"""事件响应器类"""
|
"""事件响应器类"""
|
||||||
module: Optional[str] = None
|
module: Optional[ModuleType] = None
|
||||||
"""
|
"""
|
||||||
:类型: ``Optional[str]``
|
:类型: ``Optional[ModuleType]``
|
||||||
:说明: 事件响应器所在模块名称
|
:说明: 事件响应器所在模块
|
||||||
"""
|
"""
|
||||||
|
plugin_name: Optional[str] = module and getattr(module, "__plugin_name__",
|
||||||
|
None)
|
||||||
|
module_name: Optional[str] = module and getattr(module, "__module_name__",
|
||||||
|
None)
|
||||||
|
module_prefix: Optional[str] = module and getattr(module,
|
||||||
|
"__module_prefix__", None)
|
||||||
|
|
||||||
type: str = ""
|
type: str = ""
|
||||||
"""
|
"""
|
||||||
@ -136,8 +146,9 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
self.state = self._default_state.copy()
|
self.state = self._default_state.copy()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (f"<Matcher from {self.module or 'unknown'}, type={self.type}, "
|
return (
|
||||||
f"priority={self.priority}, temp={self.temp}>")
|
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
|
||||||
|
f"priority={self.priority}, temp={self.temp}>")
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return repr(self)
|
return repr(self)
|
||||||
@ -153,7 +164,7 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
priority: int = 1,
|
priority: int = 1,
|
||||||
block: bool = False,
|
block: bool = False,
|
||||||
*,
|
*,
|
||||||
module: Optional[str] = None,
|
module: Optional[ModuleType] = None,
|
||||||
default_state: Optional[T_State] = None,
|
default_state: Optional[T_State] = None,
|
||||||
default_state_factory: Optional[T_StateFactory] = None,
|
default_state_factory: Optional[T_StateFactory] = None,
|
||||||
expire_time: Optional[datetime] = None) -> Type["Matcher"]:
|
expire_time: Optional[datetime] = None) -> Type["Matcher"]:
|
||||||
@ -185,6 +196,12 @@ class Matcher(metaclass=MatcherMeta):
|
|||||||
"Matcher", (Matcher,), {
|
"Matcher", (Matcher,), {
|
||||||
"module":
|
"module":
|
||||||
module,
|
module,
|
||||||
|
"plugin_name":
|
||||||
|
module and getattr(module, "__plugin_name__", None),
|
||||||
|
"module_name":
|
||||||
|
module and getattr(module, "__module_name__", None),
|
||||||
|
"module_prefix":
|
||||||
|
module and getattr(module, "__module_prefix__", None),
|
||||||
"type":
|
"type":
|
||||||
type_,
|
type_,
|
||||||
"rule":
|
"rule":
|
||||||
|
@ -65,15 +65,16 @@ class Plugin(object):
|
|||||||
- **类型**: ``Set[Type[Matcher]]``
|
- **类型**: ``Set[Type[Matcher]]``
|
||||||
- **说明**: 插件内定义的 ``Matcher``
|
- **说明**: 插件内定义的 ``Matcher``
|
||||||
"""
|
"""
|
||||||
return reduce(
|
# return reduce(
|
||||||
lambda x, y: x | _plugin_matchers[y],
|
# lambda x, y: x | _plugin_matchers[y],
|
||||||
filter(lambda x: x.startswith(self.name), _plugin_matchers.keys()),
|
# filter(lambda x: x.startswith(self.name), _plugin_matchers.keys()),
|
||||||
set())
|
# set())
|
||||||
|
return _plugin_matchers.get(self.name, set())
|
||||||
|
|
||||||
|
|
||||||
def _store_matcher(matcher: Type[Matcher]):
|
def _store_matcher(matcher: Type[Matcher]):
|
||||||
if matcher.module:
|
if matcher.plugin_name:
|
||||||
_plugin_matchers[matcher.module].add(matcher)
|
_plugin_matchers[matcher.plugin_name].add(matcher)
|
||||||
|
|
||||||
|
|
||||||
def on(type: str = "",
|
def on(type: str = "",
|
||||||
|
@ -12,8 +12,8 @@ from importlib.machinery import PathFinder, SourceFileLoader
|
|||||||
|
|
||||||
from .export import _export, Export
|
from .export import _export, Export
|
||||||
|
|
||||||
_current_plugin: ContextVar[Optional[str]] = ContextVar("_current_plugin",
|
_current_plugin: ContextVar[Optional[ModuleType]] = ContextVar(
|
||||||
default=None)
|
"_current_plugin", default=None)
|
||||||
|
|
||||||
_internal_space = ModuleType(__name__ + "._internal")
|
_internal_space = ModuleType(__name__ + "._internal")
|
||||||
_internal_space.__path__ = [] # type: ignore
|
_internal_space.__path__ = [] # type: ignore
|
||||||
@ -53,14 +53,13 @@ class _InternalModule(ModuleType):
|
|||||||
class PluginManager:
|
class PluginManager:
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
namespace: Optional[str] = None,
|
namespace: str,
|
||||||
plugins: Optional[Iterable[str]] = None,
|
plugins: Optional[Iterable[str]] = None,
|
||||||
search_path: Optional[Iterable[str]] = None,
|
search_path: Optional[Iterable[str]] = None,
|
||||||
*,
|
*,
|
||||||
id: Optional[str] = None):
|
id: Optional[str] = None):
|
||||||
self.namespace: Optional[str] = namespace
|
self.namespace: str = namespace
|
||||||
self.namespace_module: Optional[ModuleType] = self._setup_namespace(
|
self.namespace_module: ModuleType = self._setup_namespace(namespace)
|
||||||
namespace)
|
|
||||||
|
|
||||||
self.id: str = id or str(uuid.uuid4())
|
self.id: str = id or str(uuid.uuid4())
|
||||||
self.internal_id: str = md5(
|
self.internal_id: str = md5(
|
||||||
@ -73,12 +72,7 @@ class PluginManager:
|
|||||||
# ensure can be loaded
|
# ensure can be loaded
|
||||||
self.list_plugins()
|
self.list_plugins()
|
||||||
|
|
||||||
def _setup_namespace(self,
|
def _setup_namespace(self, namespace: str) -> ModuleType:
|
||||||
namespace: Optional[str] = None
|
|
||||||
) -> Optional[ModuleType]:
|
|
||||||
if not namespace:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(namespace)
|
module = importlib.import_module(namespace)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -156,14 +150,18 @@ class PluginManager:
|
|||||||
def load_all_plugins(self) -> List[ModuleType]:
|
def load_all_plugins(self) -> List[ModuleType]:
|
||||||
return [self.load_plugin(name) for name in self.list_plugins()]
|
return [self.load_plugin(name) for name in self.list_plugins()]
|
||||||
|
|
||||||
def _rewrite_module_name(self, module_name) -> Optional[str]:
|
def _rewrite_module_name(self, module_name: str) -> Optional[str]:
|
||||||
prefix = f"{self.internal_module.__name__}."
|
prefix = f"{self.internal_module.__name__}."
|
||||||
if module_name.startswith(self.namespace + "."):
|
raw_name = module_name[len(self.namespace) +
|
||||||
path = module_name.split(".")
|
1:] if module_name.startswith(self.namespace +
|
||||||
length = self.namespace.count(".") + 1
|
".") else None
|
||||||
return f"{prefix}{'.'.join(path[length:])}"
|
# dir plugins
|
||||||
|
if raw_name and raw_name.split(".")[0] in self.search_plugins():
|
||||||
|
return f"{prefix}{raw_name}"
|
||||||
|
# third party plugin or renamed dir plugins
|
||||||
elif module_name in self.plugins or module_name.startswith(prefix):
|
elif module_name in self.plugins or module_name.startswith(prefix):
|
||||||
return module_name
|
return module_name
|
||||||
|
# dir plugins
|
||||||
elif module_name in self.search_plugins():
|
elif module_name in self.search_plugins():
|
||||||
return f"{prefix}{module_name}"
|
return f"{prefix}{module_name}"
|
||||||
return None
|
return None
|
||||||
@ -194,43 +192,44 @@ class PluginLoader(SourceFileLoader):
|
|||||||
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
|
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
self.loaded = False
|
self.loaded = False
|
||||||
self._plugin_token = None
|
|
||||||
self._export_token = None
|
|
||||||
super().__init__(fullname, path)
|
super().__init__(fullname, path)
|
||||||
|
|
||||||
def create_module(self, spec) -> Optional[ModuleType]:
|
def create_module(self, spec) -> Optional[ModuleType]:
|
||||||
if self.name in sys.modules:
|
if self.name in sys.modules:
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
return sys.modules[self.name]
|
return sys.modules[self.name]
|
||||||
prefix = self.manager.internal_module.__name__
|
|
||||||
plugin_name = self.name[len(prefix):] if self.name.startswith(
|
|
||||||
prefix) else self.name
|
|
||||||
self._plugin_token = _current_plugin.set(plugin_name.lstrip("."))
|
|
||||||
self._export_token = _export.set(Export())
|
|
||||||
# return None to use default module creation
|
# return None to use default module creation
|
||||||
return super().create_module(spec)
|
return super().create_module(spec)
|
||||||
|
|
||||||
def exec_module(self, module: ModuleType) -> None:
|
def exec_module(self, module: ModuleType) -> None:
|
||||||
if self.loaded:
|
if self.loaded:
|
||||||
return
|
return
|
||||||
# really need?
|
|
||||||
# setattr(module, "__manager__", self.manager)
|
|
||||||
if self._plugin_token:
|
|
||||||
setattr(module, "__plugin_name__",
|
|
||||||
_current_plugin.get(self._plugin_token))
|
|
||||||
if self._export_token:
|
|
||||||
setattr(module, "__export__", _export.get())
|
|
||||||
|
|
||||||
try:
|
export = Export()
|
||||||
super().exec_module(module)
|
_export_token = _export.set(export)
|
||||||
except Exception as e:
|
|
||||||
raise ImportError(
|
|
||||||
f"Error when executing module {self.name}.") from e
|
|
||||||
|
|
||||||
if self._plugin_token:
|
prefix = self.manager.internal_module.__name__
|
||||||
_current_plugin.reset(self._plugin_token)
|
is_dir_plugin = self.name.startswith(prefix + ".")
|
||||||
if self._export_token:
|
module_name = self.name[len(prefix) +
|
||||||
_export.reset(self._export_token)
|
1:] if is_dir_plugin else self.name
|
||||||
|
_plugin_token = _current_plugin.set(module)
|
||||||
|
|
||||||
|
setattr(module, "__export__", export)
|
||||||
|
setattr(module, "__plugin_name__",
|
||||||
|
module_name.split(".")[0] if is_dir_plugin else module_name)
|
||||||
|
setattr(module, "__module_name__", module_name)
|
||||||
|
setattr(module, "__module_prefix__", prefix if is_dir_plugin else "")
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# super().exec_module(module)
|
||||||
|
# except Exception as e:
|
||||||
|
# raise ImportError(
|
||||||
|
# f"Error when executing module {module_name} from {module.__file__}."
|
||||||
|
# ) from e
|
||||||
|
super().exec_module(module)
|
||||||
|
|
||||||
|
_current_plugin.reset(_plugin_token)
|
||||||
|
_export.reset(_export_token)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user