# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ Renderer registry for automatic discovery and registration of renderers. """ import logging import importlib 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 discoverRenderers(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 currentDir = Path(__file__).parent renderersDir = currentDir # Get the package name dynamically packageName = __name__.rsplit('.', 1)[0] # Scan all Python files in the renderers directory for filePath in renderersDir.glob("*.py"): if filePath.name in ['registry.py', 'rendererBaseTemplate.py', '__init__.py']: continue # Extract module name from filename moduleName = filePath.stem try: # Import the module dynamically fullModuleName = f"{packageName}.{moduleName}" module = importlib.import_module(fullModuleName) # Look for renderer classes in the module for attrName in dir(module): attr = getattr(module, attrName) if (isinstance(attr, type) and issubclass(attr, BaseRenderer) and attr != BaseRenderer and hasattr(attr, 'getSupportedFormats')): # Register the renderer self._registerRendererClass(attr) except Exception as e: logger.warning(f"Could not load renderer from {moduleName}: {str(e)}") continue self._discovered = True except Exception as e: logger.error(f"Error during renderer discovery: {str(e)}") self._discovered = True # Mark as discovered to avoid repeated attempts def _registerRendererClass(self, rendererClass: Type[BaseRenderer]) -> None: """Register a renderer class with its supported formats.""" try: # Get supported formats from the renderer class supportedFormats = rendererClass.getSupportedFormats() for formatName in supportedFormats: # Register primary format self._renderers[formatName.lower()] = rendererClass # Register aliases if any if hasattr(rendererClass, 'getFormatAliases'): aliases = rendererClass.getFormatAliases() for alias in aliases: self._format_mappings[alias.lower()] = formatName.lower() logger.debug(f"Registered {rendererClass.__name__} for formats: {supportedFormats}") except Exception as e: logger.error(f"Error registering renderer {rendererClass.__name__}: {str(e)}") def getRenderer(self, outputFormat: str, services=None) -> Optional[BaseRenderer]: """Get a renderer instance for the specified format.""" if not self._discovered: self.discoverRenderers() # Normalize format name formatName = outputFormat.lower().strip() # Check for aliases first if formatName in self._format_mappings: formatName = self._format_mappings[formatName] # Get renderer class rendererClass = self._renderers.get(formatName) if rendererClass: try: return rendererClass(services=services) except Exception as e: logger.error(f"Error creating renderer instance for {formatName}: {str(e)}") return None logger.warning(f"No renderer found for format: {outputFormat}") return None def getSupportedFormats(self) -> List[str]: """Get list of all supported formats.""" if not self._discovered: self.discoverRenderers() formats = list(self._renderers.keys()) formats.extend(self._format_mappings.keys()) return sorted(set(formats)) def getRendererInfo(self) -> Dict[str, Dict[str, str]]: """Get information about all registered renderers.""" if not self._discovered: self.discoverRenderers() info = {} for formatName, rendererClass in self._renderers.items(): info[formatName] = { 'class_name': rendererClass.__name__, 'module': rendererClass.__module__, 'description': getattr(rendererClass, '__doc__', 'No description').strip().split('\n')[0] if rendererClass.__doc__ else 'No description' } return info # Global registry instance _registry = RendererRegistry() def getRenderer(outputFormat: str, services=None) -> Optional[BaseRenderer]: """Get a renderer instance for the specified format.""" return _registry.getRenderer(outputFormat, services) def getSupportedFormats() -> List[str]: """Get list of all supported formats.""" return _registry.getSupportedFormats() def getRendererInfo() -> Dict[str, Dict[str, str]]: """Get information about all registered renderers.""" return _registry.getRendererInfo()