Files
mai-bot/src/plugin_system/core/dependency_manager.py
UnCLAS-Prommer 1aa2734d62 typing fix
2025-07-17 00:10:41 +08:00

191 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
插件依赖管理器
负责检查和安装插件的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()