Spaces:
Runtime error
Runtime error
| """ | |
| Plugin Loader | |
| Dynamically loads and manages plugins. | |
| """ | |
| import importlib | |
| import inspect | |
| from pathlib import Path | |
| from typing import Dict, List, Type, Optional, Any | |
| from loguru import logger | |
| from plugins.base import BasePlugin, PluginMetadata | |
| from core.exceptions import PluginError, PluginLoadError | |
| class PluginLoader: | |
| """ | |
| Load and manage plugins dynamically. | |
| Discovers, loads, and manages plugin lifecycle. | |
| """ | |
| def __init__(self, plugin_dir: Optional[Path] = None): | |
| """ | |
| Initialize PluginLoader. | |
| Args: | |
| plugin_dir: Directory containing plugin modules | |
| """ | |
| if plugin_dir is None: | |
| plugin_dir = Path(__file__).parent | |
| self.plugin_dir = Path(plugin_dir) | |
| self.plugins: Dict[str, BasePlugin] = {} | |
| self.plugin_classes: Dict[str, Type[BasePlugin]] = {} | |
| logger.info(f"PluginLoader initialized with directory: {plugin_dir}") | |
| def discover_plugins(self) -> List[str]: | |
| """ | |
| Discover available plugins in the plugin directory. | |
| Returns: | |
| List of discovered plugin module names | |
| """ | |
| discovered = [] | |
| # Look for Python files in the plugin directory | |
| for file_path in self.plugin_dir.glob("*.py"): | |
| # Skip __init__.py, base.py, loader.py | |
| if file_path.stem in ["__init__", "base", "loader"]: | |
| continue | |
| module_name = file_path.stem | |
| discovered.append(module_name) | |
| logger.debug(f"Discovered plugin module: {module_name}") | |
| logger.info(f"Discovered {len(discovered)} plugin modules") | |
| return discovered | |
| def load_plugin_class(self, module_name: str) -> Optional[Type[BasePlugin]]: | |
| """ | |
| Load a plugin class from a module. | |
| Args: | |
| module_name: Name of the module to load | |
| Returns: | |
| Plugin class or None if not found | |
| """ | |
| try: | |
| # Import the module | |
| module = importlib.import_module(f"plugins.{module_name}") | |
| # Find all classes that inherit from BasePlugin | |
| for name, obj in inspect.getmembers(module, inspect.isclass): | |
| if (issubclass(obj, BasePlugin) and | |
| obj is not BasePlugin and | |
| obj.__module__ == module.__name__): | |
| logger.info(f"Loaded plugin class: {name} from {module_name}") | |
| return obj | |
| logger.warning(f"No plugin class found in module: {module_name}") | |
| return None | |
| except Exception as e: | |
| logger.error(f"Failed to load plugin module {module_name}: {e}") | |
| raise PluginLoadError( | |
| f"Cannot load plugin module {module_name}: {str(e)}", | |
| {"module": module_name, "error": str(e)} | |
| ) | |
| def load_plugin( | |
| self, | |
| plugin_name: str, | |
| auto_initialize: bool = True | |
| ) -> BasePlugin: | |
| """ | |
| Load and optionally initialize a plugin. | |
| Args: | |
| plugin_name: Name of the plugin module | |
| auto_initialize: Whether to automatically initialize the plugin | |
| Returns: | |
| Loaded plugin instance | |
| """ | |
| try: | |
| # Check if already loaded | |
| if plugin_name in self.plugins: | |
| logger.info(f"Plugin {plugin_name} already loaded") | |
| return self.plugins[plugin_name] | |
| # Load plugin class | |
| plugin_class = self.load_plugin_class(plugin_name) | |
| if plugin_class is None: | |
| raise PluginLoadError( | |
| f"No plugin class found in {plugin_name}", | |
| {"plugin": plugin_name} | |
| ) | |
| # Store plugin class | |
| self.plugin_classes[plugin_name] = plugin_class | |
| # Create instance | |
| plugin_instance = plugin_class() | |
| # Initialize if requested | |
| if auto_initialize: | |
| plugin_instance.initialize() | |
| plugin_instance._initialized = True | |
| # Store instance | |
| self.plugins[plugin_instance.metadata.name] = plugin_instance | |
| logger.info( | |
| f"Plugin loaded: {plugin_instance.metadata.name} " | |
| f"v{plugin_instance.metadata.version}" | |
| ) | |
| return plugin_instance | |
| except Exception as e: | |
| logger.error(f"Failed to load plugin {plugin_name}: {e}") | |
| raise PluginLoadError( | |
| f"Cannot load plugin {plugin_name}: {str(e)}", | |
| {"plugin": plugin_name, "error": str(e)} | |
| ) | |
| def load_all_plugins(self, auto_initialize: bool = True) -> Dict[str, BasePlugin]: | |
| """ | |
| Discover and load all available plugins. | |
| Args: | |
| auto_initialize: Whether to automatically initialize plugins | |
| Returns: | |
| Dictionary of loaded plugins | |
| """ | |
| discovered = self.discover_plugins() | |
| for module_name in discovered: | |
| try: | |
| self.load_plugin(module_name, auto_initialize=auto_initialize) | |
| except Exception as e: | |
| logger.error(f"Failed to load plugin {module_name}: {e}") | |
| # Continue loading other plugins | |
| continue | |
| logger.info(f"Loaded {len(self.plugins)} plugins") | |
| return self.plugins | |
| def unload_plugin(self, plugin_name: str) -> None: | |
| """ | |
| Unload a plugin and clean up resources. | |
| Args: | |
| plugin_name: Name of the plugin to unload | |
| """ | |
| if plugin_name not in self.plugins: | |
| logger.warning(f"Plugin {plugin_name} not loaded") | |
| return | |
| plugin = self.plugins[plugin_name] | |
| # Clean up resources | |
| try: | |
| plugin.cleanup() | |
| except Exception as e: | |
| logger.error(f"Error during plugin cleanup: {e}") | |
| # Remove from loaded plugins | |
| del self.plugins[plugin_name] | |
| logger.info(f"Plugin unloaded: {plugin_name}") | |
| def unload_all_plugins(self) -> None: | |
| """Unload all plugins.""" | |
| plugin_names = list(self.plugins.keys()) | |
| for plugin_name in plugin_names: | |
| self.unload_plugin(plugin_name) | |
| logger.info("All plugins unloaded") | |
| def get_plugin(self, plugin_name: str) -> Optional[BasePlugin]: | |
| """ | |
| Get a loaded plugin by name. | |
| Args: | |
| plugin_name: Name of the plugin | |
| Returns: | |
| Plugin instance or None | |
| """ | |
| return self.plugins.get(plugin_name) | |
| def list_plugins(self) -> List[PluginMetadata]: | |
| """ | |
| List all loaded plugins. | |
| Returns: | |
| List of plugin metadata | |
| """ | |
| return [plugin.metadata for plugin in self.plugins.values()] | |
| def reload_plugin(self, plugin_name: str) -> BasePlugin: | |
| """ | |
| Reload a plugin (unload then load). | |
| Args: | |
| plugin_name: Name of the plugin to reload | |
| Returns: | |
| Reloaded plugin instance | |
| """ | |
| logger.info(f"Reloading plugin: {plugin_name}") | |
| # Find the module name | |
| module_name = None | |
| for name, plugin in self.plugins.items(): | |
| if name == plugin_name: | |
| module_name = plugin.__class__.__module__.split(".")[-1] | |
| break | |
| if module_name is None: | |
| raise PluginError( | |
| f"Plugin {plugin_name} not found", | |
| {"plugin": plugin_name} | |
| ) | |
| # Unload | |
| self.unload_plugin(plugin_name) | |
| # Reload module | |
| importlib.reload( | |
| importlib.import_module(f"plugins.{module_name}") | |
| ) | |
| # Load again | |
| return self.load_plugin(module_name) | |
| def get_plugins_by_category(self, category: str) -> List[BasePlugin]: | |
| """ | |
| Get all plugins in a specific category. | |
| Args: | |
| category: Plugin category | |
| Returns: | |
| List of plugins in the category | |
| """ | |
| return [ | |
| plugin for plugin in self.plugins.values() | |
| if plugin.metadata.category == category | |
| ] | |
| def get_enabled_plugins(self) -> List[BasePlugin]: | |
| """ | |
| Get all enabled plugins. | |
| Returns: | |
| List of enabled plugins | |
| """ | |
| return [ | |
| plugin for plugin in self.plugins.values() | |
| if plugin.is_enabled() | |
| ] | |
| def get_plugin_info(self) -> Dict[str, Dict[str, Any]]: | |
| """ | |
| Get information about all loaded plugins. | |
| Returns: | |
| Dictionary with plugin information | |
| """ | |
| info = {} | |
| for name, plugin in self.plugins.items(): | |
| info[name] = { | |
| "name": plugin.metadata.name, | |
| "version": plugin.metadata.version, | |
| "description": plugin.metadata.description, | |
| "author": plugin.metadata.author, | |
| "category": plugin.metadata.category, | |
| "enabled": plugin.is_enabled(), | |
| "initialized": plugin.is_initialized(), | |
| "priority": plugin.metadata.priority, | |
| } | |
| return info | |
| def __repr__(self) -> str: | |
| """String representation.""" | |
| return f"PluginLoader(plugins={len(self.plugins)})" | |