From 23f9a6dde4dbb7417179b7776aadff4dc3469fe2 Mon Sep 17 00:00:00 2001 From: Snowykami Date: Sat, 14 Dec 2024 03:32:56 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=B7=BB=E5=8A=A0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=8A=A0=E8=BD=BD=E6=A8=A1=E5=9D=97=E5=8F=8A=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=A8=A1=E5=9E=8B=EF=BC=8C=E6=94=AF=E6=8C=81=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E5=8A=A8=E6=80=81=E5=8A=A0=E8=BD=BD=E4=B8=8E?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit/check_filename.py | 0 nonebot_plugin_marshoai/tool/__init__.py | 0 nonebot_plugin_marshoai/tool/load.py | 93 ++++++++++++++++++++++++ nonebot_plugin_marshoai/tool/models.py | 56 ++++++++++++++ nonebot_plugin_marshoai/tool/utils.py | 16 ++++ pyproject.toml | 3 +- 6 files changed, 167 insertions(+), 1 deletion(-) mode change 100644 => 100755 .pre-commit/check_filename.py create mode 100755 nonebot_plugin_marshoai/tool/__init__.py create mode 100755 nonebot_plugin_marshoai/tool/load.py create mode 100644 nonebot_plugin_marshoai/tool/models.py create mode 100644 nonebot_plugin_marshoai/tool/utils.py diff --git a/.pre-commit/check_filename.py b/.pre-commit/check_filename.py old mode 100644 new mode 100755 diff --git a/nonebot_plugin_marshoai/tool/__init__.py b/nonebot_plugin_marshoai/tool/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/nonebot_plugin_marshoai/tool/load.py b/nonebot_plugin_marshoai/tool/load.py new file mode 100755 index 00000000..1549841d --- /dev/null +++ b/nonebot_plugin_marshoai/tool/load.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved +本模块为工具加载模块 +""" +import os +import traceback +from importlib import import_module +from pathlib import Path +from typing import Optional + +from nonebot import logger + +from .models import Plugin, PluginMetadata + +from .utils import path_to_module_name + +_plugins: dict[str, Plugin] = {} + +__all__ = [ + "load_plugin", + "load_plugins", + "_plugins", +] + + +def load_plugin(module_path: str | Path) -> Optional[Plugin]: + """加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。 + 该函数产生的副作用在于将插件加载到 `_plugins` 中。 + + Args: + module_path: 插件名称 `path.to.your.plugin` + 或插件路径 `pathlib.Path(path/to/your/plugin)` + Returns: + Optional[Plugin]: 插件对象 + """ + module_path = ( + path_to_module_name(Path(module_path)) + if isinstance(module_path, Path) + else module_path + ) + try: + module = import_module(module_path) # 导入模块对象 + plugin = Plugin( + name=module.__name__, + module=module, + module_name=module_path, + ) + + plugin.metadata = getattr(module, "__marsho_meta__", None) + + _plugins[plugin.name] = plugin + + logger.opt(colors=True).success( + f'Succeeded to load liteyuki plugin "{plugin.name}"' + ) + return _plugins[module.__name__] + + except Exception as e: + logger.opt(colors=True).success( + f'Failed to load liteyuki plugin "{module_path}"' + ) + traceback.print_exc() + return None + + +def load_plugins(*plugin_dirs: str) -> set[Plugin]: + """导入文件夹下多个插件 + + 参数: + plugin_dir: 文件夹路径 + ignore_warning: 是否忽略警告,通常是目录不存在或目录为空 + 返回: + set[Plugin]: 插件集合 + """ + plugins = set() + for plugin_dir in plugin_dirs: + for f in os.listdir(plugin_dir): + path = Path(os.path.join(plugin_dir, f)) + + module_name = None + + if os.path.isfile(path) and f.endswith(".py"): + """单文件加载""" + module_name = f"{path_to_module_name(Path(plugin_dir))}.{f[:-3]}" + + elif os.path.isdir(path) and os.path.exists(os.path.join(path, "__init__.py")): + """包加载""" + module_name = path_to_module_name(path) + + if module_name and (plugin := load_plugin(module_name)): + plugins.add(plugin) + return plugins \ No newline at end of file diff --git a/nonebot_plugin_marshoai/tool/models.py b/nonebot_plugin_marshoai/tool/models.py new file mode 100644 index 00000000..eae2da16 --- /dev/null +++ b/nonebot_plugin_marshoai/tool/models.py @@ -0,0 +1,56 @@ +from types import ModuleType +from typing import Any +from pydantic import BaseModel + + +class Plugin(BaseModel): + """ + 存储插件信息 + + Attributes: + ---------- + name: str + 包名称 例如marsho_test + module: ModuleType + 插件模块对象 + module_name: str + 点分割模块路径 例如a.b.c + metadata: "PluginMeta" | None + 元 + """ + name: str + """包名称 例如marsho_test""" + module: ModuleType + """插件模块对象""" + module_name: str + """点分割模块路径 例如a.b.c""" + metadata: "PluginMetadata" | None = None + """元""" + +class PluginMetadata(BaseModel): + """ + Marsho 插件 对象元数据 + Attributes: + ---------- + + name: str + 友好名称: 例如Marsho Test + description: str + 插件描述 + usage: str + 插件使用方法 + type: str + 插件类型 + author: str + 插件作者 + homepage: str + 插件主页 + extra: dict[str, Any] + 额外信息,自定义键值对 + """ + name: str + description: str = "" + usage: str = "" + author: str = "" + homepage: str = "" + extra: dict[str, Any] = {} \ No newline at end of file diff --git a/nonebot_plugin_marshoai/tool/utils.py b/nonebot_plugin_marshoai/tool/utils.py new file mode 100644 index 00000000..790eb0a4 --- /dev/null +++ b/nonebot_plugin_marshoai/tool/utils.py @@ -0,0 +1,16 @@ +from pathlib import Path + + +def path_to_module_name(path: Path) -> str: + """ + 转换路径为模块名 + Args: + path: 路径a/b/c/d -> a.b.c.d + Returns: + str: 模块名 + """ + rel_path = path.resolve().relative_to(Path.cwd().resolve()) + if rel_path.stem == "__init__": + return ".".join(rel_path.parts[:-1]) + else: + return ".".join(rel_path.parts[:-1] + (rel_path.stem,)) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3eb4b55b..ef4e9d68 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,8 @@ dependencies = [ "ruamel.yaml>=0.18.6", "pyyaml>=6.0.2", "psutil>=6.1.0", - "beautifulsoup4>=4.12.3" + "beautifulsoup4>=4.12.3", + "pydantic>=2.10.3" ] license = { text = "MIT, Mulan PSL v2" }