154 lines
6 KiB
Python
154 lines
6 KiB
Python
"""
|
|
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 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)
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Could not load renderer from {module_name}: {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 _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()
|