""" EU-Utility - Dependency Helper (Core Framework Component) Built-in dependency troubleshooting - not a plugin. """ import subprocess import sys from pathlib import Path from typing import List, Dict, Optional, Tuple from dataclasses import dataclass @dataclass class DependencyCheck: """Dependency check result.""" name: str required: bool installed: bool version: Optional[str] = None fix_command: Optional[str] = None error_message: Optional[str] = None class DependencyHelper: """Dependency helper - built into the framework. Checks and helps install missing dependencies for plugins. """ CORE_DEPENDENCIES = [ ('PyQt6', 'PyQt6', True), ('requests', 'requests', True), ('Pillow', 'Pillow', True), ] OPTIONAL_DEPENDENCIES = [ ('pytesseract', 'pytesseract', False), ('easyocr', 'easyocr', False), ('paddleocr', 'paddleocr', False), ('psutil', 'psutil', False), ('keyboard', 'keyboard', False), ('win32gui', 'pywin32', False), ] def __init__(self): self.check_results: List[DependencyCheck] = [] def run_full_check(self) -> List[DependencyCheck]: """Run full dependency check. Returns: List of dependency check results """ self.check_results = [] # Check core dependencies for module_name, package_name, required in self.CORE_DEPENDENCIES: result = self._check_module(module_name, package_name, required) self.check_results.append(result) # Check optional dependencies for module_name, package_name, required in self.OPTIONAL_DEPENDENCIES: result = self._check_module(module_name, package_name, required) self.check_results.append(result) return self.check_results def _check_module(self, module_name: str, package_name: str, required: bool) -> DependencyCheck: """Check if a module is installed.""" try: module = __import__(module_name) version = getattr(module, '__version__', 'unknown') return DependencyCheck( name=package_name, required=required, installed=True, version=version ) except ImportError: return DependencyCheck( name=package_name, required=required, installed=False, fix_command=f"pip install {package_name}" ) def check_plugin_dependencies(self, plugin_class) -> List[DependencyCheck]: """Check dependencies for a specific plugin. Args: plugin_class: Plugin class to check Returns: List of dependency check results """ results = [] deps = getattr(plugin_class, 'dependencies', {}) pip_deps = deps.get('pip', []) for dep in pip_deps: # Parse package name (could be "package>=1.0") package_name = dep.split('>=')[0].split('==')[0].split('<')[0].strip() try: __import__(package_name.lower().replace('-', '_')) results.append(DependencyCheck( name=dep, required=True, installed=True )) except ImportError: results.append(DependencyCheck( name=dep, required=True, installed=False, fix_command=f"pip install {dep}" )) return results def install_dependency(self, package_name: str) -> Tuple[bool, str]: """Install a dependency. Args: package_name: Package to install Returns: (success, message) """ try: result = subprocess.run( [sys.executable, '-m', 'pip', 'install', package_name], capture_output=True, text=True, timeout=120 ) if result.returncode == 0: return True, f"Successfully installed {package_name}" else: return False, f"Installation failed: {result.stderr}" except subprocess.TimeoutExpired: return False, "Installation timed out" except Exception as e: return False, f"Error: {str(e)}" def get_missing_core_deps(self) -> List[DependencyCheck]: """Get list of missing core dependencies.""" if not self.check_results: self.run_full_check() return [r for r in self.check_results if r.required and not r.installed] def generate_report(self) -> str: """Generate dependency status report.""" if not self.check_results: self.run_full_check() lines = ["# Dependency Report\n"] lines.append("## Core Dependencies\n") for result in self.check_results: if result.required: status = "✅" if result.installed else "❌" version = f" ({result.version})" if result.version else "" lines.append(f"{status} {result.name}{version}") lines.append("\n## Optional Dependencies\n") for result in self.check_results: if not result.required: status = "✅" if result.installed else "⚠️" version = f" ({result.version})" if result.version else "" lines.append(f"{status} {result.name}{version}") missing = self.get_missing_core_deps() if missing: lines.append("\n## Missing Required Dependencies\n") lines.append("Install with:\n") for dep in missing: lines.append(f" {dep.fix_command}") return "\n".join(lines) def troubleshoot_plugin(self, plugin_id: str, plugin_manager) -> List[str]: """Troubleshoot a plugin's issues. Returns: List of issues found """ issues = [] # Get plugin class all_plugins = plugin_manager.get_all_discovered_plugins() plugin_class = all_plugins.get(plugin_id) if not plugin_class: issues.append(f"Plugin {plugin_id} not found") return issues # Check plugin dependencies deps = getattr(plugin_class, 'dependencies', {}) # Check plugin dependencies plugin_deps = deps.get('plugins', []) for dep_id in plugin_deps: if not plugin_manager.is_plugin_enabled(dep_id): dep_class = all_plugins.get(dep_id) dep_name = dep_class.name if dep_class else dep_id issues.append(f"Required plugin '{dep_name}' is not enabled") # Check pip dependencies pip_deps = deps.get('pip', []) for dep in pip_deps: package_name = dep.split('>=')[0].split('==')[0].split('<')[0].strip() try: __import__(package_name.lower().replace('-', '_')) except ImportError: issues.append(f"Missing Python package: {dep}") return issues class DependencyHelperDialog: """UI dialog for dependency helper.""" @staticmethod def show_dependency_check(parent=None): """Show dependency check dialog.""" from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit, QListWidget, QListWidgetItem ) helper = DependencyHelper() results = helper.run_full_check() dialog = QDialog(parent) dialog.setWindowTitle("Dependency Check") dialog.setMinimumSize(500, 400) dialog.setStyleSheet(""" QDialog { background-color: #1a1f2e; } QLabel { color: white; } """) layout = QVBoxLayout(dialog) # Status summary missing_core = [r for r in results if r.required and not r.installed] missing_optional = [r for r in results if not r.required and not r.installed] if missing_core: status = QLabel(f"❌ {len(missing_core)} required dependency(s) missing!") status.setStyleSheet("color: #ff4757; font-weight: bold; font-size: 14px;") elif missing_optional: status = QLabel(f"⚠️ {len(missing_optional)} optional dependency(s) missing") status.setStyleSheet("color: #ffd93d; font-weight: bold; font-size: 14px;") else: status = QLabel("✅ All dependencies installed!") status.setStyleSheet("color: #4ecdc4; font-weight: bold; font-size: 14px;") layout.addWidget(status) # Results list list_widget = QListWidget() list_widget.setStyleSheet(""" QListWidget { background-color: #232837; color: white; border: 1px solid rgba(100, 110, 130, 80); } QListWidget::item { padding: 8px; } """) for result in results: if result.required and not result.installed: icon = "❌" color = "#ff4757" elif not result.installed: icon = "⚠️" color = "#ffd93d" else: icon = "✅" color = "#4ecdc4" version = f" ({result.version})" if result.version else "" item_text = f"{icon} {result.name}{version}" item = QListWidgetItem(item_text) item.setForeground(Qt.GlobalColor.white) list_widget.addItem(item) layout.addWidget(list_widget) # Buttons btn_layout = QHBoxLayout() install_btn = QPushButton("📥 Install Missing") install_btn.setStyleSheet(""" QPushButton { background-color: #4a9eff; color: white; padding: 10px 20px; border: none; border-radius: 4px; } """) btn_layout.addWidget(install_btn) close_btn = QPushButton("Close") close_btn.clicked.connect(dialog.close) btn_layout.addWidget(close_btn) layout.addLayout(btn_layout) dialog.exec()