gateway/modules/services/serviceGeneration/renderers/registry.py

186 lines
7.2 KiB
Python

# 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
def getOutputStyle(self, outputFormat: str) -> Optional[str]:
"""
Get the output style classification for a given format.
Returns: 'code', 'document', 'image', or other (e.g., 'video' for future use)
"""
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 and call getOutputStyle (all renderers have same signature)
rendererClass = self._renderers.get(formatName)
try:
return rendererClass.getOutputStyle(formatName)
except (AttributeError, TypeError) as e:
logger.warning(f"No renderer found for format: {outputFormat}, cannot determine output style")
return None
except Exception as e:
logger.warning(f"Error getting output style for {outputFormat}: {str(e)}")
return None
# 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()
def getOutputStyle(outputFormat: str) -> Optional[str]:
"""Get the output style classification for a given format."""
return _registry.getOutputStyle(outputFormat)