""" Plugin manager for issue backends. This module handles discovery, loading, and configuration of issue management backends. """ import importlib import yaml from pathlib import Path from typing import Dict, Any, Type, Optional from .base import IssueBackend from .exceptions import PluginNotFoundError, ConfigurationError class IssuePluginManager: """Manages issue backend plugins and configuration.""" def __init__(self, config_path: Optional[str] = None): """ Initialize plugin manager. Args: config_path: Optional path to configuration file """ self.config = self._load_config(config_path) self.plugins = self._discover_plugins() def get_backend(self, backend_name: Optional[str] = None) -> IssueBackend: """ Get configured backend instance. Args: backend_name: Backend name to use, or None for default Returns: IssueBackend instance Raises: PluginNotFoundError: If backend not found """ backend_name = backend_name or self.config.get('default_backend', 'gitea') plugin_class = self.plugins.get(backend_name) if not plugin_class: raise PluginNotFoundError(f"Unknown backend: {backend_name}") backend_config = self.config.get('backends', {}).get(backend_name, {}) return plugin_class(backend_config) def _load_config(self, config_path: Optional[str] = None) -> Dict[str, Any]: """ Load configuration from file or return defaults. Args: config_path: Path to configuration file Returns: Configuration dictionary """ if config_path is None: config_path = Path('.markitect/config/issues.yml') else: config_path = Path(config_path) # Default configuration default_config = { 'default_backend': 'gitea', 'backends': { 'gitea': { 'url': 'http://92.205.130.254:32166', 'repo': 'coulomb/markitect_project' }, 'local': { 'directory': '.markitect/issues', 'auto_git': True } } } if not config_path.exists(): return default_config try: with open(config_path, 'r') as f: config = yaml.safe_load(f) or {} # Merge with defaults merged_config = default_config.copy() merged_config.update(config) return merged_config except Exception: # Return defaults if config loading fails return default_config def _discover_plugins(self) -> Dict[str, Type[IssueBackend]]: """ Discover available backend plugins. Returns: Dictionary mapping backend names to plugin classes """ plugins = {} # Try to import known plugins plugin_modules = [ ('gitea', 'markitect.issues.plugins.gitea', 'GiteaPlugin'), ('local', 'markitect.issues.plugins.local', 'LocalPlugin'), ] for name, module_path, class_name in plugin_modules: try: module = importlib.import_module(module_path) plugin_class = getattr(module, class_name) if issubclass(plugin_class, IssueBackend): plugins[name] = plugin_class except (ImportError, AttributeError): # Plugin not available, skip continue return plugins