Feature: 嵌套插件名称作用域优化 (#2665)

This commit is contained in:
Ju4tCode 2024-04-20 14:47:12 +08:00 committed by GitHub
parent e15d544341
commit 6bf10aafb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 262 additions and 131 deletions

View File

@ -266,11 +266,12 @@ def _resolve_combine_expr(obj_str: str) -> type[Driver]:
def _log_patcher(record: "loguru.Record"):
"""使用插件标识优化日志展示"""
record["name"] = (
plugin.name
plugin.id_
if (module_name := record["name"])
and (plugin := get_plugin_by_module_name(module_name))
else (module_name and module_name.split(".")[0])
else (module_name and module_name.split(".", maxsplit=1)[0])
)

View File

@ -83,8 +83,8 @@ current_handler: ContextVar[Dependent] = ContextVar("current_handler")
class MatcherSource:
"""Matcher 源代码上下文信息"""
plugin_name: Optional[str] = None
"""事件响应器所在插件名称"""
plugin_id: Optional[str] = None
"""事件响应器所在插件标识符"""
module_name: Optional[str] = None
"""事件响应器所在插件模块的路径名"""
lineno: Optional[int] = None
@ -95,8 +95,13 @@ class MatcherSource:
"""事件响应器所在插件"""
from nonebot.plugin import get_plugin
if self.plugin_name is not None:
return get_plugin(self.plugin_name)
if self.plugin_id is not None:
return get_plugin(self.plugin_id)
@property
def plugin_name(self) -> Optional[str]:
"""事件响应器所在插件名"""
return self.plugin and self.plugin.name
@property
def module(self) -> Optional[ModuleType]:
@ -245,7 +250,7 @@ class Matcher(metaclass=MatcherMeta):
)
source = source or (
MatcherSource(
plugin_name=plugin and plugin.name,
plugin_id=plugin and plugin.id_,
module_name=module and module.__name__,
)
if plugin is not None or module is not None
@ -328,15 +333,20 @@ class Matcher(metaclass=MatcherMeta):
return cls._source and cls._source.plugin
@classproperty
def module(cls) -> Optional[ModuleType]:
"""事件响应器所在插件模块"""
return cls._source and cls._source.module
def plugin_id(cls) -> Optional[str]:
"""事件响应器所在插件标识符"""
return cls._source and cls._source.plugin_id
@classproperty
def plugin_name(cls) -> Optional[str]:
"""事件响应器所在插件名"""
return cls._source and cls._source.plugin_name
@classproperty
def module(cls) -> Optional[ModuleType]:
"""事件响应器所在插件模块"""
return cls._source and cls._source.module
@classproperty
def module_name(cls) -> Optional[str]:
"""事件响应器所在插件模块路径"""

View File

@ -50,8 +50,8 @@ C = TypeVar("C", bound=BaseModel)
_plugins: dict[str, "Plugin"] = {}
_managers: list["PluginManager"] = []
_current_plugin_chain: ContextVar[tuple["Plugin", ...]] = ContextVar(
"_current_plugin_chain", default=()
_current_plugin: ContextVar[Optional["Plugin"]] = ContextVar(
"_current_plugin", default=None
)
@ -59,34 +59,87 @@ def _module_name_to_plugin_name(module_name: str) -> str:
return module_name.rsplit(".", 1)[-1]
def _controlled_modules() -> dict[str, str]:
return {
plugin_id: module_name
for manager in _managers
for plugin_id, module_name in manager.controlled_modules.items()
}
def _find_parent_plugin_id(
module_name: str, controlled_modules: Optional[dict[str, str]] = None
) -> Optional[str]:
if controlled_modules is None:
controlled_modules = _controlled_modules()
available = {
module_name: plugin_id for plugin_id, module_name in controlled_modules.items()
}
while "." in module_name:
module_name, _ = module_name.rsplit(".", 1)
if module_name in available:
return available[module_name]
def _module_name_to_plugin_id(
module_name: str, controlled_modules: Optional[dict[str, str]] = None
) -> str:
plugin_name = _module_name_to_plugin_name(module_name)
if parent_plugin_id := _find_parent_plugin_id(module_name, controlled_modules):
return f"{parent_plugin_id}:{plugin_name}"
return plugin_name
def _new_plugin(
module_name: str, module: ModuleType, manager: "PluginManager"
) -> "Plugin":
plugin_name = _module_name_to_plugin_name(module_name)
if plugin_name in _plugins:
raise RuntimeError("Plugin already exists! Check your plugin name.")
plugin = Plugin(plugin_name, module, module_name, manager)
_plugins[plugin_name] = plugin
plugin_id = _module_name_to_plugin_id(module_name)
if plugin_id in _plugins:
raise RuntimeError(
f"Plugin {plugin_id} already exists! Check your plugin name."
)
parent_plugin_id = _find_parent_plugin_id(module_name)
if parent_plugin_id is not None and parent_plugin_id not in _plugins:
raise RuntimeError(
f"Parent plugin {parent_plugin_id} must "
f"be loaded before loading {plugin_id}."
)
parent_plugin = _plugins[parent_plugin_id] if parent_plugin_id is not None else None
plugin = Plugin(
name=_module_name_to_plugin_name(module_name),
module=module,
module_name=module_name,
manager=manager,
parent_plugin=parent_plugin,
)
if parent_plugin:
parent_plugin.sub_plugins.add(plugin)
_plugins[plugin_id] = plugin
return plugin
def _revert_plugin(plugin: "Plugin") -> None:
if plugin.name not in _plugins:
if plugin.id_ not in _plugins:
raise RuntimeError("Plugin not found!")
del _plugins[plugin.name]
del _plugins[plugin.id_]
if parent_plugin := plugin.parent_plugin:
parent_plugin.sub_plugins.remove(plugin)
parent_plugin.sub_plugins.discard(plugin)
def get_plugin(name: str) -> Optional["Plugin"]:
def get_plugin(plugin_id: str) -> Optional["Plugin"]:
"""获取已经导入的某个插件。
如果为 `load_plugins` 文件夹导入的插件则为文件()
如果为嵌套的子插件标识符为 `父插件标识符:子插件文件()`
参数:
name: 插件名 {ref}`nonebot.plugin.model.Plugin.name`
plugin_id: 插件标识符 {ref}`nonebot.plugin.model.Plugin.id_`
"""
return _plugins.get(name)
return _plugins.get(plugin_id)
def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
@ -111,7 +164,7 @@ def get_loaded_plugins() -> set["Plugin"]:
def get_available_plugin_names() -> set[str]:
"""获取当前所有可用的插件(包含尚未加载的插件)。"""
"""获取当前所有可用的插件标识符(包含尚未加载的插件)。"""
return {*chain.from_iterable(manager.available_plugins for manager in _managers)}

View File

@ -15,7 +15,7 @@ from nonebot.utils import path_to_module_name
from .model import Plugin
from .manager import PluginManager
from . import _managers, get_plugin, _current_plugin_chain, _module_name_to_plugin_name
from . import _managers, get_plugin, _module_name_to_plugin_id
try: # pragma: py-gte-311
import tomllib # pyright: ignore[reportMissingImports]
@ -151,36 +151,40 @@ def load_builtin_plugins(*plugins: str) -> set[Plugin]:
def _find_manager_by_name(name: str) -> Optional[PluginManager]:
for manager in reversed(_managers):
if name in manager.plugins or name in manager.searched_plugins:
if (
name in manager.controlled_modules
or name in manager.controlled_modules.values()
):
return manager
def require(name: str) -> ModuleType:
"""获取一个插件的导出内容。
如果为 `load_plugins` 文件夹导入的插件则为文件()
"""声明依赖插件。
参数:
name: 插件 {ref}`nonebot.plugin.model.Plugin.name`
name: 插件模块名或插件标识符仅在已声明插件的情况下可使用标识符
异常:
RuntimeError: 插件无法加载
"""
plugin = get_plugin(_module_name_to_plugin_name(name))
if "." in name:
# name is a module name
plugin = get_plugin(_module_name_to_plugin_id(name))
else:
# name is a plugin id or simple module name (equals to plugin id)
plugin = get_plugin(name)
# if plugin not loaded
if not plugin:
# plugin already declared
if plugin is None:
# plugin already declared, module name / plugin id
if manager := _find_manager_by_name(name):
plugin = manager.load_plugin(name)
# plugin not declared, try to declare and load it
else:
# clear current plugin chain, ensure plugin loaded in a new context
_t = _current_plugin_chain.set(())
try:
plugin = load_plugin(name)
finally:
_current_plugin_chain.reset(_t)
if not plugin:
plugin = load_plugin(name)
if plugin is None:
raise RuntimeError(f'Cannot load plugin "{name}"!')
return plugin.module
@ -200,9 +204,11 @@ def inherit_supported_adapters(*names: str) -> Optional[set[str]]:
final_supported: Optional[set[str]] = None
for name in names:
plugin = get_plugin(_module_name_to_plugin_name(name))
plugin = get_plugin(_module_name_to_plugin_id(name))
if plugin is None:
raise RuntimeError(f'Plugin "{name}" is not loaded!')
raise RuntimeError(
f'Plugin "{name}" is not loaded! You should require it first.'
)
meta = plugin.metadata
if meta is None:
raise ValueError(f'Plugin "{name}" has no metadata!')

View File

@ -26,8 +26,8 @@ from . import (
_managers,
_new_plugin,
_revert_plugin,
_current_plugin_chain,
_module_name_to_plugin_name,
_current_plugin,
_module_name_to_plugin_id,
)
@ -36,7 +36,7 @@ class PluginManager:
参数:
plugins: 独立插件模块名集合
search_path: 插件搜索路径文件夹
search_path: 插件搜索路径文件夹相对于当前工作目录
"""
def __init__(
@ -49,29 +49,38 @@ class PluginManager:
self.search_path: set[str] = set(search_path or [])
# cache plugins
self._third_party_plugin_names: dict[str, str] = {}
self._searched_plugin_names: dict[str, Path] = {}
self.prepare_plugins()
self._third_party_plugin_ids: dict[str, str] = {}
self._searched_plugin_ids: dict[str, str] = {}
self._prepare_plugins()
def __repr__(self) -> str:
return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
return f"PluginManager(available_plugins={self.controlled_modules})"
@property
def third_party_plugins(self) -> set[str]:
"""返回所有独立插件名称"""
return set(self._third_party_plugin_names.keys())
"""返回所有独立插件标识符"""
return set(self._third_party_plugin_ids.keys())
@property
def searched_plugins(self) -> set[str]:
"""返回已搜索到的插件名称"""
return set(self._searched_plugin_names.keys())
"""返回已搜索到的插件标识符"""
return set(self._searched_plugin_ids.keys())
@property
def available_plugins(self) -> set[str]:
"""返回当前插件管理器中可用的插件名称"""
"""返回当前插件管理器中可用的插件标识符"""
return self.third_party_plugins | self.searched_plugins
def _previous_plugins(self) -> set[str]:
@property
def controlled_modules(self) -> dict[str, str]:
"""返回当前插件管理器中控制的插件标识符与模块路径映射字典。"""
return dict(
chain(
self._third_party_plugin_ids.items(), self._searched_plugin_ids.items()
)
)
def _previous_controlled_modules(self) -> dict[str, str]:
_pre_managers: list[PluginManager]
if self in _managers:
_pre_managers = _managers[: _managers.index(self)]
@ -79,26 +88,35 @@ class PluginManager:
_pre_managers = _managers[:]
return {
*chain.from_iterable(manager.available_plugins for manager in _pre_managers)
plugin_id: module_name
for manager in _pre_managers
for plugin_id, module_name in manager.controlled_modules.items()
}
def prepare_plugins(self) -> set[str]:
def _prepare_plugins(self) -> set[str]:
"""搜索插件并缓存插件名称。"""
# get all previous ready to load plugins
previous_plugins = self._previous_plugins()
searched_plugins: dict[str, Path] = {}
third_party_plugins: dict[str, str] = {}
previous_plugin_ids = self._previous_controlled_modules()
# if self not in global managers, merge self's controlled modules
def get_controlled_modules():
return (
previous_plugin_ids
if self in _managers
else {**previous_plugin_ids, **self.controlled_modules}
)
# check third party plugins
for plugin in self.plugins:
name = _module_name_to_plugin_name(plugin)
if name in third_party_plugins or name in previous_plugins:
plugin_id = _module_name_to_plugin_id(plugin, get_controlled_modules())
if (
plugin_id in self._third_party_plugin_ids
or plugin_id in previous_plugin_ids
):
raise RuntimeError(
f"Plugin already exists: {name}! Check your plugin name"
f"Plugin already exists: {plugin_id}! Check your plugin name"
)
third_party_plugins[name] = plugin
self._third_party_plugin_names = third_party_plugins
self._third_party_plugin_ids[plugin_id] = plugin
# check plugins in search path
for module_info in pkgutil.iter_modules(self.search_path):
@ -106,47 +124,55 @@ class PluginManager:
if module_info.name.startswith("_"):
continue
if (
module_info.name in searched_plugins
or module_info.name in previous_plugins
or module_info.name in third_party_plugins
):
raise RuntimeError(
f"Plugin already exists: {module_info.name}! Check your plugin name"
)
if not (
module_spec := module_info.module_finder.find_spec(
module_info.name, None
)
):
continue
if not (module_path := module_spec.origin):
continue
searched_plugins[module_info.name] = Path(module_path).resolve()
self._searched_plugin_names = searched_plugins
if not module_spec.origin:
continue
# get module name from path, pkgutil does not return the actual module name
module_path = Path(module_spec.origin).resolve()
module_name = path_to_module_name(module_path)
plugin_id = _module_name_to_plugin_id(module_name, get_controlled_modules())
if (
plugin_id in previous_plugin_ids
or plugin_id in self._third_party_plugin_ids
or plugin_id in self._searched_plugin_ids
):
raise RuntimeError(
f"Plugin already exists: {plugin_id}! Check your plugin name"
)
self._searched_plugin_ids[plugin_id] = module_name
return self.available_plugins
def load_plugin(self, name: str) -> Optional[Plugin]:
"""加载指定插件。
对于独立插件可以使用完整插件模块名或者插件名称
可以使用完整插件模块名或者插件标识符加载
参数:
name: 插件名称
name: 插件名称或插件标识符
"""
try:
if name in self.plugins:
# load using plugin id
if name in self._third_party_plugin_ids:
module = importlib.import_module(self._third_party_plugin_ids[name])
elif name in self._searched_plugin_ids:
module = importlib.import_module(self._searched_plugin_ids[name])
# load using module name
elif (
name in self._third_party_plugin_ids.values()
or name in self._searched_plugin_ids.values()
):
module = importlib.import_module(name)
elif name in self._third_party_plugin_names:
module = importlib.import_module(self._third_party_plugin_names[name])
elif name in self._searched_plugin_names:
module = importlib.import_module(
path_to_module_name(self._searched_plugin_names[name])
)
else:
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
@ -155,13 +181,13 @@ class PluginManager:
) is None or not isinstance(plugin, Plugin):
raise RuntimeError(
f"Module {module.__name__} is not loaded as a plugin! "
"Make sure not to import it before loading."
f"Make sure not to import it before loading."
)
logger.opt(colors=True).success(
f'Succeeded to load plugin "<y>{escape_tag(plugin.name)}</y>"'
f'Succeeded to load plugin "<y>{escape_tag(plugin.id_)}</y>"'
+ (
f' from "<m>{escape_tag(plugin.module_name)}</m>"'
if plugin.module_name != plugin.name
if plugin.module_name != plugin.id_
else ""
)
)
@ -193,21 +219,16 @@ class PluginFinder(MetaPathFinder):
module_origin = module_spec.origin
if not module_origin:
return
module_path = Path(module_origin).resolve()
for manager in reversed(_managers):
# use path instead of name in case of submodule name conflict
if (
fullname in manager.plugins
or module_path in manager._searched_plugin_names.values()
):
if fullname in manager.controlled_modules.values():
module_spec.loader = PluginLoader(manager, fullname, module_origin)
return module_spec
return
class PluginLoader(SourceFileLoader):
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
def __init__(self, manager: PluginManager, fullname: str, path: str) -> None:
self.manager = manager
self.loaded = False
super().__init__(fullname, path)
@ -227,17 +248,8 @@ class PluginLoader(SourceFileLoader):
plugin = _new_plugin(self.name, module, self.manager)
setattr(module, "__plugin__", plugin)
# detect parent plugin before entering current plugin context
parent_plugins = _current_plugin_chain.get()
for pre_plugin in reversed(parent_plugins):
# ensure parent plugin is declared before current plugin
if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
plugin.parent_plugin = pre_plugin
pre_plugin.sub_plugins.add(plugin)
break
# enter plugin context
_plugin_token = _current_plugin_chain.set((*parent_plugins, plugin))
_plugin_token = _current_plugin.set(plugin)
try:
super().exec_module(module)
@ -246,7 +258,7 @@ class PluginLoader(SourceFileLoader):
raise
finally:
# leave plugin context
_current_plugin_chain.reset(_plugin_token)
_current_plugin.reset(_plugin_token)
# get plugin metadata
metadata: Optional[PluginMetadata] = getattr(module, "__plugin_meta__", None)

View File

@ -66,7 +66,7 @@ class Plugin:
"""存储插件信息"""
name: str
"""插件索引标识NoneBot 使用 文件/文件夹 名称作为标识符"""
"""插件名称NoneBot 使用 文件/文件夹 名称作为插件名称"""
module: ModuleType
"""插件模块对象"""
module_name: str
@ -80,3 +80,10 @@ class Plugin:
sub_plugins: set["Plugin"] = field(default_factory=set)
"""子插件集合"""
metadata: Optional[PluginMetadata] = None
@property
def id_(self) -> str:
"""插件索引标识"""
return (
f"{self.parent_plugin.id_}:{self.name}" if self.parent_plugin else self.name
)

View File

@ -31,8 +31,8 @@ from nonebot.rule import (
)
from .model import Plugin
from .manager import _current_plugin
from . import get_plugin_by_module_name
from .manager import _current_plugin_chain
def store_matcher(matcher: type[Matcher]) -> None:
@ -42,8 +42,8 @@ def store_matcher(matcher: type[Matcher]) -> None:
matcher: 事件响应器
"""
# only store the matcher defined when plugin loading
if plugin_chain := _current_plugin_chain.get():
plugin_chain[-1].matcher.add(matcher)
if plugin := _current_plugin.get():
plugin.matcher.add(matcher)
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]: # pragma: no cover
@ -96,16 +96,14 @@ def get_matcher_source(depth: int = 0) -> Optional[MatcherSource]:
module_name = (module := inspect.getmodule(frame)) and module.__name__
plugin: Optional["Plugin"] = None
# matcher defined when plugin loading
if plugin_chain := _current_plugin_chain.get():
plugin = plugin_chain[-1]
plugin: Optional["Plugin"] = _current_plugin.get()
# matcher defined when plugin running
elif module_name:
if plugin is None and module_name:
plugin = get_plugin_by_module_name(module_name)
return MatcherSource(
plugin_name=plugin and plugin.name,
plugin_id=plugin and plugin.id_,
module_name=module_name,
lineno=frame.f_lineno,
)

View File

@ -8,5 +8,5 @@ manager = PluginManager(
_managers.append(manager)
# test load nested plugin with require
manager.load_plugin("nested_subplugin")
manager.load_plugin("nested_subplugin2")
manager.load_plugin("plugins.nested.plugins.nested_subplugin")
manager.load_plugin("nested:nested_subplugin2")

View File

@ -29,8 +29,10 @@ async def test_matcher_info(app: App):
assert matcher.module is sys.modules["plugins.matcher.matcher_info"]
assert matcher.module_name == "plugins.matcher.matcher_info"
assert matcher._source.plugin_id == "matcher:matcher_info"
assert matcher._source.plugin_name == "matcher_info"
assert matcher.plugin is get_plugin("matcher_info")
assert matcher.plugin is get_plugin("matcher:matcher_info")
assert matcher.plugin_id == "matcher:matcher_info"
assert matcher.plugin_name == "matcher_info"
assert (

View File

@ -10,18 +10,43 @@ async def test_get_plugin():
# check simple plugin
plugin = nonebot.get_plugin("export")
assert plugin
assert plugin.id_ == "export"
assert plugin.name == "export"
assert plugin.module_name == "plugins.export"
# check sub plugin
plugin = nonebot.get_plugin("nested_subplugin")
plugin = nonebot.get_plugin("nested:nested_subplugin")
assert plugin
assert plugin.id_ == "nested:nested_subplugin"
assert plugin.name == "nested_subplugin"
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
# check get plugin by module name
@pytest.mark.asyncio
async def test_get_plugin_by_module_name():
# check get plugin by exact module name
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
assert plugin
assert plugin.id_ == "nested"
assert plugin.name == "nested"
assert plugin.module_name == "plugins.nested"
# check get plugin by sub module name
plugin = nonebot.get_plugin_by_module_name("plugins.nested.utils")
assert plugin
assert plugin.id_ == "nested"
assert plugin.name == "nested"
assert plugin.module_name == "plugins.nested"
# check get plugin by sub plugin exact module name
plugin = nonebot.get_plugin_by_module_name(
"plugins.nested.plugins.nested_subplugin"
)
assert plugin
assert plugin.id_ == "nested:nested_subplugin"
assert plugin.name == "nested_subplugin"
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
@pytest.mark.asyncio
async def test_get_available_plugin():
@ -31,8 +56,8 @@ async def test_get_available_plugin():
_managers.append(PluginManager(["plugins.export", "plugin.require"]))
# check get available plugins
plugin_names = nonebot.get_available_plugin_names()
assert plugin_names == {"export", "require"}
plugin_ids = nonebot.get_available_plugin_names()
assert plugin_ids == {"export", "require"}
finally:
_managers.clear()
_managers.extend(old_managers)

View File

@ -29,12 +29,13 @@ async def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[P
# check simple plugin
assert "plugins.export" in sys.modules
assert "plugin._hidden" not in sys.modules
# check sub plugin
plugin = nonebot.get_plugin("nested_subplugin")
plugin = nonebot.get_plugin("nested:nested_subplugin")
assert plugin
assert "plugins.nested.plugins.nested_subplugin" in sys.modules
assert plugin.parent_plugin == nonebot.get_plugin("nested")
assert plugin.parent_plugin is nonebot.get_plugin("nested")
# check load again
with pytest.raises(RuntimeError):
@ -46,8 +47,8 @@ async def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[P
@pytest.mark.asyncio
async def test_load_nested_plugin():
parent_plugin = nonebot.get_plugin("nested")
sub_plugin = nonebot.get_plugin("nested_subplugin")
sub_plugin2 = nonebot.get_plugin("nested_subplugin2")
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
sub_plugin2 = nonebot.get_plugin("nested:nested_subplugin2")
assert parent_plugin
assert sub_plugin
assert sub_plugin2
@ -89,12 +90,16 @@ async def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
# require use module name
nonebot.require("plugins.export")
# require use plugin id
nonebot.require("export")
nonebot.require("nested:nested_subplugin")
@pytest.mark.asyncio
async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
m = PluginManager(["dynamic.require_not_loaded"])
m = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
_managers.append(m)
num_managers = len(_managers)
@ -106,7 +111,11 @@ async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)
# require standalone plugin
nonebot.require("dynamic.require_not_loaded")
# require searched plugin
nonebot.require("dynamic.require_not_loaded.subplugin1")
nonebot.require("require_not_loaded:subplugin2")
assert len(_managers) == num_managers

View File

@ -1,11 +1,17 @@
import pytest
from nonebot.plugin import PluginManager
from nonebot.plugin import PluginManager, _managers
@pytest.mark.asyncio
async def test_load_plugin_name():
m = PluginManager(plugins=["dynamic.manager"])
_managers.append(m)
# load by plugin id
module1 = m.load_plugin("manager")
# load by module name
module2 = m.load_plugin("dynamic.manager")
assert module1
assert module2
assert module1 is module2

View File

@ -145,6 +145,7 @@ async def test_on(
assert matcher.plugin is plugin
assert matcher in plugin.matcher
assert matcher.module is module
assert matcher.plugin_id == "plugin"
assert matcher.plugin_name == "plugin"
assert matcher.module_name == "plugins.plugin.matchers"
@ -163,6 +164,7 @@ async def test_runtime_on():
assert matcher.plugin is plugin
assert matcher not in plugin.matcher
assert matcher.module is module
assert matcher.plugin_id == "plugin"
assert matcher.plugin_name == "plugin"
assert matcher.module_name == "plugins.plugin.matchers"
finally: