Calc/app/debug_api.py

237 lines
8.2 KiB
Python

#!/usr/bin/env python3
"""
Simple Debug API - Calculadora MAV CAS
API CLI simple que usa el motor de evaluación existente para generar debug traces
sin modificar el código base. Permite debuggear "como si usaras la aplicación"
mediante archivos JSON.
Principio: texto → texto tal como se muestra en la aplicación.
No duplica lógica, solo encapsula llamadas directas.
Uso:
python simple_debug.py debug_input.json
python simple_debug.py debug_input.json --output debug_results.json
python simple_debug.py debug_input.json --verbose
"""
import json
import sys
import argparse
from datetime import datetime
from pathlib import Path
# Importar motores de evaluación
from .evaluation import HybridEvaluationEngine
def run_debug(input_file: str, output_file: str = None, verbose: bool = False):
"""
Ejecuta las queries de debug desde un archivo JSON y guarda los resultados.
Args:
input_file: Archivo JSON con las queries
output_file: Archivo de salida (opcional)
verbose: Modo verboso
"""
# Cargar queries del archivo de entrada
try:
with open(input_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: No se encontró el archivo '{input_file}'")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: JSON inválido en '{input_file}': {e}")
sys.exit(1)
if 'queries' not in data:
print("Error: El archivo JSON debe contener una clave 'queries'")
sys.exit(1)
# Determinar qué motor usar
engine_module = data.get('engine_module', 'main_evaluation')
if verbose:
print(f"Usando motor: {engine_module}")
print("Iniciando motor de evaluación...")
# Crear motor de evaluación según el módulo especificado
if engine_module == 'main_evaluation_puro':
from .evaluation import PureAlgebraicEngine
engine = PureAlgebraicEngine()
else:
# Motor por defecto
engine = HybridEvaluationEngine()
results = []
successful = 0
failed = 0
# Ejecutar cada query
for query in data['queries']:
if verbose:
print(f"Ejecutando query {query.get('index', '?')}: {query.get('content', '')[:50]}...")
try:
if query['type'] == 'input':
# Query de tipo input: evaluar expresión como si fuera entrada del usuario
result = engine.evaluate_line(query['content'])
# Capturar resultado directo sin procesamiento
output = {
'index': query['index'],
'input': query['content'],
'output': str(result.result) if hasattr(result, 'result') else str(result),
'result_type': type(result.result).__name__ if hasattr(result, 'result') else type(result).__name__,
'success': not (hasattr(result, 'is_error') and result.is_error),
'error': result.error if hasattr(result, 'is_error') and result.is_error else None
}
if not (hasattr(result, 'is_error') and result.is_error):
successful += 1
else:
failed += 1
elif query['type'] == 'exec':
# Query de tipo exec: evaluar usando el motor adecuado
if engine_module == 'main_evaluation_puro':
# Para motor puro, evaluar directamente
result = engine.evaluate_line(query['content'])
output = {
'index': query['index'],
'input': query['content'],
'output': result.output,
'result_type': result.result_type,
'success': result.success,
'error': result.error_message
}
if result.success:
successful += 1
else:
failed += 1
else:
# Motor original: ejecutar código Python para inspeccionar el estado
exec_result = eval(query['content'], {'engine': engine})
output = {
'index': query['index'],
'input': query['content'],
'output': str(exec_result),
'result_type': type(exec_result).__name__,
'success': True,
'error': None,
'exec_result': exec_result
}
successful += 1
else:
raise ValueError(f"Tipo de query desconocido: {query['type']}")
except Exception as e:
# Manejar errores en la ejecución
output = {
'index': query['index'],
'input': query['content'],
'output': None,
'result_type': None,
'success': False,
'error': str(e)
}
failed += 1
if verbose:
print(f" Error: {str(e)}")
results.append(output)
# Preparar salida final
final_output = {
'execution_info': {
'timestamp': datetime.now().isoformat() + 'Z',
'total_queries': len(data['queries']),
'successful': successful,
'failed': failed,
'input_file': input_file
},
'results': results
}
# Determinar archivo de salida
if output_file is None:
base_name = Path(input_file).stem
output_file = f"{base_name}_results.json"
# Guardar resultados
try:
# Serialización simple con fallback a string
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(final_output, f, indent=2, ensure_ascii=False, default=str)
if verbose:
print(f"\nResultados guardados en: {output_file}")
print(f"Queries exitosas: {successful}")
print(f"Queries con error: {failed}")
except Exception as e:
print(f"Error al guardar resultados: {e}")
sys.exit(1)
return final_output
def main():
"""Función principal del CLI"""
parser = argparse.ArgumentParser(
description='Simple Debug API para Calculadora MAV CAS',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Ejemplos de uso:
python simple_debug.py debug_input.json
python simple_debug.py debug_input.json --output custom_results.json
python simple_debug.py debug_input.json --verbose
"""
)
parser.add_argument('input_file',
help='Archivo JSON con las queries de debug')
parser.add_argument('--output', '-o',
help='Archivo de salida (por defecto: <input>_results.json)')
parser.add_argument('--verbose', '-v',
action='store_true',
help='Modo verboso')
args = parser.parse_args()
# Verificar que el archivo de entrada existe
if not Path(args.input_file).exists():
print(f"Error: El archivo '{args.input_file}' no existe")
sys.exit(1)
# Ejecutar debug
try:
results = run_debug(args.input_file, args.output, args.verbose)
# Mostrar resumen si no es modo verboso
if not args.verbose:
info = results['execution_info']
print(f"Debug completado: {info['successful']}/{info['total_queries']} exitosas")
if args.output:
print(f"Resultados en: {args.output}")
else:
base_name = Path(args.input_file).stem
print(f"Resultados en: {base_name}_results.json")
except KeyboardInterrupt:
print("\nInterrumpido por el usuario")
sys.exit(1)
except Exception as e:
print(f"Error inesperado: {e}")
sys.exit(1)
if __name__ == '__main__':
main()