""" Plugin registry for managing discovered and loaded plugins. This module provides a central registry for all plugins in the system. """ from typing import Dict, List, Optional, Type, Any from .base import BasePlugin, PluginType class PluginRegistry: """Central registry for managing plugins.""" def __init__(self): """Initialize empty registry.""" self._plugins: Dict[str, Type[BasePlugin]] = {} self._instances: Dict[str, BasePlugin] = {} self._plugins_by_type: Dict[PluginType, List[str]] = {} def register(self, plugin_class: Type[BasePlugin], name: Optional[str] = None) -> str: """ Register a plugin class. Args: plugin_class: Plugin class to register name: Optional plugin name (uses class name if not provided) Returns: The name the plugin was registered under Raises: ValueError: If plugin name already exists """ if name is None: name = plugin_class.__name__ if name in self._plugins: raise ValueError(f"Plugin '{name}' is already registered") self._plugins[name] = plugin_class # Create instance to get metadata try: instance = plugin_class() plugin_type = instance.metadata.plugin_type if plugin_type not in self._plugins_by_type: self._plugins_by_type[plugin_type] = [] self._plugins_by_type[plugin_type].append(name) except Exception: # If we can't get metadata, register as generic extension if PluginType.EXTENSION not in self._plugins_by_type: self._plugins_by_type[PluginType.EXTENSION] = [] self._plugins_by_type[PluginType.EXTENSION].append(name) return name def unregister(self, name: str) -> bool: """ Unregister a plugin. Args: name: Plugin name to unregister Returns: True if plugin was unregistered, False if not found """ if name not in self._plugins: return False # Remove from instances if loaded if name in self._instances: instance = self._instances[name] instance.cleanup() del self._instances[name] # Remove from type mapping for plugin_type, plugin_names in self._plugins_by_type.items(): if name in plugin_names: plugin_names.remove(name) break # Remove from main registry del self._plugins[name] return True def get_plugin(self, name: str, config: Dict[str, Any] = None) -> Optional[BasePlugin]: """ Get plugin instance by name. Args: name: Plugin name config: Optional configuration for plugin Returns: Plugin instance or None if not found """ if name not in self._plugins: return None # Return existing instance if already loaded and no new config if name in self._instances and config is None: return self._instances[name] # Create new instance try: plugin_class = self._plugins[name] instance = plugin_class(config) if instance.initialize(): self._instances[name] = instance return instance except Exception: pass return None def get_plugins_by_type(self, plugin_type: PluginType) -> List[str]: """ Get list of plugin names by type. Args: plugin_type: Type of plugins to retrieve Returns: List of plugin names of the specified type """ return self._plugins_by_type.get(plugin_type, []).copy() def list_plugins(self) -> Dict[str, Dict[str, Any]]: """ List all registered plugins with metadata. Returns: Dictionary mapping plugin names to metadata """ result = {} for name, plugin_class in self._plugins.items(): try: instance = plugin_class() metadata = instance.metadata result[name] = { 'name': metadata.name, 'version': metadata.version, 'description': metadata.description, 'author': metadata.author, 'type': metadata.plugin_type.value, 'dependencies': metadata.dependencies, 'markitect_version': metadata.markitect_version, 'loaded': name in self._instances } except Exception: result[name] = { 'name': name, 'error': 'Failed to load metadata' } return result def is_loaded(self, name: str) -> bool: """ Check if plugin is loaded. Args: name: Plugin name Returns: True if plugin is loaded """ return name in self._instances def reload_plugin(self, name: str, config: Dict[str, Any] = None) -> bool: """ Reload a plugin with new configuration. Args: name: Plugin name config: New configuration Returns: True if reload successful """ if name not in self._plugins: return False # Cleanup existing instance if name in self._instances: self._instances[name].cleanup() del self._instances[name] # Load with new config return self.get_plugin(name, config) is not None def cleanup_all(self) -> None: """Cleanup all loaded plugin instances.""" for instance in self._instances.values(): try: instance.cleanup() except Exception: pass self._instances.clear() # Global plugin registry instance plugin_registry = PluginRegistry()