feat:插件依赖管理(如管

This commit is contained in:
SengokuCola
2025-06-15 23:53:23 +08:00
parent 16c0dd1b9a
commit 2fce679aa4
9 changed files with 1214 additions and 4 deletions

View File

@@ -0,0 +1,337 @@
# 📦 插件依赖管理系统
> 🎯 **简介**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):
plugin_name = "my_plugin"
plugin_description = "我的示例插件"
# 声明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")
```
## 📚 详细教程
### PythonDependency 类详解
`PythonDependency`是依赖声明的核心类:
```python
PythonDependency(
package_name="requests", # 导入时的包名
version=">=2.25.0", # 版本要求
optional=False, # 是否为可选依赖
description="HTTP请求库", # 依赖描述
install_name="" # pip安装时的包名可选
)
```
#### 参数说明
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `package_name` | str | ✅ | Python导入时使用的包名`requests` |
| `version` | str | ❌ | 版本要求支持pip格式`>=1.0.0`, `==2.1.3` |
| `optional` | bool | ❌ | 是否为可选依赖,默认`False` |
| `description` | str | ❌ | 依赖的用途描述 |
| `install_name` | str | ❌ | pip安装时的包名默认与`package_name`相同 |
#### 版本格式示例
```python
# 常用版本格式
PythonDependency("requests", ">=2.25.0") # 最小版本
PythonDependency("numpy", ">=1.20.0,<2.0.0") # 版本范围
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'])
```
## 🔗 相关文档
- [🚀 快速开始指南](quick-start.md) - 创建你的第一个插件
- [⚡ Action组件详解](action-components.md) - Action开发指南
- [💻 Command组件详解](command-components.md) - Command开发指南
- [📋 开发规范](development-standards.md) - 代码规范和最佳实践
---
通过依赖管理系统,你的插件将更加健壮和易于维护。开始使用这些功能让你的插件开发更加高效吧! 🚀

View File

@@ -0,0 +1,477 @@
# 📦 依赖管理完整示例
> 这个示例展示了如何在插件中正确使用Python依赖管理功能。
## 🎯 示例插件:智能数据分析插件
这个插件展示了如何处理必需依赖、可选依赖,以及优雅降级处理。
```python
"""
智能数据分析插件
展示依赖管理的完整用法
"""
from src.plugin_system import (
BasePlugin,
BaseAction,
register_plugin,
ActionInfo,
PythonDependency,
ActionActivationType
)
from src.common.logger import get_logger
logger = get_logger("data_analysis_plugin")
@register_plugin
class DataAnalysisPlugin(BasePlugin):
"""智能数据分析插件"""
plugin_name = "data_analysis_plugin"
plugin_description = "提供数据分析和可视化功能的示例插件"
plugin_version = "1.0.0"
plugin_author = "MaiBot Team"
# 声明Python包依赖
python_dependencies = [
# 必需依赖 - 核心功能
PythonDependency(
package_name="requests",
version=">=2.25.0",
description="HTTP库用于获取外部数据"
),
# 可选依赖 - 数据处理
PythonDependency(
package_name="pandas",
version=">=1.3.0",
optional=True,
description="数据处理库,提供高级数据操作功能"
),
# 可选依赖 - 数值计算
PythonDependency(
package_name="numpy",
version=">=1.20.0",
optional=True,
description="数值计算库,用于数学运算"
),
# 可选依赖 - 数据可视化
PythonDependency(
package_name="matplotlib",
version=">=3.3.0",
optional=True,
description="绘图库,用于生成数据图表"
),
# 特殊情况:导入名与安装名不同
PythonDependency(
package_name="PIL",
install_name="Pillow",
version=">=8.0.0",
optional=True,
description="图像处理库,用于图表保存和处理"
),
]
def get_plugin_components(self):
"""返回插件组件"""
return [
# 基础数据获取只依赖requests
(ActionInfo(
name="fetch_data_action",
description="获取外部数据",
focus_activation_type=ActionActivationType.KEYWORD,
normal_activation_type=ActionActivationType.KEYWORD,
activation_keywords=["获取数据", "下载数据"],
), FetchDataAction),
# 数据分析依赖pandas和numpy
(ActionInfo(
name="analyze_data_action",
description="数据分析和统计",
focus_activation_type=ActionActivationType.KEYWORD,
normal_activation_type=ActionActivationType.KEYWORD,
activation_keywords=["分析数据", "数据统计"],
), AnalyzeDataAction),
# 数据可视化依赖matplotlib
(ActionInfo(
name="visualize_data_action",
description="数据可视化",
focus_activation_type=ActionActivationType.KEYWORD,
normal_activation_type=ActionActivationType.KEYWORD,
activation_keywords=["数据图表", "可视化"],
), VisualizeDataAction),
]
class FetchDataAction(BaseAction):
"""数据获取Action - 仅依赖必需的requests库"""
async def execute(self, action_input, context=None):
"""获取外部数据"""
try:
import requests
# 模拟数据获取
url = action_input.get("url", "https://api.github.com/users/octocat")
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
return {
"status": "success",
"message": f"成功获取数据,响应大小: {len(str(data))} 字符",
"data": data,
"capabilities": ["basic_fetch"]
}
except ImportError:
return {
"status": "error",
"message": "缺少必需依赖requests库",
"hint": "请运行: pip install requests>=2.25.0",
"error_code": "MISSING_DEPENDENCY"
}
except Exception as e:
return {
"status": "error",
"message": f"数据获取失败: {str(e)}",
"error_code": "FETCH_ERROR"
}
class AnalyzeDataAction(BaseAction):
"""数据分析Action - 支持多级功能降级"""
async def execute(self, action_input, context=None):
"""分析数据,支持功能降级"""
# 检查可用的依赖
has_pandas = self._check_dependency("pandas")
has_numpy = self._check_dependency("numpy")
# 获取输入数据
data = action_input.get("data", [1, 2, 3, 4, 5])
if has_pandas and has_numpy:
return await self._advanced_analysis(data)
elif has_numpy:
return await self._numpy_analysis(data)
else:
return await self._basic_analysis(data)
def _check_dependency(self, package_name):
"""检查依赖是否可用"""
try:
__import__(package_name)
return True
except ImportError:
return False
async def _advanced_analysis(self, data):
"""高级分析使用pandas + numpy"""
import pandas as pd
import numpy as np
# 转换为DataFrame
df = pd.DataFrame({"values": data})
# 高级统计分析
stats = {
"count": len(df),
"mean": df["values"].mean(),
"median": df["values"].median(),
"std": df["values"].std(),
"min": df["values"].min(),
"max": df["values"].max(),
"quartiles": df["values"].quantile([0.25, 0.5, 0.75]).to_dict(),
"skewness": df["values"].skew(),
"kurtosis": df["values"].kurtosis()
}
return {
"status": "success",
"message": "高级数据分析完成",
"data": stats,
"method": "advanced",
"capabilities": ["pandas", "numpy", "advanced_stats"]
}
async def _numpy_analysis(self, data):
"""中级分析仅使用numpy"""
import numpy as np
arr = np.array(data)
stats = {
"count": len(arr),
"mean": np.mean(arr),
"median": np.median(arr),
"std": np.std(arr),
"min": np.min(arr),
"max": np.max(arr),
"sum": np.sum(arr)
}
return {
"status": "success",
"message": "数值计算分析完成",
"data": stats,
"method": "numpy",
"capabilities": ["numpy", "basic_stats"]
}
async def _basic_analysis(self, data):
"""基础分析纯Python"""
stats = {
"count": len(data),
"mean": sum(data) / len(data) if data else 0,
"min": min(data) if data else None,
"max": max(data) if data else None,
"sum": sum(data)
}
return {
"status": "success",
"message": "基础数据分析完成",
"data": stats,
"method": "basic",
"capabilities": ["pure_python"],
"note": "安装numpy和pandas可获得更多分析功能"
}
class VisualizeDataAction(BaseAction):
"""数据可视化Action - 展示条件功能启用"""
async def execute(self, action_input, context=None):
"""数据可视化"""
# 检查可视化依赖
visualization_available = self._check_visualization_deps()
if not visualization_available:
return {
"status": "unavailable",
"message": "数据可视化功能不可用",
"reason": "缺少matplotlib和PIL依赖",
"install_hint": "pip install matplotlib>=3.3.0 Pillow>=8.0.0",
"alternative": "可以使用基础数据分析功能"
}
return await self._create_visualization(action_input)
def _check_visualization_deps(self):
"""检查可视化所需的依赖"""
try:
import matplotlib
import PIL
return True
except ImportError:
return False
async def _create_visualization(self, action_input):
"""创建数据可视化"""
import matplotlib.pyplot as plt
import io
import base64
from PIL import Image
# 获取数据
data = action_input.get("data", [1, 2, 3, 4, 5])
chart_type = action_input.get("type", "line")
# 创建图表
plt.figure(figsize=(10, 6))
if chart_type == "line":
plt.plot(data)
plt.title("线性图")
elif chart_type == "bar":
plt.bar(range(len(data)), data)
plt.title("柱状图")
elif chart_type == "hist":
plt.hist(data, bins=10)
plt.title("直方图")
else:
plt.plot(data)
plt.title("默认线性图")
plt.xlabel("索引")
plt.ylabel("数值")
plt.grid(True)
# 保存为字节流
buffer = io.BytesIO()
plt.savefig(buffer, format='png', dpi=150, bbox_inches='tight')
buffer.seek(0)
# 转换为base64
image_base64 = base64.b64encode(buffer.getvalue()).decode()
plt.close() # 释放内存
return {
"status": "success",
"message": f"生成{chart_type}图表成功",
"data": {
"chart_type": chart_type,
"data_points": len(data),
"image_base64": image_base64
},
"capabilities": ["matplotlib", "pillow", "visualization"]
}
# 测试和演示代码
async def demo_dependency_management():
"""演示依赖管理功能"""
print("🔍 插件依赖管理演示")
print("=" * 50)
# 创建插件实例
plugin = DataAnalysisPlugin()
print("\n📦 插件依赖信息:")
for dep in plugin.python_dependencies:
status = "✅" if plugin._check_dependency_available(dep.package_name) else "❌"
optional_str = " (可选)" if dep.optional else " (必需)"
print(f" {status} {dep.package_name} {dep.version}{optional_str}")
print(f" {dep.description}")
print("\n🧪 功能测试:")
# 测试数据获取
fetch_action = FetchDataAction()
result = await fetch_action.execute({"url": "https://httpbin.org/json"})
print(f" 数据获取: {result['status']}")
# 测试数据分析
analyze_action = AnalyzeDataAction()
test_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = await analyze_action.execute({"data": test_data})
print(f" 数据分析: {result['status']} (方法: {result.get('method', 'unknown')})")
print(f" 可用功能: {result.get('capabilities', [])}")
# 测试数据可视化
viz_action = VisualizeDataAction()
result = await viz_action.execute({"data": test_data, "type": "line"})
print(f" 数据可视化: {result['status']}")
print("\n💡 依赖管理建议:")
missing_deps = plugin.plugin_info.get_missing_packages()
if missing_deps:
print(" 缺失的必需依赖:")
for dep in missing_deps:
print(f" - {dep.get_pip_requirement()}")
print(f"\n 安装命令:")
print(f" pip install {' '.join([dep.get_pip_requirement() for dep in missing_deps])}")
else:
print(" ✅ 所有必需依赖都已安装")
if __name__ == "__main__":
import asyncio
# 为演示添加依赖检查方法
def _check_dependency_available(package_name):
try:
__import__(package_name)
return True
except ImportError:
return False
DataAnalysisPlugin._check_dependency_available = _check_dependency_available
# 运行演示
asyncio.run(demo_dependency_management())
```
## 🎯 示例说明
### 1. 依赖分层设计
这个示例展示了三层依赖设计:
- **必需依赖**: `requests` - 核心功能必需
- **增强依赖**: `pandas`, `numpy` - 提供更强大的分析能力
- **可选依赖**: `matplotlib`, `PIL` - 提供可视化功能
### 2. 优雅降级策略
```python
# 三级功能降级
if has_pandas and has_numpy:
return await self._advanced_analysis(data) # 最佳体验
elif has_numpy:
return await self._numpy_analysis(data) # 中等体验
else:
return await self._basic_analysis(data) # 基础体验
```
### 3. 条件功能启用
```python
# 只有依赖可用时才提供功能
visualization_available = self._check_visualization_deps()
if not visualization_available:
return {"status": "unavailable", "install_hint": "..."}
```
## 🚀 使用这个示例
### 1. 复制代码
将示例代码保存为 `plugins/data_analysis_plugin/plugin.py`
### 2. 测试依赖检查
```python
from src.plugin_system import plugin_manager
# 检查这个插件的依赖
result = plugin_manager.check_all_dependencies()
print(result['plugin_status']['data_analysis_plugin'])
```
### 3. 安装缺失依赖
```python
# 生成requirements文件
plugin_manager.generate_plugin_requirements("data_plugin_deps.txt")
# 手动安装
# pip install -r data_plugin_deps.txt
```
### 4. 测试功能降级
```bash
# 测试基础功能只安装requests
pip install requests>=2.25.0
# 测试增强功能(添加数据处理)
pip install numpy>=1.20.0 pandas>=1.3.0
# 测试完整功能(添加可视化)
pip install matplotlib>=3.3.0 Pillow>=8.0.0
```
## 💡 最佳实践总结
1. **分层依赖设计**: 区分核心、增强、可选依赖
2. **优雅降级处理**: 提供多级功能体验
3. **明确错误信息**: 告诉用户如何解决依赖问题
4. **条件功能启用**: 根据依赖可用性动态调整功能
5. **详细依赖描述**: 说明每个依赖的用途
这个示例展示了如何构建一个既强大又灵活的插件,即使在依赖不完整的情况下也能提供有用的功能。

View File

@@ -124,6 +124,16 @@ class HelloWorldPlugin(BasePlugin):
plugin_author = "你的名字"
enable_plugin = True
config_file_name = "config.toml"
# Python依赖声明可选
python_dependencies = [
# 如果你的插件需要额外的Python包在这里声明
# PythonDependency(
# package_name="requests",
# version=">=2.25.0",
# description="HTTP请求库"
# ),
]
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
"""返回插件包含的组件列表"""
@@ -245,14 +255,59 @@ python main.py
2. **组件列表**: `get_plugin_components()` 返回所有组件
3. **配置加载**: 自动加载 `config.toml` 文件
## 📦 添加依赖包(可选)
如果你的插件需要额外的Python包可以声明依赖
```python
from src.plugin_system import PythonDependency
@register_plugin
class HelloWorldPlugin(BasePlugin):
# ... 其他配置 ...
# 声明Python依赖
python_dependencies = [
PythonDependency(
package_name="requests",
version=">=2.25.0",
description="HTTP请求库用于网络功能"
),
PythonDependency(
package_name="numpy",
version=">=1.20.0",
optional=True,
description="数值计算库(可选功能)"
),
]
```
### 依赖检查
系统会自动检查依赖,你也可以手动检查:
```python
from src.plugin_system import plugin_manager
# 检查所有插件依赖
result = plugin_manager.check_all_dependencies()
print(f"缺少依赖的插件: {result['plugins_with_missing_required']}个")
# 生成requirements文件
plugin_manager.generate_plugin_requirements("plugin_deps.txt")
```
📚 **详细了解**: [依赖管理系统](dependency-management.md)
## 🎯 下一步
恭喜你已经创建了第一个MaiBot插件。接下来可以
1. 学习 [Action组件详解](action-components.md) 掌握更复杂的Action开发
2. 学习 [Command组件详解](command-components.md) 创建更强大的命令
3. 查看 [API参考](api/) 了解所有可用的接口
4. 参考 [完整示例](examples/complete-examples.md) 学习最佳实践
3. 了解 [依赖管理系统](dependency-management.md) 管理Python包依赖
4. 查看 [API参考](api/) 了解所有可用的接口
5. 参考 [完整示例](examples/complete-examples.md) 学习最佳实践
## 🐛 常见问题