From c759a756be062a98de42bf7ddb0559b9ddbc207e Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 3 May 2025 21:03:33 +0200 Subject: [PATCH] config_manager dividido en partes para que no sea tan grande --- __pycache__/config_manager.cpython-312.pyc | Bin 44746 -> 10788 bytes app.py | 57 +- .../ObtainIOFromProjectTia/data.json | 1 + .../XML Parser to SCL/log_x1_to_json.txt | 15 + .../script_groups/example_group/log_x1.txt | 6 +- .../script_groups/example_group/log_x2.txt | 10 +- claude_file_organizer.py | 171 --- config_manager.py | 1010 ----------------- data/esquema.json | 20 - data/esquema_general.json | 18 +- data/log.txt | 44 +- 11 files changed, 88 insertions(+), 1264 deletions(-) create mode 100644 backend/script_groups/ObtainIOFromProjectTia/data.json create mode 100644 backend/script_groups/XML Parser to SCL/log_x1_to_json.txt delete mode 100644 claude_file_organizer.py delete mode 100644 config_manager.py delete mode 100644 data/esquema.json diff --git a/__pycache__/config_manager.cpython-312.pyc b/__pycache__/config_manager.cpython-312.pyc index 4a46e5dd175f7ce7c76c4e5dd47f4b1aaa92aef8..648a9a6dac702a5e6295e17467e25955e5c99bd3 100644 GIT binary patch literal 10788 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 literal 44746 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", methods=["GET", "POST"]) def handle_group_description(group): - description_path = os.path.join( - config_manager.script_groups_path, group, "description.json" - ) - if request.method == "GET": try: - with open(description_path, "r", encoding="utf-8") as f: - return jsonify(json.load(f)) - except FileNotFoundError: - return jsonify( - { - "name": group, - "description": "Sin descripción", - "version": "1.0", - "author": "Unknown", - } - ) + details = config_manager.get_group_details(group) + if "error" in details: + return jsonify(details), 404 # Group not found + return jsonify(details) + except Exception as e: + return jsonify({"status": "error", "message": str(e)}), 500 else: # POST try: data = request.json - os.makedirs(os.path.dirname(description_path), exist_ok=True) - with open(description_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, ensure_ascii=False) - return jsonify({"status": "success"}) + result = config_manager.update_group_description(group, data) + return jsonify(result) except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 diff --git a/backend/script_groups/ObtainIOFromProjectTia/data.json b/backend/script_groups/ObtainIOFromProjectTia/data.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/backend/script_groups/ObtainIOFromProjectTia/data.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/backend/script_groups/XML Parser to SCL/log_x1_to_json.txt b/backend/script_groups/XML Parser to SCL/log_x1_to_json.txt new file mode 100644 index 0000000..e28223b --- /dev/null +++ b/backend/script_groups/XML Parser to SCL/log_x1_to_json.txt @@ -0,0 +1,15 @@ +--- Log de Ejecución: x1_to_json.py --- +Grupo: XML Parser to SCL +Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport +Inicio: 2025-05-03 20:08:18 +Fin: 2025-05-03 20:08:22 +Duración: 0:00:03.850097 +Estado: SUCCESS (Código de Salida: 0) + +--- SALIDA ESTÁNDAR (STDOUT) --- +Por favor, selecciona el archivo XML de entrada... + +--- ERRORES (STDERR) --- +No se seleccionó ningún archivo. Saliendo. + +--- FIN DEL LOG --- diff --git a/backend/script_groups/example_group/log_x1.txt b/backend/script_groups/example_group/log_x1.txt index cad1585..193c09e 100644 --- a/backend/script_groups/example_group/log_x1.txt +++ b/backend/script_groups/example_group/log_x1.txt @@ -1,9 +1,9 @@ --- Log de Ejecución: x1.py --- Grupo: example_group Directorio de Trabajo: C:\Estudio -Inicio: 2025-05-03 17:26:22 -Fin: 2025-05-03 17:26:27 -Duración: 0:00:05.167151 +Inicio: 2025-05-03 20:54:12 +Fin: 2025-05-03 20:54:17 +Duración: 0:00:05.196719 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- diff --git a/backend/script_groups/example_group/log_x2.txt b/backend/script_groups/example_group/log_x2.txt index 8bbea37..e5ef7f7 100644 --- a/backend/script_groups/example_group/log_x2.txt +++ b/backend/script_groups/example_group/log_x2.txt @@ -1,9 +1,9 @@ --- Log de Ejecución: x2.py --- Grupo: example_group Directorio de Trabajo: C:\Estudio -Inicio: 2025-05-03 17:26:13 -Fin: 2025-05-03 17:26:14 -Duración: 0:00:01.149705 +Inicio: 2025-05-03 20:48:23 +Fin: 2025-05-03 20:48:27 +Duración: 0:00:03.208350 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- @@ -11,6 +11,10 @@ Estado: SUCCESS (Código de Salida: 0) Iniciando análisis de datos simulado... Analizando lote 1... +Lote 1 completado exitosamente +Analizando lote 2... +Lote 2 completado exitosamente +Analizando lote 3... ERROR: Error simulado en el procesamiento El proceso se detuvo debido a un error diff --git a/claude_file_organizer.py b/claude_file_organizer.py deleted file mode 100644 index 2889dae..0000000 --- a/claude_file_organizer.py +++ /dev/null @@ -1,171 +0,0 @@ -import os -import shutil -from pathlib import Path -import re - -class ClaudeProjectOrganizer: - def __init__(self): - self.source_dir = Path.cwd() - self.claude_dir = self.source_dir / 'claude' - self.file_mapping = {} - - def should_skip_directory(self, dir_name): - skip_dirs = {'.git', '__pycache__', 'venv', 'env', '.pytest_cache', '.vscode', 'claude'} - return dir_name in skip_dirs - - def get_comment_prefix(self, file_extension): - """Determina el prefijo de comentario según la extensión del archivo""" - comment_styles = { - '.py': '#', - '.js': '//', - '.css': '/*', - '.html': '', - } - return comment_suffixes.get(file_extension.lower(), '') - - def normalize_path(self, path_str: str) -> str: - """Normaliza la ruta usando forward slashes""" - return str(path_str).replace('\\', '/') - - def check_existing_path_comment(self, content: str, normalized_path: str, comment_prefix: str) -> bool: - """Verifica si ya existe un comentario con la ruta en el archivo""" - # Escapar caracteres especiales en el prefijo de comentario para regex - escaped_prefix = re.escape(comment_prefix) - - # Crear patrones para buscar tanto forward como backward slashes - forward_pattern = f"{escaped_prefix}\\s*{re.escape(normalized_path)}\\b" - backward_path = normalized_path.replace('/', '\\\\') # Doble backslash para el patrón - backward_pattern = f"{escaped_prefix}\\s*{re.escape(backward_path)}" - - # Buscar en las primeras líneas del archivo - first_lines = content.split('\n')[:5] - for line in first_lines: - if (re.search(forward_pattern, line) or - re.search(backward_pattern, line)): - return True - return False - - def add_path_comment(self, file_path: Path, content: str) -> str: - """Agrega un comentario con la ruta al inicio del archivo si no existe""" - relative_path = file_path.relative_to(self.source_dir) - normalized_path = self.normalize_path(relative_path) - comment_prefix = self.get_comment_prefix(file_path.suffix) - - if comment_prefix is None: - return content - - comment_suffix = self.get_comment_suffix(file_path.suffix) - - # Verificar si ya existe el comentario - if self.check_existing_path_comment(content, normalized_path, comment_prefix): - print(f" - Comentario de ruta ya existe en {file_path}") - return content - - path_comment = f"{comment_prefix} {normalized_path}{comment_suffix}\n" - - # Para archivos HTML, insertar después del doctype si existe - if file_path.suffix.lower() == '.html': - if content.lower().startswith('') + 1 - return content[:doctype_end] + '\n' + path_comment + content[doctype_end:] - - return path_comment + content - - def clean_claude_directory(self): - if self.claude_dir.exists(): - shutil.rmtree(self.claude_dir) - self.claude_dir.mkdir() - print(f"Directorio claude limpiado: {self.claude_dir}") - - def copy_files(self): - self.clean_claude_directory() - - for root, dirs, files in os.walk(self.source_dir): - dirs[:] = [d for d in dirs if not self.should_skip_directory(d)] - current_path = Path(root) - - for file in files: - file_path = current_path / file - - if file.endswith(('.py', '.js', '.css', '.html', '.json', '.yml', '.yaml', - '.tsx', '.ts', '.jsx', '.scss', '.less')): - target_path = self.claude_dir / file - - # Si el archivo ya existe en el directorio claude, agregar un sufijo numérico - if target_path.exists(): - base = target_path.stem - ext = target_path.suffix - counter = 1 - while target_path.exists(): - target_path = self.claude_dir / f"{base}_{counter}{ext}" - counter += 1 - - try: - # Leer el contenido del archivo - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - - # Agregar el comentario con la ruta si no existe - modified_content = self.add_path_comment(file_path, content) - - # Escribir el nuevo contenido - with open(target_path, 'w', encoding='utf-8', newline='\n') as f: - f.write(modified_content) - - self.file_mapping[str(file_path)] = target_path.name - print(f"Copiado: {file_path} -> {target_path}") - - except UnicodeDecodeError: - print(f"Advertencia: No se pudo procesar {file_path} como texto. Copiando sin modificar...") - shutil.copy2(file_path, target_path) - except Exception as e: - print(f"Error procesando {file_path}: {str(e)}") - - def generate_tree_report(self): - """Genera el reporte en formato árbol visual""" - report = ["Estructura del proyecto original:\n"] - - def add_to_report(path, prefix="", is_last=True): - report.append(prefix + ("└── " if is_last else "├── ") + path.name) - - if path.is_dir() and not self.should_skip_directory(path.name): - children = sorted(path.iterdir(), key=lambda x: (x.is_file(), x.name)) - children = [c for c in children if not (c.is_dir() and self.should_skip_directory(c.name))] - - for i, child in enumerate(children): - is_last_child = i == len(children) - 1 - new_prefix = prefix + (" " if is_last else "│ ") - add_to_report(child, new_prefix, is_last_child) - - add_to_report(self.source_dir) - - report_path = self.claude_dir / "project_structure.txt" - with open(report_path, "w", encoding="utf-8") as f: - f.write("\n".join(report)) - print(f"\nReporte generado en: {report_path}") - -def main(): - try: - print("Iniciando organización de archivos para Claude...") - organizer = ClaudeProjectOrganizer() - organizer.copy_files() - organizer.generate_tree_report() - print("\n¡Proceso completado exitosamente!") - except Exception as e: - print(f"\nError durante la ejecución: {str(e)}") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/config_manager.py b/config_manager.py deleted file mode 100644 index 7e868e1..0000000 --- a/config_manager.py +++ /dev/null @@ -1,1010 +0,0 @@ -import os -import json -import subprocess -import re -import traceback -from typing import Dict, Any, List, Optional -import sys # Import sys to check the platform -import time # Add this import -from datetime import datetime # Add this import - - -# --- ConfigurationManager Class --- -class ConfigurationManager: - def __init__(self): - self.base_path = os.path.dirname(os.path.abspath(__file__)) - self.data_path = os.path.join(self.base_path, "data") - self.script_groups_path = os.path.join( - self.base_path, "backend", "script_groups" - ) - self.working_directory = None - self.log_file = os.path.join(self.data_path, "log.txt") - self._init_log_file() - self.last_execution_time = 0 # Add this attribute - # Minimum seconds between script executions to prevent rapid clicks - self.min_execution_interval = 1 # Minimum seconds between executions - - def _init_log_file(self): - """Initialize log file if it doesn't exist""" - if not os.path.exists(self.data_path): - os.makedirs(self.data_path) - if not os.path.exists(self.log_file): - with open(self.log_file, "w", encoding="utf-8") as f: - f.write("") - - # --- Logging Methods --- - def append_log(self, message: str) -> None: - """Append a message to the CENTRAL log file with timestamp.""" - # This function now primarily logs messages from the app itself, - # script output is handled separately in execute_script. - try: - timestamp = datetime.now().strftime("[%H:%M:%S] ") - lines = message.split("\n") - lines_with_timestamp = [] - for line in lines: - if line.strip(): - # Add timestamp only if line doesn't already have one (e.g., from script output) - if not line.strip().startswith("["): - line = f"{timestamp}{line}" - lines_with_timestamp.append(f"{line}\n") - - if lines_with_timestamp: - with open(self.log_file, "a", encoding="utf-8") as f: - f.writelines(lines_with_timestamp) - except Exception as e: - print(f"Error writing to central log file: {e}") - - def read_last_log_line(self) -> str: - """Read the last line from the log file.""" - try: - with open(self.log_file, "r", encoding="utf-8") as f: - # Leer las últimas líneas y encontrar la última no vacía - lines = f.readlines() - for line in reversed(lines): - if line.strip(): - return line - return "" - except Exception as e: - print(f"Error reading last log line: {e}") - return "" - - def read_log(self) -> str: - """Read the entire log file""" - try: - with open(self.log_file, "r", encoding="utf-8") as f: - return f.read() - except Exception as e: - print(f"Error reading log file: {e}") - return "" - - def clear_log(self) -> bool: - """Clear the log file""" - try: - with open(self.log_file, "w", encoding="utf-8") as f: - f.write("") - return True - except Exception as e: - print(f"Error clearing log file: {e}") - return False - - # --- Working Directory Methods --- - def set_working_directory(self, path: str) -> Dict[str, str]: - """Set and validate working directory.""" - if not os.path.exists(path): - return {"status": "error", "message": "Directory does not exist"} - - self.working_directory = path - - # Create default data.json if it doesn't exist - # This data.json will be populated with defaults by get_config later if needed - data_path = os.path.join(path, "data.json") - if not os.path.exists(data_path): - try: - with open(data_path, "w", encoding="utf-8") as f: - json.dump({}, f, indent=2) - print( - f"Info: Created empty data.json in working directory: {data_path}" - ) - except Exception as e: - print(f"Error creating data.json in working directory {path}: {e}") - # Non-fatal, get_config will handle missing file - - return {"status": "success", "path": path} - - def get_work_dir(self, group: str) -> Optional[str]: - """Get working directory path for a script group from work_dir.json.""" - work_dir_path = os.path.join(self.script_groups_path, group, "work_dir.json") - try: - with open(work_dir_path, "r", encoding="utf-8") as f: - data = json.load(f) - path = data.get("path", "") - # Normalizar separadores de ruta - if path: - path = os.path.normpath(path) - # Actualizar la variable de instancia si hay una ruta válida y existe - if path and os.path.isdir(path): # Check if it's a directory - self.working_directory = path - return path - elif path: - print( - f"Warning: Stored working directory for group '{group}' is invalid or does not exist: {path}" - ) - self.working_directory = None # Reset if invalid - return None - else: - self.working_directory = None # Reset if no path stored - return None - except (FileNotFoundError, json.JSONDecodeError): - self.working_directory = None # Reset if file missing or invalid - return None - except Exception as e: - print(f"Error reading work_dir.json for group '{group}': {e}") - self.working_directory = None - return None - - def get_directory_history(self, group: str) -> List[str]: - """Get the directory history for a script group.""" - work_dir_path = os.path.join(self.script_groups_path, group, "work_dir.json") - try: - with open(work_dir_path, "r", encoding="utf-8") as f: - data = json.load(f) - # Normalizar todos los paths en el historial - history = [os.path.normpath(p) for p in data.get("history", [])] - # Filtrar solo directorios que existen - return [ - p for p in history if os.path.isdir(p) - ] # Check if directory exists - except (FileNotFoundError, json.JSONDecodeError): - return [] - - def get_script_groups(self) -> List[Dict[str, Any]]: - """Returns list of available script groups with their descriptions.""" - groups = [] - for d in os.listdir(self.script_groups_path): - group_path = os.path.join(self.script_groups_path, d) - if os.path.isdir(group_path): - description = self._get_group_description(group_path) - groups.append( - { - "id": d, - "name": description.get("name", d), - "description": description.get( - "description", "Sin descripción" - ), - "version": description.get("version", "1.0"), - "author": description.get("author", "Unknown"), - } - ) - return groups - - def _get_group_description(self, group_path: str) -> Dict[str, Any]: - """Get description for a script group.""" - description_file = os.path.join(group_path, "description.json") - try: - if os.path.exists(description_file): - with open(description_file, "r", encoding="utf-8") as f: - return json.load(f) - except Exception as e: - print(f"Error reading group description: {e}") - return {} - - # --- Configuration (data.json) Methods --- - def get_config(self, level: str, group: str = None) -> Dict[str, Any]: - """ - Get configuration for specified level. - Applies default values from the corresponding schema if the config - file doesn't exist or is missing keys with defaults. - """ - config_data = {} - needs_save = False - schema = None - data_path = None - schema_path_for_debug = "N/A" # For logging - - # 1. Determine data path based on level - if level == "1": - data_path = os.path.join(self.data_path, "data.json") - schema_path_for_debug = os.path.join(self.data_path, "esquema_general.json") - elif level == "2": - if not group: - return {"error": "Group required for level 2 config"} - data_path = os.path.join(self.script_groups_path, group, "data.json") - schema_path_for_debug = os.path.join( - self.script_groups_path, group, "esquema_group.json" - ) - elif level == "3": - # Level 3 config is always in the current working directory - if not self.working_directory: - return {} # Return empty config if working directory not set - data_path = os.path.join(self.working_directory, "data.json") - # Level 3 config might be based on level 3 schema (esquema_work.json) - if group: - schema_path_for_debug = os.path.join( - self.script_groups_path, group, "esquema_work.json" - ) - else: - # If no group, we can't determine the L3 schema for defaults. - schema_path_for_debug = "N/A (Level 3 without group)" - else: - return {"error": f"Invalid level specified for config: {level}"} - - # 2. Get the corresponding schema to check for defaults - try: - # Only attempt to load schema if needed (e.g., not L3 without group) - if not (level == "3" and not group): - schema = self.get_schema( - level, group - ) # Use the robust get_schema method - else: - schema = None # Cannot determine L3 schema without group - except Exception as e: - print( - f"Warning: Could not load schema for level {level}, group {group}. Defaults will not be applied. Error: {e}" - ) - schema = None # Ensure schema is None if loading failed - - # 3. Try to load existing data - data_file_exists = os.path.exists(data_path) - if data_file_exists: - try: - with open(data_path, "r", encoding="utf-8") as f_data: - content = f_data.read() - if content.strip(): - config_data = json.loads(content) - else: - print( - f"Warning: Data file {data_path} is empty. Will initialize with defaults." - ) - needs_save = True # Force save if file was empty - except json.JSONDecodeError: - print( - f"Warning: Could not decode JSON from {data_path}. Will initialize with defaults." - ) - config_data = {} - needs_save = True - except Exception as e: - print( - f"Error reading data from {data_path}: {e}. Will attempt to initialize with defaults." - ) - config_data = {} - needs_save = True - except FileNotFoundError: - print( - f"Info: Data file not found at {data_path}. Will initialize with defaults." - ) - needs_save = True # Mark for saving as it's a new file - - # 4. Apply defaults from schema if schema was loaded successfully - if schema and isinstance(schema, dict) and "properties" in schema: - schema_properties = schema.get("properties", {}) - if isinstance(schema_properties, dict): # Ensure properties is a dict - for key, prop_definition in schema_properties.items(): - # Ensure prop_definition is a dictionary before checking 'default' - if ( - isinstance(prop_definition, dict) - and key not in config_data - and "default" in prop_definition - ): - print( - f"Info: Applying default for '{key}' from schema {schema_path_for_debug}" - ) - config_data[key] = prop_definition["default"] - needs_save = ( - True # Mark for saving because a default was applied - ) - else: - print( - f"Warning: 'properties' in schema {schema_path_for_debug} is not a dictionary. Cannot apply defaults." - ) - - # 5. Save the file if it was created or updated with defaults - if needs_save and data_path: - try: - print(f"Info: Saving updated config data to: {data_path}") - os.makedirs(os.path.dirname(data_path), exist_ok=True) - with open(data_path, "w", encoding="utf-8") as f_data: - json.dump(config_data, f_data, indent=2, ensure_ascii=False) - except IOError as e: - print(f"Error: Could not write data file to {data_path}: {e}") - except Exception as e: - print(f"Unexpected error saving data to {data_path}: {e}") - - # 6. Return the final configuration - return config_data - - def update_config( - self, level: str, data: Dict[str, Any], group: str = None - ) -> Dict[str, str]: - """Update configuration for specified level.""" - path = None - if level == "1": - path = os.path.join(self.data_path, "data.json") - elif level == "2": - if not group: - return { - "status": "error", - "message": "Group required for level 2 config update", - } - path = os.path.join(self.script_groups_path, group, "data.json") - elif level == "3": - if not self.working_directory: - return { - "status": "error", - "message": "Working directory not set for level 3 config update", - } - path = os.path.join(self.working_directory, "data.json") - else: - return { - "status": "error", - "message": f"Invalid level for config update: {level}", - } - - try: - # Ensure directory exists - os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, ensure_ascii=False) - print(f"Info: Config successfully updated at {path}") - return {"status": "success"} - except Exception as e: - print(f"Error updating config at {path}: {str(e)}") - return {"status": "error", "message": str(e)} - - def get_schema(self, level: str, group: str = None) -> Dict[str, Any]: - """Get schema for specified level.""" - schema_path = None - try: - # Clean level parameter - clean_level = str(level).split("-")[0] - - # Determine schema path based on level - if clean_level == "1": - schema_path = os.path.join(self.data_path, "esquema_general.json") - elif clean_level == "2": - if not group: - raise ValueError("Group is required for level 2 schema") - schema_path = os.path.join( - self.script_groups_path, group, "esquema_group.json" - ) - elif clean_level == "3": - if not group: - # Level 3 schema (esquema_work) is tied to a group. - # If no group, we can't know which schema to load. - print( - "Warning: Group needed to determine level 3 schema (esquema_work.json). Returning empty schema." - ) - return {"type": "object", "properties": {}} - schema_path = os.path.join( - self.script_groups_path, group, "esquema_work.json" - ) - else: - print( - f"Warning: Invalid level '{level}' for schema retrieval. Returning empty schema." - ) - return {"type": "object", "properties": {}} - - # Read existing schema or create default if it doesn't exist - if os.path.exists(schema_path): - try: - with open(schema_path, "r", encoding="utf-8") as f: - schema = json.load(f) - # Basic validation - if ( - not isinstance(schema, dict) - or "properties" not in schema - or "type" not in schema - ): - print( - f"Warning: Schema file {schema_path} has invalid structure. Returning default." - ) - return {"type": "object", "properties": {}} - # Ensure properties is a dict - if not isinstance(schema.get("properties"), dict): - print( - f"Warning: 'properties' in schema file {schema_path} is not a dictionary. Normalizing." - ) - schema["properties"] = {} - return schema - except json.JSONDecodeError: - print( - f"Error: Could not decode JSON from schema file: {schema_path}. Returning default." - ) - return {"type": "object", "properties": {}} - except Exception as e: - print( - f"Error reading schema file {schema_path}: {e}. Returning default." - ) - return {"type": "object", "properties": {}} - else: - print( - f"Info: Schema file not found at {schema_path}. Creating default schema." - ) - default_schema = {"type": "object", "properties": {}} - try: - # Ensure directory exists before writing - os.makedirs(os.path.dirname(schema_path), exist_ok=True) - with open(schema_path, "w", encoding="utf-8") as f: - json.dump(default_schema, f, indent=2, ensure_ascii=False) - return default_schema - except Exception as e: - print(f"Error creating default schema file at {schema_path}: {e}") - return { - "type": "object", - "properties": {}, - } # Return empty if creation fails - - except ValueError as ve: # Catch specific errors like missing group - print(f"Error getting schema path: {ve}") - return {"type": "object", "properties": {}} - except Exception as e: - # Log the full path in case of unexpected errors - error_path = schema_path if schema_path else f"Level {level}, Group {group}" - print(f"Unexpected error loading schema from {error_path}: {str(e)}") - return {"type": "object", "properties": {}} - - def update_schema( - self, level: str, data: Dict[str, Any], group: str = None - ) -> Dict[str, str]: - """Update schema for specified level and clean corresponding config.""" - schema_path = None - config_path = None - try: - # Clean level parameter if it contains extra info like '-edit' - clean_level = str(level).split("-")[0] - - # Determinar rutas de schema y config - if clean_level == "1": - schema_path = os.path.join(self.data_path, "esquema_general.json") - config_path = os.path.join(self.data_path, "data.json") - elif clean_level == "2": - if not group: - return { - "status": "error", - "message": "Group is required for level 2 schema update", - } - schema_path = os.path.join( - self.script_groups_path, group, "esquema_group.json" - ) - config_path = os.path.join(self.script_groups_path, group, "data.json") - elif clean_level == "3": - if not group: - return { - "status": "error", - "message": "Group is required for level 3 schema update", - } - schema_path = os.path.join( - self.script_groups_path, group, "esquema_work.json" - ) - # Config path depends on whether working_directory is set and valid - config_path = ( - os.path.join(self.working_directory, "data.json") - if self.working_directory - and os.path.isdir(self.working_directory) # Check it's a directory - else None - ) - if not config_path: - print( - f"Warning: Working directory not set or invalid ('{self.working_directory}'). Level 3 config file will not be cleaned." - ) - else: - return {"status": "error", "message": "Invalid level"} - - # Ensure directory exists - os.makedirs(os.path.dirname(schema_path), exist_ok=True) - - # Basic validation and normalization of the schema data being saved - if not isinstance(data, dict): - print( - f"Warning: Invalid schema data received (not a dict). Wrapping in default structure." - ) - data = {"type": "object", "properties": {}} # Reset to default empty - if "type" not in data: - data["type"] = "object" # Ensure type exists - if "properties" not in data or not isinstance(data["properties"], dict): - print( - f"Warning: Invalid or missing 'properties' in schema data. Resetting properties." - ) - data["properties"] = {} # Ensure properties exists and is a dict - - # Write schema - with open(schema_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, ensure_ascii=False) - print(f"Info: Schema successfully updated at {schema_path}") - - # Clean the corresponding config file *if* its path is valid - if config_path: - self._clean_config_for_schema(config_path, data) - else: - print( - f"Info: Config cleaning skipped for level {level} (no valid config path)." - ) - - return {"status": "success"} - - except Exception as e: - error_path = schema_path if schema_path else f"Level {level}, Group {group}" - print(f"Error updating schema at {error_path}: {str(e)}") - # Consider adding traceback here for debugging - print(traceback.format_exc()) - return {"status": "error", "message": str(e)} - - def _clean_config_for_schema( - self, config_path: str, schema: Dict[str, Any] - ) -> None: - """Clean configuration file to match schema structure.""" - # Check existence *before* trying to open - try: - if not os.path.exists(config_path): - print( - f"Info: Config file {config_path} not found for cleaning. Skipping." - ) - return - - # Cargar configuración actual - config = {} - content = "" # Store original content for comparison - with open(config_path, "r", encoding="utf-8") as f: - content = f.read() - if content.strip(): # Avoid error on empty file - config = json.loads(content) - else: - print( - f"Info: Config file {config_path} is empty. Cleaning will result in an empty object." - ) - - # Limpiar configuración recursivamente - cleaned_config = self._clean_object_against_schema(config, schema) - - # Guardar configuración limpia solo si cambió o si el original estaba vacío - # (para evitar escrituras innecesarias) - # Use dumps for reliable comparison, handle potential errors during dumps - try: - original_config_str = json.dumps(config, sort_keys=True) - cleaned_config_str = json.dumps(cleaned_config, sort_keys=True) - except TypeError as te: - print( - f"Warning: Could not serialize config for comparison during clean: {te}. Forcing save." - ) - original_config_str = "" # Force inequality - cleaned_config_str = " " # Force inequality - - if original_config_str != cleaned_config_str or not content.strip(): - print(f"Info: Cleaning config file: {config_path}") - with open(config_path, "w", encoding="utf-8") as f: - json.dump(cleaned_config, f, indent=2, ensure_ascii=False) - else: - print( - f"Info: Config file {config_path} already matches schema. No cleaning needed." - ) - - except json.JSONDecodeError: - print( - f"Error: Could not decode JSON from config file {config_path} during cleaning. Skipping clean." - ) - except IOError as e: - print(f"Error accessing config file {config_path} during cleaning: {e}") - except Exception as e: - print(f"Unexpected error cleaning config {config_path}: {str(e)}") - # Consider adding traceback here - print(traceback.format_exc()) - - def _clean_object_against_schema(self, data: Any, schema: Dict[str, Any]) -> Any: - """Recursively clean data to match schema structure.""" - # Ensure schema is a dictionary, otherwise cannot proceed - if not isinstance(schema, dict): - print( - f"Warning: Invalid schema provided to _clean_object_against_schema (not a dict). Returning data as is: {type(schema)}" - ) - return data - - schema_type = schema.get("type") - - if schema_type == "object": - if not isinstance(data, dict): - # If data is not a dict, but schema expects object, return empty dict - return {} - - # This 'result' and the loop should be inside the 'if schema_type == "object":' block - result = {} - schema_props = schema.get("properties", {}) - # Ensure schema_props is a dictionary - if not isinstance(schema_props, dict): - print( - f"Warning: 'properties' in schema is not a dictionary during cleaning. Returning empty object." - ) - return {} - - for key, value in data.items(): - # Solo mantener campos que existen en el schema - if key in schema_props: - # Recursively clean the value based on the property's schema - # Ensure the property schema itself is a dict before recursing - prop_schema = schema_props[key] - if isinstance(prop_schema, dict): - result[key] = self._clean_object_against_schema( - value, prop_schema - ) - else: - # If property schema is invalid, maybe keep original value or omit? Let's omit. - print( - f"Warning: Schema for property '{key}' is not a dictionary. Omitting from cleaned data." - ) - # Return result should be OUTSIDE the loop, but INSIDE the 'if object' block - return result - - elif schema_type == "array": - if not isinstance(data, list): - - # If data is not a list, but schema expects array, return empty list - return [] - # If schema defines items structure, clean each item - items_schema = schema.get("items") - if isinstance( - items_schema, dict - ): # Check if 'items' schema is a valid dict - return [ - self._clean_object_against_schema(item, items_schema) - for item in data - ] - else: - # If no valid item schema, return list as is (or potentially filter based on basic types if needed) - # Let's return as is for now. - return data # Keep array items as they are if no valid 'items' schema defined - - elif "enum" in schema: - # Ensure enum values are defined as a list - enum_values = schema.get("enum") - if isinstance(enum_values, list): - # If schema has enum, keep data only if it's one of the allowed values - if data in enum_values: - return data - else: - # If value not in enum, return None or potentially the default value if specified? - # For cleaning, returning None or omitting might be safer. Let's return None. - return None # Or consider returning schema.get('default') if cleaning should apply defaults too - else: - # Invalid enum definition, return original data or None? Let's return None. - print( - f"Warning: Invalid 'enum' definition in schema (not a list). Returning None for value '{data}'." - ) - return None - - # For basic types (string, integer, number, boolean, null), just return the data - # We could add type checking here if strict cleaning is needed, - # e.g., return None if type(data) doesn't match schema_type - elif schema_type in ["string", "integer", "number", "boolean", "null"]: - # Optional: Add stricter type check if needed - # expected_type_map = { "string": str, "integer": int, "number": (int, float), "boolean": bool, "null": type(None) } - # expected_types = expected_type_map.get(schema_type) - # if expected_types and not isinstance(data, expected_types): - # print(f"Warning: Type mismatch during cleaning. Expected {schema_type}, got {type(data)}. Returning None.") - # return None # Or schema.get('default') - return data - - # If schema type is unknown or not handled, return data as is - else: - # This case might indicate an issue with the schema definition itself - # print(f"Warning: Unknown or unhandled schema type '{schema_type}' during cleaning. Returning data as is.") - return data - - # --- Script Listing and Execution Methods --- - def list_scripts(self, group: str) -> List[Dict[str, str]]: - """List all scripts in a group with their descriptions.""" - try: - scripts_dir = os.path.join(self.script_groups_path, group) - scripts = [] - - if not os.path.exists(scripts_dir): - print(f"Directory not found: {scripts_dir}") - return [] # Return empty list if group directory doesn't exist - - for file in os.listdir(scripts_dir): - # Modificar la condición para incluir cualquier archivo .py - if file.endswith(".py"): - path = os.path.join(scripts_dir, file) - description = self._extract_script_description(path) - print( - f"Debug: Found script: {file} with description: {description}" - ) # Debug line - scripts.append({"name": file, "description": description}) - - print(f"Debug: Total scripts found in group '{group}': {len(scripts)}") - return scripts - except Exception as e: - print(f"Error listing scripts for group '{group}': {str(e)}") - return [] # Return empty list on error - - def _extract_script_description(self, script_path: str) -> str: - """Extract description from script's docstring or initial comments.""" - try: - with open(script_path, "r", encoding="utf-8") as f: - content = f.read() - - # Try to find docstring - docstring_match = re.search(r'"""(.*?)"""', content, re.DOTALL) - if docstring_match: - return docstring_match.group(1).strip() - - # Try to find initial comment - comment_match = re.search(r"^#\s*(.*?)$", content, re.MULTILINE) - if comment_match: - return comment_match.group(1).strip() - - return "No description available" - except Exception as e: - print(f"Error extracting description from {script_path}: {str(e)}") - return "Error reading script description" - - def execute_script( - self, group: str, script_name: str, broadcast_fn=None - ) -> Dict[str, Any]: - """ - Execute script, broadcast output in real-time, and save final log - to a script-specific file in the script's directory. - """ - current_time = time.time() - time_since_last = current_time - self.last_execution_time - if time_since_last < self.min_execution_interval: - msg = f"Por favor espere {self.min_execution_interval - time_since_last:.1f} segundo(s) más entre ejecuciones" - self.append_log(f"Warning: {msg}") # Log throttling attempt - if broadcast_fn: - broadcast_fn(msg) - return {"status": "throttled", "error": msg} - - self.last_execution_time = current_time - script_path = os.path.join(self.script_groups_path, group, script_name) - script_dir = os.path.dirname(script_path) - script_base_name = os.path.splitext(script_name)[0] - # Define script-specific log file path - script_log_path = os.path.join(script_dir, f"log_{script_base_name}.txt") - - if not os.path.exists(script_path): - msg = f"Error Fatal: Script no encontrado en {script_path}" - self.append_log(msg) - if broadcast_fn: - broadcast_fn(msg) - return {"status": "error", "error": "Script not found"} - - # Get working directory specific to the group - working_dir = self.get_work_dir(group) - if not working_dir: - msg = f"Error Fatal: Directorio de trabajo no configurado o inválido para el grupo '{group}'" - self.append_log(msg) - if broadcast_fn: - broadcast_fn(msg) - return {"status": "error", "error": "Working directory not set"} - # Double check validity (get_work_dir should already do this) - if not os.path.isdir(working_dir): - msg = f"Error Fatal: El directorio de trabajo '{working_dir}' no es válido o no existe." - self.append_log(msg) - if broadcast_fn: - broadcast_fn(msg) - return {"status": "error", "error": "Invalid working directory"} - - # Aggregate configurations using the updated get_config - configs = { - "level1": self.get_config("1"), - "level2": self.get_config("2", group), - "level3": self.get_config( - "3", group - ), # get_config uses self.working_directory - "working_directory": working_dir, - } - print(f"Debug: Aggregated configs for script execution: {configs}") - - config_file_path = os.path.join(script_dir, "script_config.json") - try: - with open(config_file_path, "w", encoding="utf-8") as f: - json.dump(configs, f, indent=2, ensure_ascii=False) - # Don't broadcast config saving unless debugging - # if broadcast_fn: broadcast_fn(f"Configuraciones guardadas en {config_file_path}") - except Exception as e: - msg = f"Error Fatal: No se pudieron guardar las configuraciones temporales en {config_file_path}: {str(e)}" - self.append_log(msg) - if broadcast_fn: - broadcast_fn(msg) - # Optionally return error here if config saving is critical - - stdout_capture = [] - stderr_capture = "" - process = None - start_time = datetime.now() - - try: - if broadcast_fn: - start_msg = f"[{start_time.strftime('%H:%M:%S')}] Iniciando ejecución de {script_name} en {working_dir}..." - broadcast_fn(start_msg) - - # Determine creation flags for subprocess based on OS - creation_flags = 0 - if sys.platform == "win32": - creation_flags = subprocess.CREATE_NO_WINDOW - - # Execute the script - process = subprocess.Popen( - ["python", "-u", script_path], # Added -u for unbuffered output - cwd=working_dir, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - encoding="utf-8", - errors="replace", - bufsize=1, - env=dict(os.environ, PYTHONIOENCODING="utf-8"), - creationflags=creation_flags, # Add this line - ) - - # Real-time stdout reading and broadcasting - while True: - line = process.stdout.readline() - if not line and process.poll() is not None: - break - if line: - cleaned_line = line.rstrip() - stdout_capture.append(cleaned_line) # Store line for final log - if broadcast_fn: - broadcast_fn(cleaned_line) # Broadcast in real-time - - # Wait for process to finish and get return code - return_code = process.wait() - end_time = datetime.now() - duration = end_time - start_time - - # Capture any remaining stderr - stderr_capture = process.stderr.read() - - status = "success" if return_code == 0 else "error" - completion_msg = f"[{end_time.strftime('%H:%M:%S')}] Ejecución de {script_name} finalizada ({status}). Duración: {duration}." - - if stderr_capture: - # Broadcast stderr only if there was an error potentially - if status == "error" and broadcast_fn: - broadcast_fn(f"--- ERRORES ---") - broadcast_fn(stderr_capture.strip()) - broadcast_fn(f"--- FIN ERRORES ---") - # Always include stderr in the final log if present - completion_msg += f" Se detectaron errores (ver log)." - - if broadcast_fn: - broadcast_fn(completion_msg) - - # --- Write to script-specific log file --- - try: - with open(script_log_path, "w", encoding="utf-8") as log_f: - log_f.write(f"--- Log de Ejecución: {script_name} ---\n") - log_f.write(f"Grupo: {group}\n") - log_f.write(f"Directorio de Trabajo: {working_dir}\n") - log_f.write(f"Inicio: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n") - log_f.write(f"Fin: {end_time.strftime('%Y-%m-%d %H:%M:%S')}\n") - log_f.write(f"Duración: {duration}\n") - log_f.write( - f"Estado: {status.upper()} (Código de Salida: {return_code})\n" - ) - log_f.write("\n--- SALIDA ESTÁNDAR (STDOUT) ---\n") - log_f.write("\n".join(stdout_capture)) - log_f.write("\n\n--- ERRORES (STDERR) ---\n") - log_f.write(stderr_capture if stderr_capture else "Ninguno") - log_f.write("\n--- FIN DEL LOG ---\n") - if broadcast_fn: - broadcast_fn(f"Log completo guardado en: {script_log_path}") - print(f"Info: Script log saved to {script_log_path}") - except Exception as log_e: - err_msg = f"Error al guardar el log específico del script en {script_log_path}: {log_e}" - print(err_msg) - if broadcast_fn: - broadcast_fn(err_msg) - # ------------------------------------------ - - return { - "status": status, - "return_code": return_code, - "error": stderr_capture if stderr_capture else None, - "log_file": script_log_path, # Return path to the specific log - } - - except Exception as e: - end_time = datetime.now() - duration = end_time - start_time - error_msg = ( - f"Error inesperado durante la ejecución de {script_name}: {str(e)}" - ) - traceback_info = traceback.format_exc() # Get full traceback - print(error_msg) # Print to console as well - print(traceback_info) - self.append_log( - f"ERROR FATAL: {error_msg}\n{traceback_info}" - ) # Log centrally - - if broadcast_fn: - # Ensure fatal errors are clearly marked in UI - broadcast_fn( - f"[{end_time.strftime('%H:%M:%S')}] ERROR FATAL: {error_msg}" - ) - - # Attempt to write error to script-specific log - try: - with open(script_log_path, "w", encoding="utf-8") as log_f: - log_f.write(f"--- Log de Ejecución: {script_name} ---\n") - log_f.write(f"Grupo: {group}\n") - log_f.write(f"Directorio de Trabajo: {working_dir}\n") - log_f.write(f"Inicio: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n") - log_f.write( - f"Fin: {end_time.strftime('%Y-%m-%d %H:%M:%S')} (Interrumpido por error)\n" - ) - log_f.write(f"Duración: {duration}\n") - log_f.write(f"Estado: FATAL ERROR\n") - log_f.write("\n--- ERROR ---\n") - log_f.write(error_msg + "\n") - log_f.write("\n--- TRACEBACK ---\n") - log_f.write(traceback_info) # Include traceback in log - log_f.write("\n--- FIN DEL LOG ---\n") - except Exception as log_e: - err_msg_log = ( - f"Error adicional al intentar guardar el log de error: {log_e}" - ) - print(err_msg_log) - - return {"status": "error", "error": error_msg, "traceback": traceback_info} - finally: - # Ensure stderr pipe is closed if process exists - if process and process.stderr: - process.stderr.close() - # Ensure stdout pipe is closed if process exists - if process and process.stdout: - process.stdout.close() - - def set_work_dir(self, group: str, path: str) -> Dict[str, str]: - """Set working directory path for a script group and update history.""" - # Normalizar el path recibido - path = os.path.normpath(path) - - if not os.path.exists(path): - return {"status": "error", "message": "Directory does not exist"} - - work_dir_path = os.path.join(self.script_groups_path, group, "work_dir.json") - - try: - # Cargar datos existentes o crear nuevos - try: - with open(work_dir_path, "r", encoding="utf-8") as f: - data = json.load(f) - # Normalizar paths existentes en el historial - if "history" in data: - data["history"] = [os.path.normpath(p) for p in data["history"]] - except (FileNotFoundError, json.JSONDecodeError): - data = {"path": "", "history": []} - - # Actualizar path actual - data["path"] = path - - # Actualizar historial - if "history" not in data: - data["history"] = [] - - # Eliminar la ruta del historial si ya existe (usando path normalizado) - data["history"] = [ - p for p in data["history"] if os.path.normpath(p) != path - ] - - # Agregar la ruta al principio del historial - data["history"].insert(0, path) - - # Mantener solo los últimos 10 directorios - data["history"] = data["history"][:10] - - # Guardar datos actualizados - with open(work_dir_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2) - - # Actualizar la variable de instancia - self.working_directory = path - - # Crear data.json en el directorio de trabajo si no existe - data_path = os.path.join(path, "data.json") - if not os.path.exists(data_path): - with open(data_path, "w", encoding="utf-8") as f: - json.dump({}, f, indent=2) - - return {"status": "success", "path": path} - except Exception as e: - return {"status": "error", "message": str(e)} diff --git a/data/esquema.json b/data/esquema.json deleted file mode 100644 index 5d04917..0000000 --- a/data/esquema.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "object", - "properties": { - "api_key": { - "type": "string", - "title": "API Key", - "description": "Tu clave de API para servicios externos" - }, - "model": { - "type": "string", - "title": "Modelo LLM", - "description": "Modelo de lenguaje a utilizar", - "enum": [ - "gpt-3.5-turbo", - "gpt-4", - "claude-v1" - ] - } - } -} \ No newline at end of file diff --git a/data/esquema_general.json b/data/esquema_general.json index 1c9e43a..5d04917 100644 --- a/data/esquema_general.json +++ b/data/esquema_general.json @@ -1,4 +1,20 @@ { "type": "object", - "properties": {} + "properties": { + "api_key": { + "type": "string", + "title": "API Key", + "description": "Tu clave de API para servicios externos" + }, + "model": { + "type": "string", + "title": "Modelo LLM", + "description": "Modelo de lenguaje a utilizar", + "enum": [ + "gpt-3.5-turbo", + "gpt-4", + "claude-v1" + ] + } + } } \ No newline at end of file diff --git a/data/log.txt b/data/log.txt index 15570d3..4514b26 100644 --- a/data/log.txt +++ b/data/log.txt @@ -1,22 +1,22 @@ -[17:26:22] Iniciando ejecución de x1.py en C:\Estudio... -[17:26:22] === Ejecutando Script de Prueba 1 === -[17:26:22] Configuraciones cargadas: -[17:26:22] Nivel 1: { -[17:26:22] "api_key": "your-api-key-here", -[17:26:22] "model": "gpt-3.5-turbo" -[17:26:22] } -[17:26:22] Nivel 2: { -[17:26:22] "input_dir": "D:/Datos/Entrada", -[17:26:22] "output_dir": "D:/Datos/Salida", -[17:26:22] "batch_size": 50 -[17:26:22] } -[17:26:22] Nivel 3: {} -[17:26:22] Simulando procesamiento... -[17:26:23] Progreso: 20% -[17:26:24] Progreso: 40% -[17:26:25] Progreso: 60% -[17:26:26] Progreso: 80% -[17:26:27] Progreso: 100% -[17:26:27] ¡Proceso completado! -[17:26:27] Ejecución de x1.py finalizada (success). Duración: 0:00:05.167151. -[17:26:27] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\example_group\log_x1.txt +[20:54:12] Iniciando ejecución de x1.py en C:\Estudio... +[20:54:12] === Ejecutando Script de Prueba 1 === +[20:54:12] Configuraciones cargadas: +[20:54:12] Nivel 1: { +[20:54:12] "api_key": "your-api-key-here", +[20:54:12] "model": "gpt-3.5-turbo" +[20:54:12] } +[20:54:12] Nivel 2: { +[20:54:12] "input_dir": "D:/Datos/Entrada", +[20:54:12] "output_dir": "D:/Datos/Salida", +[20:54:12] "batch_size": 50 +[20:54:12] } +[20:54:12] Nivel 3: {} +[20:54:12] Simulando procesamiento... +[20:54:13] Progreso: 20% +[20:54:14] Progreso: 40% +[20:54:15] Progreso: 60% +[20:54:16] Progreso: 80% +[20:54:17] Progreso: 100% +[20:54:17] ¡Proceso completado! +[20:54:17] Ejecución de x1.py finalizada (success). Duración: 0:00:05.196719. +[20:54:17] Log completo guardado en: d:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\example_group\log_x1.txt