Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions nonebot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
)


Expand Down
26 changes: 18 additions & 8 deletions nonebot/internal/matcher/matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@
class MatcherSource:
"""Matcher 源代码上下文信息"""

plugin_name: Optional[str] = None
"""事件响应器所在插件名称"""
plugin_id: Optional[str] = None
"""事件响应器所在插件标识符"""
module_name: Optional[str] = None
"""事件响应器所在插件模块的路径名"""
lineno: Optional[int] = None
Expand All @@ -95,8 +95,13 @@ def plugin(self) -> Optional["Plugin"]:
"""事件响应器所在插件"""
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]:
Expand Down Expand Up @@ -245,7 +250,7 @@ def new(
)
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
Expand Down Expand Up @@ -328,15 +333,20 @@ def plugin(cls) -> Optional["Plugin"]:
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]:
"""事件响应器所在插件模块路径"""
Expand Down
81 changes: 67 additions & 14 deletions nonebot/plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,43 +50,96 @@

_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
)


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(

Check warning on line 98 in nonebot/plugin/__init__.py

View check run for this annotation

Codecov / codecov/patch

nonebot/plugin/__init__.py#L98

Added line #L98 was not covered by tests
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(

Check warning on line 104 in nonebot/plugin/__init__.py

View check run for this annotation

Codecov / codecov/patch

nonebot/plugin/__init__.py#L104

Added line #L104 was not covered by tests
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)

Check warning on line 129 in nonebot/plugin/__init__.py

View check run for this annotation

Codecov / codecov/patch

nonebot/plugin/__init__.py#L129

Added line #L129 was not covered by tests


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"]:
Expand All @@ -111,7 +164,7 @@


def get_available_plugin_names() -> set[str]:
"""获取当前所有可用的插件名(包含尚未加载的插件)。"""
"""获取当前所有可用的插件标识符(包含尚未加载的插件)。"""
return {*chain.from_iterable(manager.available_plugins for manager in _managers)}


Expand Down
42 changes: 24 additions & 18 deletions nonebot/plugin/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

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]
Expand Down Expand Up @@ -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

Expand All @@ -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!')
Expand Down
Loading
Loading