improve plugin matcher system

This commit is contained in:
yanyongyu 2021-05-10 18:39:59 +08:00
parent f8ad9ef278
commit 41c5ac0ac7
3 changed files with 70 additions and 53 deletions

View File

@ -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,7 +146,8 @@ 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"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
f"priority={self.priority}, temp={self.temp}>") f"priority={self.priority}, temp={self.temp}>")
def __str__(self) -> str: def __str__(self) -> str:
@ -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":

View File

@ -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 = "",

View File

@ -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) export = Export()
if self._plugin_token: _export_token = _export.set(export)
prefix = self.manager.internal_module.__name__
is_dir_plugin = self.name.startswith(prefix + ".")
module_name = self.name[len(prefix) +
1:] if is_dir_plugin else self.name
_plugin_token = _current_plugin.set(module)
setattr(module, "__export__", export)
setattr(module, "__plugin_name__", setattr(module, "__plugin_name__",
_current_plugin.get(self._plugin_token)) module_name.split(".")[0] if is_dir_plugin else module_name)
if self._export_token: setattr(module, "__module_name__", module_name)
setattr(module, "__export__", _export.get()) setattr(module, "__module_prefix__", prefix if is_dir_plugin else "")
try: # 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) super().exec_module(module)
except Exception as e:
raise ImportError(
f"Error when executing module {self.name}.") from e
if self._plugin_token: _current_plugin.reset(_plugin_token)
_current_plugin.reset(self._plugin_token) _export.reset(_export_token)
if self._export_token:
_export.reset(self._export_token)
return return