diff --git a/changes.md b/changes.md index 7d4f2ae8f..b776991de 100644 --- a/changes.md +++ b/changes.md @@ -23,6 +23,8 @@ 6. 增加了插件和组件管理的API。 7. `BaseCommand`的`execute`方法现在返回一个三元组,包含是否执行成功、可选的回复消息和是否拦截消息。 - 这意味着你终于可以动态控制是否继续后续消息的处理了。 +8. 移除了dependency_manager,但是依然保留了`python_dependencies`属性,等待后续重构。 + - 一并移除了文档有关manager的内容。 # 插件系统修改 1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)** diff --git a/docs/plugins/dependency-management.md b/docs/plugins/dependency-management.md index 9b9695846..4bb4ed000 100644 --- a/docs/plugins/dependency-management.md +++ b/docs/plugins/dependency-management.md @@ -1,93 +1,6 @@ # 📦 插件依赖管理系统 -> 🎯 **简介**:MaiBot插件系统提供了强大的Python包依赖管理功能,让插件开发更加便捷和可靠。 - -## ✨ 功能概述 - -### 🎯 核心能力 -- **声明式依赖**:插件可以明确声明需要的Python包 -- **智能检查**:自动检查依赖包的安装状态 -- **版本控制**:精确的版本要求管理 -- **可选依赖**:区分必需依赖和可选依赖 -- **自动安装**:可选的自动安装功能 -- **批量管理**:生成统一的requirements文件 -- **安全控制**:防止意外安装和版本冲突 - -### 🔄 工作流程 -1. **声明依赖** → 在插件中声明所需的Python包 -2. **加载检查** → 插件加载时自动检查依赖状态 -3. **状态报告** → 详细报告缺失或版本不匹配的依赖 -4. **智能安装** → 可选择自动安装或手动安装 -5. **运行时处理** → 插件运行时优雅处理依赖缺失 - -## 🚀 快速开始 - -### 步骤1:声明依赖 - -在你的插件类中添加`python_dependencies`字段: - -```python -from src.plugin_system import BasePlugin, PythonDependency, register_plugin - -@register_plugin -class MyPlugin(BasePlugin): - name = "my_plugin" - - # 声明Python包依赖 - python_dependencies = [ - PythonDependency( - package_name="requests", - version=">=2.25.0", - description="HTTP请求库,用于网络通信" - ), - PythonDependency( - package_name="numpy", - version=">=1.20.0", - optional=True, - description="数值计算库(可选功能)" - ), - ] - - def get_plugin_components(self): - # 返回插件组件 - return [] -``` - -### 步骤2:处理依赖 - -在组件代码中优雅处理依赖缺失: - -```python -class MyAction(BaseAction): - async def execute(self, action_input, context=None): - try: - import requests - # 使用requests进行网络请求 - response = requests.get("https://api.example.com") - return {"status": "success", "data": response.json()} - except ImportError: - return { - "status": "error", - "message": "功能不可用:缺少requests库", - "hint": "请运行: pip install requests>=2.25.0" - } -``` - -### 步骤3:检查和管理 - -使用依赖管理API: - -```python -from src.plugin_system import plugin_manager - -# 检查所有插件的依赖状态 -result = plugin_manager.check_all_dependencies() -print(f"检查了 {result['total_plugins_checked']} 个插件") -print(f"缺少必需依赖的插件: {result['plugins_with_missing_required']} 个") - -# 生成requirements文件 -plugin_manager.generate_plugin_requirements("plugin_requirements.txt") -``` +现在的Python依赖包管理依然存在问题,请保留你的`python_dependencies`属性,等待后续重构。 ## 📚 详细教程 @@ -97,11 +10,11 @@ plugin_manager.generate_plugin_requirements("plugin_requirements.txt") ```python PythonDependency( - package_name="requests", # 导入时的包名 - version=">=2.25.0", # 版本要求 - optional=False, # 是否为可选依赖 - description="HTTP请求库", # 依赖描述 - install_name="" # pip安装时的包名(可选) + package_name="PIL", # 导入时的包名 + version=">=11.2.0", # 版本要求 + optional=False, # 是否为可选依赖 + description="图像处理库", # 依赖描述 + install_name="pillow" # pip安装时的包名(可选) ) ``` @@ -110,10 +23,10 @@ PythonDependency( | 参数 | 类型 | 必需 | 说明 | |------|------|------|------| | `package_name` | str | ✅ | Python导入时使用的包名(如`requests`) | -| `version` | str | ❌ | 版本要求,支持pip格式(如`>=1.0.0`, `==2.1.3`) | +| `version` | str | ❌ | 版本要求,使用pip格式(如`>=1.0.0`, `==2.1.3`) | | `optional` | bool | ❌ | 是否为可选依赖,默认`False` | | `description` | str | ❌ | 依赖的用途描述 | -| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同 | +| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同,用于处理安装名称和导入名称不一致的情况 | #### 版本格式示例 @@ -125,201 +38,3 @@ PythonDependency("pillow", "==8.3.2") # 精确版本 PythonDependency("scipy", ">=1.7.0,!=1.8.0") # 排除特定版本 ``` -#### 特殊情况处理 - -**导入名与安装名不同的包:** - -```python -PythonDependency( - package_name="PIL", # import PIL - install_name="Pillow", # pip install Pillow - version=">=8.0.0" -) -``` - -**可选依赖示例:** - -```python -python_dependencies = [ - # 必需依赖 - 核心功能 - PythonDependency( - package_name="requests", - version=">=2.25.0", - description="HTTP库,插件核心功能必需" - ), - - # 可选依赖 - 增强功能 - PythonDependency( - package_name="numpy", - version=">=1.20.0", - optional=True, - description="数值计算库,用于高级数学运算" - ), - PythonDependency( - package_name="matplotlib", - version=">=3.0.0", - optional=True, - description="绘图库,用于数据可视化功能" - ), -] -``` - -### 依赖检查机制 - -系统在以下时机会自动检查依赖: - -1. **插件加载时**:检查插件声明的所有依赖 -2. **手动调用时**:通过API主动检查 -3. **运行时检查**:在组件执行时动态检查 - -#### 检查结果状态 - -| 状态 | 描述 | 处理建议 | -|------|------|----------| -| `no_dependencies` | 插件未声明任何依赖 | 无需处理 | -| `ok` | 所有依赖都已满足 | 正常使用 | -| `missing_optional` | 缺少可选依赖 | 部分功能不可用,考虑安装 | -| `missing_required` | 缺少必需依赖 | 插件功能受限,需要安装 | - -## 🎯 最佳实践 - -### 1. 依赖声明原则 - -#### ✅ 推荐做法 - -```python -python_dependencies = [ - # 明确的版本要求 - PythonDependency( - package_name="requests", - version=">=2.25.0,<3.0.0", # 主版本兼容 - description="HTTP请求库,用于API调用" - ), - - # 合理的可选依赖 - PythonDependency( - package_name="numpy", - version=">=1.20.0", - optional=True, - description="数值计算库,用于数据处理功能" - ), -] -``` - -#### ❌ 避免的做法 - -```python -python_dependencies = [ - # 过于宽泛的版本要求 - PythonDependency("requests"), # 没有版本限制 - - # 过于严格的版本要求 - PythonDependency("numpy", "==1.21.0"), # 精确版本过于严格 - - # 缺少描述 - PythonDependency("matplotlib", ">=3.0.0"), # 没有说明用途 -] -``` - -### 2. 错误处理模式 - -#### 优雅降级模式 - -```python -class SmartAction(BaseAction): - async def execute(self, action_input, context=None): - # 检查可选依赖 - try: - import numpy as np - # 使用numpy的高级功能 - return await self._advanced_processing(action_input, np) - except ImportError: - # 降级到基础功能 - return await self._basic_processing(action_input) - - async def _advanced_processing(self, input_data, np): - """使用numpy的高级处理""" - result = np.array(input_data).mean() - return {"result": result, "method": "advanced"} - - async def _basic_processing(self, input_data): - """基础处理(不依赖外部库)""" - result = sum(input_data) / len(input_data) - return {"result": result, "method": "basic"} -``` - -## 🔧 使用API - -### 检查依赖状态 - -```python -from src.plugin_system import plugin_manager - -# 检查所有插件依赖(仅检查,不安装) -result = plugin_manager.check_all_dependencies(auto_install=False) - -# 检查并自动安装缺失的必需依赖 -result = plugin_manager.check_all_dependencies(auto_install=True) -``` - -### 生成requirements文件 - -```python -# 生成包含所有插件依赖的requirements文件 -plugin_manager.generate_plugin_requirements("plugin_requirements.txt") -``` - -### 获取依赖状态报告 - -```python -# 获取详细的依赖检查报告 -result = plugin_manager.check_all_dependencies() -for plugin_name, status in result['plugin_status'].items(): - print(f"插件 {plugin_name}: {status['status']}") - if status['missing']: - print(f" 缺失必需依赖: {status['missing']}") - if status['optional_missing']: - print(f" 缺失可选依赖: {status['optional_missing']}") -``` - -## 🛡️ 安全考虑 - -### 1. 自动安装控制 -- 🛡️ **默认手动**: 自动安装默认关闭,需要明确启用 -- 🔍 **依赖审查**: 安装前会显示将要安装的包列表 -- ⏱️ **超时控制**: 安装操作有超时限制(5分钟) - -### 2. 权限管理 -- 📁 **环境隔离**: 推荐在虚拟环境中使用 -- 🔒 **版本锁定**: 支持精确的版本控制 -- 📝 **安装日志**: 记录所有安装操作 - -## 📊 故障排除 - -### 常见问题 - -1. **依赖检查失败** - ```python - # 手动检查包是否可导入 - try: - import package_name - print("包可用") - except ImportError: - print("包不可用,需要安装") - ``` - -2. **版本冲突** - ```python - # 检查已安装的包版本 - import package_name - print(f"当前版本: {package_name.__version__}") - ``` - -3. **安装失败** - ```python - # 查看安装日志 - from src.plugin_system import dependency_manager - result = dependency_manager.get_install_summary() - print("安装日志:", result['install_log']) - print("失败详情:", result['failed_installs']) - ``` diff --git a/src/plugin_system/core/__init__.py b/src/plugin_system/core/__init__.py index 3193828bf..eb794a30b 100644 --- a/src/plugin_system/core/__init__.py +++ b/src/plugin_system/core/__init__.py @@ -6,14 +6,12 @@ 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.dependency_manager import dependency_manager from src.plugin_system.core.events_manager import events_manager from src.plugin_system.core.global_announcement_manager import global_announcement_manager __all__ = [ "plugin_manager", "component_registry", - "dependency_manager", "events_manager", "global_announcement_manager", ] diff --git a/src/plugin_system/core/dependency_manager.py b/src/plugin_system/core/dependency_manager.py deleted file mode 100644 index 266254e72..000000000 --- a/src/plugin_system/core/dependency_manager.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -插件依赖管理器 - -负责检查和安装插件的Python包依赖 -""" - -import subprocess -import sys -import importlib -from typing import List, Dict, Tuple, Any - -from src.common.logger import get_logger -from src.plugin_system.base.component_types import PythonDependency - -logger = get_logger("dependency_manager") - - -class DependencyManager: - """依赖管理器""" - - def __init__(self): - self.install_log: List[str] = [] - self.failed_installs: Dict[str, str] = {} - - def check_dependencies( - self, dependencies: List[PythonDependency] - ) -> Tuple[List[PythonDependency], List[PythonDependency]]: - """检查依赖包状态 - - Args: - dependencies: 依赖包列表 - - Returns: - Tuple[List[PythonDependency], List[PythonDependency]]: (缺失的依赖, 可选缺失的依赖) - """ - missing_required = [] - missing_optional = [] - - for dep in dependencies: - if self._is_package_available(dep.package_name): - logger.debug(f"依赖包已存在: {dep.package_name}") - elif dep.optional: - missing_optional.append(dep) - logger.warning(f"可选依赖包缺失: {dep.package_name} - {dep.description}") - else: - missing_required.append(dep) - logger.error(f"必需依赖包缺失: {dep.package_name} - {dep.description}") - return missing_required, missing_optional - - def _is_package_available(self, package_name: str) -> bool: - """检查包是否可用""" - try: - importlib.import_module(package_name) - return True - except ImportError: - return False - - def install_dependencies(self, dependencies: List[PythonDependency], auto_install: bool = False) -> bool: - """安装依赖包 - - Args: - dependencies: 需要安装的依赖包列表 - auto_install: 是否自动安装(True时不询问用户) - - Returns: - bool: 安装是否成功 - """ - if not dependencies: - return True - - logger.info(f"需要安装 {len(dependencies)} 个依赖包") - - # 显示将要安装的包 - for dep in dependencies: - install_cmd = dep.get_pip_requirement() - logger.info(f" - {install_cmd} {'(可选)' if dep.optional else '(必需)'}") - if dep.description: - logger.info(f" 说明: {dep.description}") - - if not auto_install: - # 这里可以添加用户确认逻辑 - logger.warning("手动安装模式:请手动运行 pip install 命令安装依赖包") - return False - - # 执行安装 - success_count = 0 - for dep in dependencies: - if self._install_single_package(dep): - success_count += 1 - else: - self.failed_installs[dep.package_name] = f"安装失败: {dep.get_pip_requirement()}" - - logger.info(f"依赖安装完成: {success_count}/{len(dependencies)} 个成功") - return success_count == len(dependencies) - - def _install_single_package(self, dependency: PythonDependency) -> bool: - """安装单个包""" - pip_requirement = dependency.get_pip_requirement() - - try: - logger.info(f"正在安装: {pip_requirement}") - - # 使用subprocess安装包 - cmd = [sys.executable, "-m", "pip", "install", pip_requirement] - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=300, # 5分钟超时 - ) - - if result.returncode == 0: - logger.info(f"✅ 成功安装: {pip_requirement}") - self.install_log.append(f"成功安装: {pip_requirement}") - return True - else: - logger.error(f"❌ 安装失败: {pip_requirement}") - logger.error(f"错误输出: {result.stderr}") - self.install_log.append(f"安装失败: {pip_requirement} - {result.stderr}") - return False - - except subprocess.TimeoutExpired: - logger.error(f"❌ 安装超时: {pip_requirement}") - return False - except Exception as e: - logger.error(f"❌ 安装异常: {pip_requirement} - {str(e)}") - return False - - def generate_requirements_file( - self, plugins_dependencies: List[List[PythonDependency]], output_path: str = "plugin_requirements.txt" - ) -> bool: - """生成插件依赖的requirements文件 - - Args: - plugins_dependencies: 所有插件的依赖列表 - output_path: 输出文件路径 - - Returns: - bool: 生成是否成功 - """ - try: - all_deps = {} - - # 合并所有插件的依赖 - for plugin_deps in plugins_dependencies: - for dep in plugin_deps: - key = dep.install_name - if key in all_deps: - # 如果已存在,可以添加版本兼容性检查逻辑 - existing = all_deps[key] - if dep.version and existing.version != dep.version: - logger.warning(f"依赖版本冲突: {key} ({existing.version} vs {dep.version})") - else: - all_deps[key] = dep - - # 写入requirements文件 - with open(output_path, "w", encoding="utf-8") as f: - f.write("# 插件依赖包自动生成\n") - f.write("# Auto-generated plugin dependencies\n\n") - - # 按包名排序 - sorted_deps = sorted(all_deps.values(), key=lambda x: x.install_name) - - for dep in sorted_deps: - requirement = dep.get_pip_requirement() - if dep.description: - f.write(f"# {dep.description}\n") - if dep.optional: - f.write("# Optional dependency\n") - f.write(f"{requirement}\n\n") - - logger.info(f"已生成插件依赖文件: {output_path} ({len(all_deps)} 个包)") - return True - - except Exception as e: - logger.error(f"生成requirements文件失败: {str(e)}") - return False - - def get_install_summary(self) -> Dict[str, Any]: - """获取安装摘要""" - return { - "install_log": self.install_log.copy(), - "failed_installs": self.failed_installs.copy(), - "total_attempts": len(self.install_log), - "failed_count": len(self.failed_installs), - } - - -# 全局依赖管理器实例 -dependency_manager = DependencyManager() diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index 8bb005a94..98bce4bdb 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -8,10 +8,9 @@ from pathlib import Path from src.common.logger import get_logger from src.plugin_system.base.plugin_base import PluginBase -from src.plugin_system.base.component_types import ComponentType, PythonDependency +from src.plugin_system.base.component_types import ComponentType from src.plugin_system.utils.manifest_utils import VersionComparator from .component_registry import component_registry -from .dependency_manager import dependency_manager logger = get_logger("plugin_manager") @@ -207,104 +206,6 @@ class PluginManager: """ return self.loaded_plugins.get(plugin_name) - def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, Any]: - """检查所有插件的Python依赖包 - - Args: - auto_install: 是否自动安装缺失的依赖包 - - Returns: - Dict[str, any]: 检查结果摘要 - """ - logger.info("开始检查所有插件的Python依赖包...") - - all_required_missing: List[PythonDependency] = [] - all_optional_missing: List[PythonDependency] = [] - plugin_status = {} - - for plugin_name in self.loaded_plugins: - plugin_info = component_registry.get_plugin_info(plugin_name) - if not plugin_info or not plugin_info.python_dependencies: - plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []} - continue - - logger.info(f"检查插件 {plugin_name} 的依赖...") - - missing_required, missing_optional = dependency_manager.check_dependencies(plugin_info.python_dependencies) - - if missing_required: - all_required_missing.extend(missing_required) - plugin_status[plugin_name] = { - "status": "missing_required", - "missing": [dep.package_name for dep in missing_required], - "optional_missing": [dep.package_name for dep in missing_optional], - } - logger.error(f"插件 {plugin_name} 缺少必需依赖: {[dep.package_name for dep in missing_required]}") - elif missing_optional: - all_optional_missing.extend(missing_optional) - plugin_status[plugin_name] = { - "status": "missing_optional", - "missing": [], - "optional_missing": [dep.package_name for dep in missing_optional], - } - logger.warning(f"插件 {plugin_name} 缺少可选依赖: {[dep.package_name for dep in missing_optional]}") - else: - plugin_status[plugin_name] = {"status": "ok", "missing": []} - logger.info(f"插件 {plugin_name} 依赖检查通过") - - # 汇总结果 - total_missing = len({dep.package_name for dep in all_required_missing}) - total_optional_missing = len({dep.package_name for dep in all_optional_missing}) - - logger.info(f"依赖检查完成 - 缺少必需包: {total_missing}个, 缺少可选包: {total_optional_missing}个") - - # 如果需要自动安装 - install_success = True - if auto_install and all_required_missing: - unique_required = {dep.package_name: dep for dep in all_required_missing} - logger.info(f"开始自动安装 {len(unique_required)} 个必需依赖包...") - install_success = dependency_manager.install_dependencies(list(unique_required.values()), auto_install=True) - - return { - "total_plugins_checked": len(plugin_status), - "plugins_with_missing_required": len( - [p for p in plugin_status.values() if p["status"] == "missing_required"] - ), - "plugins_with_missing_optional": len( - [p for p in plugin_status.values() if p["status"] == "missing_optional"] - ), - "total_missing_required": total_missing, - "total_missing_optional": total_optional_missing, - "plugin_status": plugin_status, - "auto_install_attempted": auto_install and bool(all_required_missing), - "auto_install_success": install_success, - "install_summary": dependency_manager.get_install_summary(), - } - - def generate_plugin_requirements(self, output_path: str = "plugin_requirements.txt") -> bool: - """生成所有插件依赖的requirements文件 - - Args: - output_path: 输出文件路径 - - Returns: - bool: 生成是否成功 - """ - logger.info("开始生成插件依赖requirements文件...") - - all_dependencies = [] - - for plugin_name in self.loaded_plugins: - plugin_info = component_registry.get_plugin_info(plugin_name) - if plugin_info and plugin_info.python_dependencies: - all_dependencies.append(plugin_info.python_dependencies) - - if not all_dependencies: - logger.info("没有找到任何插件依赖") - return False - - return dependency_manager.generate_requirements_file(all_dependencies, output_path) - # === 查询方法 === def list_loaded_plugins(self) -> List[str]: """