""" Renderer registry for automatic discovery and registration of renderers. """ import logging import importlib import pkgutil from typing import Dict, Type, List, Optional from .rendererBaseTemplate import BaseRenderer logger = logging.getLogger(__name__) class RendererRegistry: """Registry for automatic renderer discovery and management.""" def __init__(self): self._renderers: Dict[str, Type[BaseRenderer]] = {} self._format_mappings: Dict[str, str] = {} self._discovered = False def discover_renderers(self) -> None: """Automatically discover and register all renderers by scanning files.""" if self._discovered: return try: import os import sys from pathlib import Path # Get the directory containing this registry file current_dir = Path(__file__).parent renderers_dir = current_dir # Get the package name dynamically package_name = __name__.rsplit('.', 1)[0] # Scan all Python files in the renderers directory for file_path in renderers_dir.glob("*.py"): if file_path.name in ['registry.py', 'rendererBaseTemplate.py', '__init__.py']: continue # Extract module name from filename module_name = file_path.stem try: # Import the module dynamically full_module_name = f"{package_name}.{module_name}" module = importlib.import_module(full_module_name) # Look for renderer classes in the module for attr_name in dir(module): attr = getattr(module, attr_name) if (isinstance(attr, type) and issubclass(attr, BaseRenderer) and attr != BaseRenderer and hasattr(attr, 'get_supported_formats')): # Register the renderer self._register_renderer_class(attr) logger.info(f"Discovered renderer: {attr.__name__} from {module_name}") except Exception as e: logger.warning(f"Could not load renderer from {module_name}: {str(e)}") continue self._discovered = True logger.info(f"Renderer discovery completed. Found {len(self._renderers)} renderers.") except Exception as e: logger.error(f"Error during renderer discovery: {str(e)}") self._discovered = True # Mark as discovered to avoid repeated attempts def _register_renderer_class(self, renderer_class: Type[BaseRenderer]) -> None: """Register a renderer class with its supported formats.""" try: # Get supported formats from the renderer class supported_formats = renderer_class.get_supported_formats() for format_name in supported_formats: # Register primary format self._renderers[format_name.lower()] = renderer_class # Register aliases if any if hasattr(renderer_class, 'get_format_aliases'): aliases = renderer_class.get_format_aliases() for alias in aliases: self._format_mappings[alias.lower()] = format_name.lower() logger.debug(f"Registered {renderer_class.__name__} for formats: {supported_formats}") except Exception as e: logger.error(f"Error registering renderer {renderer_class.__name__}: {str(e)}") def get_renderer(self, output_format: str, services=None) -> Optional[BaseRenderer]: """Get a renderer instance for the specified format.""" if not self._discovered: self.discover_renderers() # Normalize format name format_name = output_format.lower().strip() # Check for aliases first if format_name in self._format_mappings: format_name = self._format_mappings[format_name] # Get renderer class renderer_class = self._renderers.get(format_name) if renderer_class: try: return renderer_class(services=services) except Exception as e: logger.error(f"Error creating renderer instance for {format_name}: {str(e)}") return None logger.warning(f"No renderer found for format: {output_format}") return None def get_supported_formats(self) -> List[str]: """Get list of all supported formats.""" if not self._discovered: self.discover_renderers() formats = list(self._renderers.keys()) formats.extend(self._format_mappings.keys()) return sorted(set(formats)) def get_renderer_info(self) -> Dict[str, Dict[str, str]]: """Get information about all registered renderers.""" if not self._discovered: self.discover_renderers() info = {} for format_name, renderer_class in self._renderers.items(): info[format_name] = { 'class_name': renderer_class.__name__, 'module': renderer_class.__module__, 'description': getattr(renderer_class, '__doc__', 'No description').strip().split('\n')[0] if renderer_class.__doc__ else 'No description' } return info # Global registry instance _registry = RendererRegistry() def get_renderer(output_format: str, services=None) -> Optional[BaseRenderer]: """Get a renderer instance for the specified format.""" return _registry.get_renderer(output_format, services) def get_supported_formats() -> List[str]: """Get list of all supported formats.""" return _registry.get_supported_formats() def get_renderer_info() -> Dict[str, Dict[str, str]]: """Get information about all registered renderers.""" return _registry.get_renderer_info()