Files
Mofox-Core/docs/plugins/examples/dependency-example.md
2025-06-15 23:53:23 +08:00

15 KiB
Raw Blame History

📦 依赖管理完整示例

这个示例展示了如何在插件中正确使用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. 优雅降级策略

# 三级功能降级
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. 条件功能启用

# 只有依赖可用时才提供功能
visualization_available = self._check_visualization_deps()
if not visualization_available:
    return {"status": "unavailable", "install_hint": "..."}

🚀 使用这个示例

1. 复制代码

将示例代码保存为 plugins/data_analysis_plugin/plugin.py

2. 测试依赖检查

from src.plugin_system import plugin_manager

# 检查这个插件的依赖
result = plugin_manager.check_all_dependencies()
print(result['plugin_status']['data_analysis_plugin'])

3. 安装缺失依赖

# 生成requirements文件
plugin_manager.generate_plugin_requirements("data_plugin_deps.txt")

# 手动安装
# pip install -r data_plugin_deps.txt

4. 测试功能降级

# 测试基础功能只安装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. 详细依赖描述: 说明每个依赖的用途

这个示例展示了如何构建一个既强大又灵活的插件,即使在依赖不完整的情况下也能提供有用的功能。