plugin_name不再受文件夹名称限制
This commit is contained in:
53
changes.md
53
changes.md
@@ -1,16 +1,20 @@
|
|||||||
# 插件API与规范修改
|
# 插件API与规范修改
|
||||||
|
|
||||||
1. 现在`plugin_system`的`__init__.py`文件中包含了所有插件API的导入,用户可以直接使用`from plugin_system import *`来导入所有API。
|
1. 现在`plugin_system`的`__init__.py`文件中包含了所有插件API的导入,用户可以直接使用`from src.plugin_system import *`来导入所有API。
|
||||||
|
|
||||||
2. register_plugin函数现在转移到了`plugin_system.apis.plugin_register_api`模块中,用户可以通过`from plugin_system.apis.plugin_register_api import register_plugin`来导入。
|
2. register_plugin函数现在转移到了`plugin_system.apis.plugin_register_api`模块中,用户可以通过`from src.plugin_system.apis.plugin_register_api import register_plugin`来导入。
|
||||||
|
- 顺便一提,按照1中说法,你可以这么用:
|
||||||
|
```python
|
||||||
|
from src.plugin_system import register_plugin
|
||||||
|
```
|
||||||
|
|
||||||
3. 现在强制要求的property如下:
|
3. 现在强制要求的property如下,即你必须覆盖的属性有:
|
||||||
- `plugin_name`: 插件名称,必须是唯一的。(与文件夹相同)
|
- `plugin_name`: 插件名称,必须是唯一的。(与文件夹相同)
|
||||||
- `enable_plugin`: 是否启用插件,默认为`True`。
|
- `enable_plugin`: 是否启用插件,默认为`True`。
|
||||||
- `dependencies`: 插件依赖的其他插件列表,默认为空。**现在并不检查(也许)**
|
- `dependencies`: 插件依赖的其他插件列表,默认为空。**现在并不检查(也许)**
|
||||||
- `python_dependencies`: 插件依赖的Python包列表,默认为空。**现在并不检查**
|
- `python_dependencies`: 插件依赖的Python包列表,默认为空。**现在并不检查**
|
||||||
- `config_file_name`: 插件配置文件名,默认为`config.toml`。
|
- `config_file_name`: 插件配置文件名,默认为`config.toml`。
|
||||||
- `config_schema`: 插件配置文件的schema,用于自动生成配置文件。
|
- `config_schema`: 插件配置文件的schema,用于自动生成配置文件。
|
||||||
|
|
||||||
# 插件系统修改
|
# 插件系统修改
|
||||||
1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)**
|
1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)**
|
||||||
@@ -22,5 +26,32 @@
|
|||||||
- `database_api.py`中的`db_query`方法调整了参数顺序以增强参数限制的同时,保证了typing正确;`db_get`方法增加了`single_result`参数,与`db_query`保持一致。
|
- `database_api.py`中的`db_query`方法调整了参数顺序以增强参数限制的同时,保证了typing正确;`db_get`方法增加了`single_result`参数,与`db_query`保持一致。
|
||||||
4. 现在增加了参数类型检查,完善了对应注释
|
4. 现在增加了参数类型检查,完善了对应注释
|
||||||
5. 现在插件抽象出了总基类 `PluginBase`
|
5. 现在插件抽象出了总基类 `PluginBase`
|
||||||
- 基于`Action`和`Command`的插件基类现在为`BasePlugin`,它继承自`PluginBase`,由`register_plugin`装饰器注册。
|
- 基于`Action`和`Command`的插件基类现在为`BasePlugin`。
|
||||||
- 基于`Event`的插件基类现在为`BaseEventPlugin`,它也继承自`PluginBase`,由`register_event_plugin`装饰器注册。
|
- 基于`Event`的插件基类现在为`BaseEventPlugin`。
|
||||||
|
- 所有的插件都继承自`PluginBase`。
|
||||||
|
- 所有的插件都由`register_plugin`装饰器注册。
|
||||||
|
6. 现在我们终于可以让插件有自定义的名字了!
|
||||||
|
- 真正实现了插件的`plugin_name`**不受文件夹名称限制**的功能。(吐槽:可乐你的某个小小细节导致我搞了好久……)
|
||||||
|
- 通过在插件类中定义`plugin_name`属性来指定插件内部标识符。
|
||||||
|
- 由于此更改一个文件中现在可以有多个插件类,但每个插件类必须有**唯一的**`plugin_name`。
|
||||||
|
- 在某些插件加载失败时,现在会显示包名而不是插件内部标识符。
|
||||||
|
- 例如:`MaiMBot.plugins.example_plugin`而不是`example_plugin`。
|
||||||
|
- 仅在插件 import 失败时会如此,正常注册过程中失败的插件不会显示包名,而是显示插件内部标识符。(这是特性,但是基本上不可能出现这个情况)
|
||||||
|
7. 现在不支持单文件插件了,加载方式已经完全删除。
|
||||||
|
|
||||||
|
|
||||||
|
# 吐槽
|
||||||
|
```python
|
||||||
|
plugin_path = Path(plugin_file)
|
||||||
|
if plugin_path.parent.name != "plugins":
|
||||||
|
# 插件包格式:parent_dir.plugin
|
||||||
|
module_name = f"plugins.{plugin_path.parent.name}.plugin"
|
||||||
|
else:
|
||||||
|
# 单文件格式:plugins.filename
|
||||||
|
module_name = f"plugins.{plugin_path.stem}"
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
plugin_path = Path(plugin_file)
|
||||||
|
module_name = ".".join(plugin_path.parent.parts)
|
||||||
|
```
|
||||||
|
这两个区别很大的。
|
||||||
@@ -18,11 +18,16 @@ from .base import (
|
|||||||
CommandInfo,
|
CommandInfo,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
PythonDependency,
|
PythonDependency,
|
||||||
|
BaseEventHandler,
|
||||||
|
EventHandlerInfo,
|
||||||
|
EventType,
|
||||||
|
BaseEventPlugin,
|
||||||
)
|
)
|
||||||
from .core.plugin_manager import (
|
from .core import (
|
||||||
plugin_manager,
|
plugin_manager,
|
||||||
component_registry,
|
component_registry,
|
||||||
dependency_manager,
|
dependency_manager,
|
||||||
|
events_manager,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 导入工具模块
|
# 导入工具模块
|
||||||
@@ -43,6 +48,8 @@ __all__ = [
|
|||||||
"BasePlugin",
|
"BasePlugin",
|
||||||
"BaseAction",
|
"BaseAction",
|
||||||
"BaseCommand",
|
"BaseCommand",
|
||||||
|
"BaseEventPlugin",
|
||||||
|
"BaseEventHandler",
|
||||||
# 类型定义
|
# 类型定义
|
||||||
"ComponentType",
|
"ComponentType",
|
||||||
"ActionActivationType",
|
"ActionActivationType",
|
||||||
@@ -52,10 +59,13 @@ __all__ = [
|
|||||||
"CommandInfo",
|
"CommandInfo",
|
||||||
"PluginInfo",
|
"PluginInfo",
|
||||||
"PythonDependency",
|
"PythonDependency",
|
||||||
|
"EventHandlerInfo",
|
||||||
|
"EventType",
|
||||||
# 管理器
|
# 管理器
|
||||||
"plugin_manager",
|
"plugin_manager",
|
||||||
"component_registry",
|
"component_registry",
|
||||||
"dependency_manager",
|
"dependency_manager",
|
||||||
|
"events_manager",
|
||||||
# 装饰器
|
# 装饰器
|
||||||
"register_plugin",
|
"register_plugin",
|
||||||
"ConfigField",
|
"ConfigField",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
logger = get_logger("plugin_register")
|
logger = get_logger("plugin_register")
|
||||||
@@ -22,8 +24,20 @@ def register_plugin(cls):
|
|||||||
|
|
||||||
# 只是注册插件类,不立即实例化
|
# 只是注册插件类,不立即实例化
|
||||||
# 插件管理器会负责实例化和注册
|
# 插件管理器会负责实例化和注册
|
||||||
plugin_name = cls.plugin_name or cls.__name__
|
plugin_name: str = cls.plugin_name # type: ignore
|
||||||
plugin_manager.plugin_classes[plugin_name] = cls # type: ignore
|
plugin_manager.plugin_classes[plugin_name] = cls
|
||||||
logger.debug(f"插件类已注册: {plugin_name}")
|
splitted_name = cls.__module__.split(".")
|
||||||
|
root_path = Path(__file__)
|
||||||
|
|
||||||
return cls
|
# 查找项目根目录
|
||||||
|
while not (root_path / "pyproject.toml").exists() and root_path.parent != root_path:
|
||||||
|
root_path = root_path.parent
|
||||||
|
|
||||||
|
if not (root_path / "pyproject.toml").exists():
|
||||||
|
logger.error(f"注册 {plugin_name} 无法找到项目根目录")
|
||||||
|
return cls
|
||||||
|
|
||||||
|
plugin_manager.plugin_paths[plugin_name] = str(Path(root_path, *splitted_name).resolve())
|
||||||
|
logger.debug(f"插件类已注册: {plugin_name}, 路径: {plugin_manager.plugin_paths[plugin_name]}")
|
||||||
|
|
||||||
|
return cls
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
from .base_plugin import BasePlugin
|
from .base_plugin import BasePlugin
|
||||||
from .base_action import BaseAction
|
from .base_action import BaseAction
|
||||||
from .base_command import BaseCommand
|
from .base_command import BaseCommand
|
||||||
|
from .base_event_plugin import BaseEventPlugin
|
||||||
|
from .base_events_handler import BaseEventHandler
|
||||||
from .component_types import (
|
from .component_types import (
|
||||||
ComponentType,
|
ComponentType,
|
||||||
ActionActivationType,
|
ActionActivationType,
|
||||||
@@ -16,6 +18,8 @@ from .component_types import (
|
|||||||
CommandInfo,
|
CommandInfo,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
PythonDependency,
|
PythonDependency,
|
||||||
|
EventHandlerInfo,
|
||||||
|
EventType,
|
||||||
)
|
)
|
||||||
from .config_types import ConfigField
|
from .config_types import ConfigField
|
||||||
|
|
||||||
@@ -32,4 +36,8 @@ __all__ = [
|
|||||||
"PluginInfo",
|
"PluginInfo",
|
||||||
"PythonDependency",
|
"PythonDependency",
|
||||||
"ConfigField",
|
"ConfigField",
|
||||||
|
"EventHandlerInfo",
|
||||||
|
"EventType",
|
||||||
|
"BaseEventPlugin",
|
||||||
|
"BaseEventHandler",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import List, Tuple, Type, TYPE_CHECKING
|
from typing import List, Tuple, Type
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from .plugin_base import PluginBase
|
from .plugin_base import PluginBase
|
||||||
from .component_types import EventHandlerInfo
|
from .component_types import EventHandlerInfo
|
||||||
|
from .base_events_handler import BaseEventHandler
|
||||||
if TYPE_CHECKING:
|
|
||||||
from src.plugin_system.base.base_events_handler import BaseEventHandler
|
|
||||||
|
|
||||||
logger = get_logger("base_event_plugin")
|
logger = get_logger("base_event_plugin")
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
from src.plugin_system.core.dependency_manager import dependency_manager
|
from src.plugin_system.core.dependency_manager import dependency_manager
|
||||||
|
from src.plugin_system.core.events_manager import events_manager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"plugin_manager",
|
"plugin_manager",
|
||||||
"component_registry",
|
"component_registry",
|
||||||
"dependency_manager",
|
"dependency_manager",
|
||||||
|
"events_manager",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from typing import Dict, List, Optional, Tuple, Type, Any
|
|
||||||
import os
|
import os
|
||||||
from importlib.util import spec_from_file_location, module_from_spec
|
import inspect
|
||||||
from inspect import getmodule
|
|
||||||
from pathlib import Path
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from typing import Dict, List, Optional, Tuple, Type, Any
|
||||||
|
from importlib.util import spec_from_file_location, module_from_spec
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
from src.plugin_system.core.dependency_manager import dependency_manager
|
from src.plugin_system.core.dependency_manager import dependency_manager
|
||||||
@@ -28,7 +30,7 @@ class PluginManager:
|
|||||||
self.plugin_paths: Dict[str, str] = {} # 记录插件名到目录路径的映射,插件名 -> 目录路径
|
self.plugin_paths: Dict[str, str] = {} # 记录插件名到目录路径的映射,插件名 -> 目录路径
|
||||||
|
|
||||||
self.loaded_plugins: Dict[str, PluginBase] = {} # 已加载的插件类实例注册表,插件名 -> 插件类实例
|
self.loaded_plugins: Dict[str, PluginBase] = {} # 已加载的插件类实例注册表,插件名 -> 插件类实例
|
||||||
self.failed_plugins: Dict[str, str] = {} # 记录加载失败的插件类及其错误信息,插件名 -> 错误信息
|
self.failed_plugins: Dict[str, str] = {} # 记录加载失败的插件文件及其错误信息,插件名 -> 错误信息
|
||||||
|
|
||||||
# 确保插件目录存在
|
# 确保插件目录存在
|
||||||
self._ensure_plugin_directories()
|
self._ensure_plugin_directories()
|
||||||
@@ -107,13 +109,9 @@ class PluginManager:
|
|||||||
# 使用记录的插件目录路径
|
# 使用记录的插件目录路径
|
||||||
plugin_dir = self.plugin_paths.get(plugin_name)
|
plugin_dir = self.plugin_paths.get(plugin_name)
|
||||||
|
|
||||||
# 如果没有记录,则尝试查找(fallback)
|
# 如果没有记录,直接返回失败
|
||||||
if not plugin_dir:
|
if not plugin_dir:
|
||||||
plugin_dir = self._find_plugin_directory(plugin_class)
|
return False, 1
|
||||||
if plugin_dir:
|
|
||||||
self.plugin_paths[plugin_name] = plugin_dir # 更新路径
|
|
||||||
else:
|
|
||||||
return False, 1
|
|
||||||
|
|
||||||
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件(可能因为缺少manifest而失败)
|
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件(可能因为缺少manifest而失败)
|
||||||
if not plugin_instance:
|
if not plugin_instance:
|
||||||
@@ -360,24 +358,14 @@ class PluginManager:
|
|||||||
|
|
||||||
logger.debug(f"正在扫描插件根目录: {directory}")
|
logger.debug(f"正在扫描插件根目录: {directory}")
|
||||||
|
|
||||||
# 遍历目录中的所有Python文件和包
|
# 遍历目录中的所有包
|
||||||
for item in os.listdir(directory):
|
for item in os.listdir(directory):
|
||||||
item_path = os.path.join(directory, item)
|
item_path = os.path.join(directory, item)
|
||||||
|
|
||||||
if os.path.isfile(item_path) and item.endswith(".py") and item != "__init__.py":
|
if os.path.isdir(item_path) and not item.startswith(".") and not item.startswith("__"):
|
||||||
# 单文件插件
|
|
||||||
plugin_name = Path(item_path).stem
|
|
||||||
if self._load_plugin_module_file(item_path, plugin_name, directory):
|
|
||||||
loaded_count += 1
|
|
||||||
else:
|
|
||||||
failed_count += 1
|
|
||||||
|
|
||||||
elif os.path.isdir(item_path) and not item.startswith(".") and not item.startswith("__"):
|
|
||||||
# 插件包
|
|
||||||
plugin_file = os.path.join(item_path, "plugin.py")
|
plugin_file = os.path.join(item_path, "plugin.py")
|
||||||
if os.path.exists(plugin_file):
|
if os.path.exists(plugin_file):
|
||||||
plugin_name = item # 使用目录名作为插件名
|
if self._load_plugin_module_file(plugin_file):
|
||||||
if self._load_plugin_module_file(plugin_file, plugin_name, item_path):
|
|
||||||
loaded_count += 1
|
loaded_count += 1
|
||||||
else:
|
else:
|
||||||
failed_count += 1
|
failed_count += 1
|
||||||
@@ -387,14 +375,16 @@ class PluginManager:
|
|||||||
def _find_plugin_directory(self, plugin_class: Type[PluginBase]) -> Optional[str]:
|
def _find_plugin_directory(self, plugin_class: Type[PluginBase]) -> Optional[str]:
|
||||||
"""查找插件类对应的目录路径"""
|
"""查找插件类对应的目录路径"""
|
||||||
try:
|
try:
|
||||||
module = getmodule(plugin_class)
|
# module = getmodule(plugin_class)
|
||||||
if module and hasattr(module, "__file__") and module.__file__:
|
# if module and hasattr(module, "__file__") and module.__file__:
|
||||||
return os.path.dirname(module.__file__)
|
# return os.path.dirname(module.__file__)
|
||||||
|
file_path = inspect.getfile(plugin_class)
|
||||||
|
return os.path.dirname(file_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"通过inspect获取插件目录失败: {e}")
|
logger.debug(f"通过inspect获取插件目录失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _load_plugin_module_file(self, plugin_file: str, plugin_name: str, plugin_dir: str) -> bool:
|
def _load_plugin_module_file(self, plugin_file: str) -> bool:
|
||||||
# sourcery skip: extract-method
|
# sourcery skip: extract-method
|
||||||
"""加载单个插件模块文件
|
"""加载单个插件模块文件
|
||||||
|
|
||||||
@@ -405,12 +395,7 @@ class PluginManager:
|
|||||||
"""
|
"""
|
||||||
# 生成模块名
|
# 生成模块名
|
||||||
plugin_path = Path(plugin_file)
|
plugin_path = Path(plugin_file)
|
||||||
if plugin_path.parent.name != "plugins":
|
module_name = ".".join(plugin_path.parent.parts)
|
||||||
# 插件包格式:parent_dir.plugin
|
|
||||||
module_name = f"plugins.{plugin_path.parent.name}.plugin"
|
|
||||||
else:
|
|
||||||
# 单文件格式:plugins.filename
|
|
||||||
module_name = f"plugins.{plugin_path.stem}"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 动态导入插件模块
|
# 动态导入插件模块
|
||||||
@@ -422,16 +407,13 @@ class PluginManager:
|
|||||||
module = module_from_spec(spec)
|
module = module_from_spec(spec)
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
# 记录插件名和目录路径的映射
|
|
||||||
self.plugin_paths[plugin_name] = plugin_dir
|
|
||||||
|
|
||||||
logger.debug(f"插件模块加载成功: {plugin_file}")
|
logger.debug(f"插件模块加载成功: {plugin_file}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"加载插件模块 {plugin_file} 失败: {e}"
|
error_msg = f"加载插件模块 {plugin_file} 失败: {e}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
self.failed_plugins[plugin_name] = error_msg
|
self.failed_plugins[module_name] = error_msg
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]:
|
def _check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]:
|
||||||
|
|||||||
Reference in New Issue
Block a user