300 lines
10 KiB
Python
300 lines
10 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] = []
|
|
|
|
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 encontradas")
|
|
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()
|
|
|
|
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)
|
|
|
|
# Registrar función Helper si existe
|
|
if hasattr(class_obj, 'Helper') and callable(class_obj.Helper):
|
|
self.helper_functions.append(class_obj.Helper)
|
|
|
|
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(),
|
|
'class_count': len(self.registered_classes),
|
|
'bracket_count': len(self.bracket_classes)
|
|
}
|
|
|
|
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()
|
|
|
|
|
|
# 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 create_types_directory_structure():
|
|
"""
|
|
Crea la estructura básica del directorio de tipos
|
|
"""
|
|
types_dir = Path("custom_types")
|
|
types_dir.mkdir(exist_ok=True)
|
|
|
|
# Crear __init__.py
|
|
init_file = types_dir / "__init__.py"
|
|
if not init_file.exists():
|
|
init_file.write_text('''"""
|
|
Directorio de tipos personalizados para la Calculadora MAV
|
|
|
|
Cada archivo *_type.py en este directorio debe definir:
|
|
|
|
def register_classes_in_module():
|
|
"""Devuelve una lista de clases definidas en este módulo para ser registradas."""
|
|
return [
|
|
("NombrePublico", ClaseObjeto, "SympyClassBase", {"add_lowercase": True}),
|
|
# ... más clases
|
|
]
|
|
|
|
Categorías soportadas:
|
|
- "ClassBase": Clase base simple
|
|
- "SympyClassBase": Clase híbrida con SymPy
|
|
|
|
Opciones disponibles:
|
|
- "add_lowercase": bool - Añadir versión en minúsculas al contexto
|
|
- "supports_brackets": bool - Forzar soporte de sintaxis con corchetes
|
|
"""
|
|
''')
|
|
|
|
return types_dir
|
|
|
|
|
|
# Función de testing
|
|
def test_type_registry():
|
|
"""Test del sistema de registro de tipos"""
|
|
print("🧪 Test Type Registry System")
|
|
print("=" * 50)
|
|
|
|
# Crear estructura si no existe
|
|
types_dir = create_types_directory_structure()
|
|
print(f"📁 Directorio de tipos: {types_dir.absolute()}")
|
|
|
|
# Crear archivo de ejemplo si no existe
|
|
example_file = types_dir / "example_type.py"
|
|
if not example_file.exists():
|
|
example_content = '''"""
|
|
Ejemplo de archivo de tipo personalizado
|
|
"""
|
|
from class_base import ClassBase
|
|
|
|
class ExampleClass(ClassBase):
|
|
def __init__(self, value):
|
|
super().__init__(value, str(value))
|
|
|
|
@staticmethod
|
|
def Helper(input_str):
|
|
if "Example" in input_str:
|
|
return "Ej: Example[test]"
|
|
return None
|
|
|
|
@staticmethod
|
|
def PopupFunctionList():
|
|
return [("test_method", "Método de prueba")]
|
|
|
|
def register_classes_in_module():
|
|
"""Devuelve clases para registro"""
|
|
return [
|
|
("Example", ExampleClass, "ClassBase", {"add_lowercase": True}),
|
|
]
|
|
'''
|
|
example_file.write_text(example_content)
|
|
print(f"📝 Archivo de ejemplo creado: {example_file}")
|
|
|
|
# Probar descubrimiento
|
|
try:
|
|
registry_info = discover_and_register_types()
|
|
|
|
print(f"✅ Descubrimiento completado:")
|
|
print(f" 📦 Clases registradas: {registry_info['class_count']}")
|
|
print(f" 🔧 Clases con brackets: {registry_info['bracket_count']}")
|
|
print(f" 📋 Contexto base: {len(registry_info['base_context'])} entradas")
|
|
|
|
print("\n📋 Clases encontradas:")
|
|
for name, cls in registry_info['registered_classes'].items():
|
|
print(f" • {name}: {cls.__name__}")
|
|
|
|
print("\n🔧 Clases con sintaxis de corchetes:")
|
|
for name in registry_info['bracket_classes']:
|
|
print(f" • {name}")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error en test: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_type_registry()
|