""" Sistema de auto-descubrimiento y registro de clases personalizadas """ import os import importlib.util import inspect from pathlib import Path from typing import List, Tuple, Dict, Any, Type, Optional import logging # Configurar logging para debugging logger = logging.getLogger(__name__) class TypeRegistry: """ Sistema centralizado para el registro automático de clases personalizadas """ def __init__(self, types_directory: str = "custom_types"): self.types_directory = Path(types_directory) self.registered_classes: Dict[str, Type] = {} self.bracket_classes: set = set() self.base_context: Dict[str, Any] = {} self.helper_functions: List[callable] = [] self.tokenization_patterns: List[Dict] = [] # NUEVO: patrones de tokenización def discover_and_register_all(self) -> Dict[str, Any]: """ Descubre y registra todas las clases en el directorio de tipos Returns: Dict con toda la información registrada """ if not self.types_directory.exists(): logger.warning(f"Directorio de tipos no encontrado: {self.types_directory}") return self._get_registry_info() # Limpiar registros previos self._clear_registries() # Escanear archivos *_type.py type_files = list(self.types_directory.glob("*_type.py")) for type_file in type_files: try: self._process_type_file(type_file) except Exception as e: logger.error(f"Error procesando {type_file}: {e}") continue logger.info(f"Registro completado: {len(self.registered_classes)} clases, {len(self.tokenization_patterns)} patrones") return self._get_registry_info() def _clear_registries(self): """Limpia todos los registros""" self.registered_classes.clear() self.bracket_classes.clear() self.base_context.clear() self.helper_functions.clear() self.tokenization_patterns.clear() # NUEVO def _process_type_file(self, type_file: Path): """Procesa un archivo de tipo individual""" module_name = type_file.stem # Importar dinámicamente el módulo spec = importlib.util.spec_from_file_location(module_name, type_file) if not spec or not spec.loader: logger.error(f"No se pudo cargar spec para {type_file}") return module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # Buscar función de registro if hasattr(module, 'register_classes_in_module'): class_info_list = module.register_classes_in_module() self._register_classes_from_info(class_info_list, module_name) else: # Fallback: buscar clases automáticamente logger.warning(f"{type_file} no tiene register_classes_in_module(), usando auto-detección") self._auto_detect_classes(module, module_name) def _register_classes_from_info(self, class_info_list: List[Tuple], module_name: str): """ Registra clases basándose en la información proporcionada class_info_list: Lista de tuplas (nombre_publico, clase_objeto, categoria, [opciones]) """ for class_info in class_info_list: try: if len(class_info) >= 3: name, class_obj, category = class_info[:3] options = class_info[3] if len(class_info) > 3 else {} else: logger.error(f"Formato incorrecto en {module_name}: {class_info}") continue self._register_single_class(name, class_obj, category, options, module_name) except Exception as e: logger.error(f"Error registrando clase en {module_name}: {e}") def _register_single_class(self, name: str, class_obj: Type, category: str, options: Dict, module_name: str): """Registra una clase individual""" # Registro básico self.registered_classes[name] = class_obj # Añadir al contexto base (siempre) self.base_context[name] = class_obj # Añadir versión en minúsculas si se especifica if options.get('add_lowercase', True): self.base_context[name.lower()] = class_obj # Registrar en bracket_classes si es apropiado if category in ['ClassBase', 'SympyClassBase'] or options.get('supports_brackets', False): self.bracket_classes.add(name) # NUEVA FUNCIONALIDAD: También agregar versión en minúscula a bracket_classes if options.get('add_lowercase', True): self.bracket_classes.add(name.lower()) # Registrar función Helper si existe if hasattr(class_obj, 'Helper') and callable(class_obj.Helper): self.helper_functions.append(class_obj.Helper) # NUEVO: Registrar patrones de tokenización si existen if hasattr(class_obj, 'get_tokenization_patterns') and callable(class_obj.get_tokenization_patterns): try: patterns = class_obj.get_tokenization_patterns() if patterns: self.tokenization_patterns.extend(patterns) logger.debug(f"Patrones de tokenización añadidos desde {name}: {len(patterns)}") except Exception as e: logger.warning(f"Error obteniendo patrones de tokenización de {name}: {e}") logger.debug(f"Registrada: {name} ({category}) desde {module_name}") def _auto_detect_classes(self, module, module_name: str): """Auto-detección de clases cuando no hay función de registro""" for name, obj in inspect.getmembers(module, inspect.isclass): if name.startswith('Class_') or name.endswith('Mask'): # Detectar categoría category = "SympyClassBase" if hasattr(obj, '_sympystr') else "ClassBase" # Usar nombre sin prefijo Class_ public_name = name.replace('Class_', '') if name.startswith('Class_') else name self._register_single_class(public_name, obj, category, {}, module_name) def _get_registry_info(self) -> Dict[str, Any]: """Retorna información completa del registro""" return { 'base_context': self.base_context.copy(), 'bracket_classes': self.bracket_classes.copy(), 'helper_functions': self.helper_functions.copy(), 'registered_classes': self.registered_classes.copy(), 'tokenization_patterns': self.tokenization_patterns.copy(), # NUEVO 'class_count': len(self.registered_classes), 'bracket_count': len(self.bracket_classes), 'pattern_count': len(self.tokenization_patterns) # NUEVO } def get_base_context(self) -> Dict[str, Any]: """Retorna contexto base para el motor de evaluación""" return self.base_context.copy() def get_bracket_classes(self) -> set: """Retorna set de clases que soportan sintaxis con corchetes""" return self.bracket_classes.copy() def get_helper_functions(self) -> List[callable]: """Retorna lista de funciones Helper""" return self.helper_functions.copy() def get_tokenization_patterns(self) -> List[Dict]: """NUEVO: Retorna lista de patrones de tokenización""" return self.tokenization_patterns.copy() # Instancia global del registro _global_registry = TypeRegistry() def discover_and_register_types(types_directory: str = "custom_types") -> Dict[str, Any]: """ Función principal para descubrir y registrar todos los tipos Args: types_directory: Directorio donde están los archivos *_type.py Returns: Dict con información del registro """ global _global_registry _global_registry = TypeRegistry(types_directory) return _global_registry.discover_and_register_all() def get_registered_base_context() -> Dict[str, Any]: """Obtiene el contexto base con todas las clases registradas""" return _global_registry.get_base_context() def get_registered_bracket_classes() -> set: """Obtiene las clases que soportan sintaxis con corchetes""" return _global_registry.get_bracket_classes() def get_registered_helper_functions() -> List[callable]: """Obtiene las funciones Helper registradas""" return _global_registry.get_helper_functions() def get_registered_tokenization_patterns() -> List[Dict]: """NUEVO: Obtiene los patrones de tokenización registrados""" return _global_registry.get_tokenization_patterns()