207 lines
5.9 KiB
Python
207 lines
5.9 KiB
Python
|
|
"""
|
||
|
|
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()
|