477 lines
15 KiB
Markdown
477 lines
15 KiB
Markdown
# 📦 依赖管理完整示例
|
||
|
||
> 这个示例展示了如何在插件中正确使用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. **详细依赖描述**: 说明每个依赖的用途
|
||
|
||
这个示例展示了如何构建一个既强大又灵活的插件,即使在依赖不完整的情况下也能提供有用的功能。 |