暴露全部api,解决循环import问题

This commit is contained in:
UnCLAS-Prommer
2025-07-08 00:10:31 +08:00
parent 6633d5e273
commit 36974197a8
14 changed files with 89 additions and 702 deletions

View File

@@ -4,6 +4,9 @@ import os
import inspect
import toml
import json
import shutil
import datetime
from src.common.logger import get_logger
from src.plugin_system.base.component_types import (
PluginInfo,
@@ -11,13 +14,10 @@ from src.plugin_system.base.component_types import (
PythonDependency,
)
from src.plugin_system.base.config_types import ConfigField
from src.plugin_system.core.component_registry import component_registry
from src.plugin_system.utils.manifest_utils import ManifestValidator
logger = get_logger("base_plugin")
# 全局插件类注册表
_plugin_classes: Dict[str, Type["BasePlugin"]] = {}
class BasePlugin(ABC):
"""插件基类
@@ -29,7 +29,7 @@ class BasePlugin(ABC):
"""
# 插件基本信息(子类必须定义)
plugin_name: str = "" # 插件内部标识符(如 "doubao_pic_plugin"
plugin_name: str = "" # 插件内部标识符(如 "hello_world_plugin"
enable_plugin: bool = False # 是否启用插件
dependencies: List[str] = [] # 依赖的其他插件
python_dependencies: List[PythonDependency] = [] # Python包依赖
@@ -103,7 +103,7 @@ class BasePlugin(ABC):
if not self.get_manifest_info("description"):
raise ValueError(f"插件 {self.plugin_name} 的manifest中缺少description字段")
def _load_manifest(self):
def _load_manifest(self): # sourcery skip: raise-from-previous-error
"""加载manifest文件强制要求"""
if not self.plugin_dir:
raise ValueError(f"{self.log_prefix} 没有插件目录路径无法加载manifest")
@@ -124,9 +124,6 @@ class BasePlugin(ABC):
# 验证manifest格式
self._validate_manifest()
# 从manifest覆盖插件基本信息如果插件类中未定义
self._apply_manifest_overrides()
except json.JSONDecodeError as e:
error_msg = f"{self.log_prefix} manifest文件格式错误: {e}"
logger.error(error_msg)
@@ -136,15 +133,6 @@ class BasePlugin(ABC):
logger.error(error_msg)
raise IOError(error_msg) # noqa
def _apply_manifest_overrides(self):
"""从manifest文件覆盖插件信息现在只处理内部标识符的fallback"""
if not self.manifest_data:
return
# 只有当插件类中没有定义plugin_name时才从manifest中获取作为fallback
if not self.plugin_name:
self.plugin_name = self.manifest_data.get("name", "").replace(" ", "_").lower()
def _get_author_name(self) -> str:
"""从manifest获取作者名称"""
author_info = self.get_manifest_info("author", {})
@@ -156,10 +144,7 @@ class BasePlugin(ABC):
def _validate_manifest(self):
"""验证manifest文件格式使用强化的验证器"""
if not self.manifest_data:
return
# 导入验证器
from src.plugin_system.utils.manifest_utils import ManifestValidator
raise ValueError(f"{self.log_prefix} manifest数据为空验证失败")
validator = ManifestValidator()
is_valid = validator.validate_manifest(self.manifest_data)
@@ -176,36 +161,6 @@ class BasePlugin(ABC):
error_msg += f": {'; '.join(validator.validation_errors)}"
raise ValueError(error_msg)
def _generate_default_manifest(self, manifest_path: str):
"""生成默认的manifest文件"""
if not self.plugin_name:
logger.debug(f"{self.log_prefix} 插件名称未定义无法生成默认manifest")
return
# 从plugin_name生成友好的显示名称
display_name = self.plugin_name.replace("_", " ").title()
default_manifest = {
"manifest_version": 1,
"name": display_name,
"version": "1.0.0",
"description": "插件描述",
"author": {"name": "Unknown", "url": ""},
"license": "MIT",
"host_application": {"min_version": "1.0.0", "max_version": "4.0.0"},
"keywords": [],
"categories": [],
"default_locale": "zh-CN",
"locales_path": "_locales",
}
try:
with open(manifest_path, "w", encoding="utf-8") as f:
json.dump(default_manifest, f, ensure_ascii=False, indent=2)
logger.info(f"{self.log_prefix} 已生成默认manifest文件: {manifest_path}")
except IOError as e:
logger.error(f"{self.log_prefix} 保存默认manifest文件失败: {e}")
def get_manifest_info(self, key: str, default: Any = None) -> Any:
"""获取manifest信息
@@ -304,9 +259,6 @@ class BasePlugin(ABC):
def _backup_config_file(self, config_file_path: str) -> str:
"""备份配置文件"""
import shutil
import datetime
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{config_file_path}.backup_{timestamp}"
@@ -377,13 +329,14 @@ class BasePlugin(ABC):
logger.warning(f"{self.log_prefix} 配置节 {section_name} 结构已改变,使用默认值")
# 检查旧配置中是否有新配置没有的节
for section_name in old_config.keys():
for section_name in old_config:
if section_name not in migrated_config:
logger.warning(f"{self.log_prefix} 配置节 {section_name} 在新版本中已被移除")
return migrated_config
def _generate_config_from_schema(self) -> Dict[str, Any]:
# sourcery skip: dict-comprehension
"""根据schema生成配置数据结构不写入文件"""
if not self.config_schema:
return {}
@@ -473,7 +426,7 @@ class BasePlugin(ABC):
except IOError as e:
logger.error(f"{self.log_prefix} 保存配置文件失败: {e}", exc_info=True)
def _load_plugin_config(self):
def _load_plugin_config(self): # sourcery skip: extract-method
"""加载插件配置文件,支持版本检查和自动迁移"""
if not self.config_file_name:
logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载")
@@ -568,7 +521,7 @@ class BasePlugin(ABC):
def register_plugin(self) -> bool:
"""注册插件及其所有组件"""
from src.plugin_system.core.component_registry import component_registry
components = self.get_plugin_components()
# 检查依赖
@@ -598,6 +551,7 @@ class BasePlugin(ABC):
def _check_dependencies(self) -> bool:
"""检查插件依赖"""
from src.plugin_system.core.component_registry import component_registry
if not self.dependencies:
return True
@@ -629,52 +583,3 @@ class BasePlugin(ABC):
return default
return current
def register_plugin(cls):
"""插件注册装饰器
用法:
@register_plugin
class MyPlugin(BasePlugin):
plugin_name = "my_plugin"
plugin_description = "我的插件"
...
"""
if not issubclass(cls, BasePlugin):
logger.error(f"{cls.__name__} 不是 BasePlugin 的子类")
return cls
# 只是注册插件类,不立即实例化
# 插件管理器会负责实例化和注册
plugin_name = cls.plugin_name or cls.__name__
_plugin_classes[plugin_name] = cls
logger.debug(f"插件类已注册: {plugin_name}")
return cls
def get_registered_plugin_classes() -> Dict[str, Type["BasePlugin"]]:
"""获取所有已注册的插件类"""
return _plugin_classes.copy()
def instantiate_and_register_plugin(plugin_class: Type["BasePlugin"], plugin_dir: str = None) -> bool:
"""实例化并注册插件
Args:
plugin_class: 插件类
plugin_dir: 插件目录路径
Returns:
bool: 是否成功
"""
try:
plugin_instance = plugin_class(plugin_dir=plugin_dir)
return plugin_instance.register_plugin()
except Exception as e:
logger.error(f"注册插件 {plugin_class.__name__} 时出错: {e}")
import traceback
logger.error(traceback.format_exc())
return False