221 lines
8.8 KiB
Python
221 lines
8.8 KiB
Python
"""
|
|
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()
|