from flask import Blueprint, request, jsonify import logging import os import yaml from .utils import (get_category_directory, load_yaml_file, validate, save_yaml_file, update_yaml_file, get_file_modified_date, test_regex_pattern, test_format_conditions, check_delete_constraints, filename_to_display) from ..db import add_format_to_renames, remove_format_from_renames, is_format_in_renames logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) bp = Blueprint('data', __name__) @bp.route('/', methods=['GET']) def retrieve_all(category): try: directory = get_category_directory(category) files = [f for f in os.listdir(directory) if f.endswith('.yml')] logger.debug(f"Found {len(files)} files in {category}") if not files: return jsonify([]), 200 result = [] errors = 0 for file_name in files: file_path = os.path.join(directory, file_name) try: content = load_yaml_file(file_path) # Add metadata for custom formats if category == 'custom_format': content['metadata'] = { 'includeInRename': is_format_in_renames(content['name']) } result.append({ "file_name": file_name, "content": content, "modified_date": get_file_modified_date(file_path) }) except yaml.YAMLError: errors += 1 result.append({ "file_name": file_name, "error": "Failed to parse YAML" }) logger.info( f"Processed {len(files)} {category} files ({errors} errors)") return jsonify(result), 200 except ValueError as ve: logger.error(ve) return jsonify({"error": str(ve)}), 400 except FileNotFoundError as fnfe: logger.error(fnfe) return jsonify({"error": str(fnfe)}), 404 except Exception as e: logger.exception("Unexpected error occurred") return jsonify({"error": "An unexpected error occurred"}), 500 @bp.route('//', methods=['GET', 'POST', 'PUT', 'DELETE']) def handle_item(category, name): try: directory = get_category_directory(category) file_name = f"{name}.yml" if not name.endswith('.yml') else name file_path = os.path.join(directory, file_name) if request.method == 'GET': try: content = load_yaml_file(file_path) # Add metadata for custom formats if category == 'custom_format': content['metadata'] = { 'includeInRename': is_format_in_renames(content['name']) } return jsonify({ "file_name": file_name, "content": content, "modified_date": get_file_modified_date(file_path) }), 200 except FileNotFoundError: return jsonify({"error": f"File {file_name} not found"}), 404 except yaml.YAMLError: return jsonify( {"error": f"Failed to parse YAML file {file_name}"}), 500 elif request.method == 'DELETE': if not os.path.exists(file_path): return jsonify({"error": f"File {file_name} not found"}), 404 # Check for references before deleting can_delete, error_message = check_delete_constraints( category, filename_to_display(name)) if not can_delete: logger.error( f"Delete constraint check failed for {name}: {error_message}" ) return jsonify({"error": error_message}), 409 try: # If it's a custom format, remove from renames table first if category == 'custom_format': # Get the format name from the file before deleting it content = load_yaml_file(file_path) format_name = content.get('name') if format_name: # Check if it exists in renames before trying to remove if is_format_in_renames(format_name): remove_format_from_renames(format_name) logger.info( f"Removed {format_name} from renames table") else: logger.info( f"{format_name} was not in renames table") # Then delete the file os.remove(file_path) return jsonify( {"message": f"Successfully deleted {file_name}"}), 200 except OSError as e: logger.error(f"Error deleting file {file_path}: {e}") return jsonify({"error": f"Failed to delete {file_name}"}), 500 elif request.method == 'POST': # If a file already exists with that name, conflict if os.path.exists(file_path): return jsonify({"error": f"File {file_name} already exists"}), 409 try: data = request.get_json() if data and 'name' in data: data['name'] = data['name'].strip() # Handle rename inclusion for custom formats if category == 'custom_format': include_in_rename = data.get('metadata', {}).get( 'includeInRename', False) # Remove metadata before saving YAML if 'metadata' in data: del data['metadata'] if validate(data, category): # Save YAML save_yaml_file(file_path, data, category) # If custom format, handle rename table if category == 'custom_format' and include_in_rename: add_format_to_renames(data['name']) return jsonify( {"message": f"Successfully created {file_name}"}), 201 return jsonify({"error": "Validation failed"}), 400 except Exception as e: logger.error(f"Error creating file: {e}") return jsonify({"error": str(e)}), 500 elif request.method == 'PUT': if not os.path.exists(file_path): return jsonify({"error": f"File {file_name} not found"}), 404 try: data = request.get_json() logger.info(f"Received PUT data for {name}: {data}") if data and 'name' in data: data['name'] = data['name'].strip() if data and 'rename' in data: data['rename'] = data['rename'].strip() # Handle rename inclusion for custom formats if category == 'custom_format': include_in_rename = data.get('metadata', {}).get( 'includeInRename', False) # Get current content to check for rename current_content = load_yaml_file(file_path) old_name = current_content.get('name') new_name = data['name'] # Handle renames and toggles if old_name != new_name and include_in_rename: # Handle rename while keeping in table remove_format_from_renames(old_name) add_format_to_renames(new_name) elif include_in_rename: # Just turning it on add_format_to_renames(new_name) else: # Turning it off remove_format_from_renames(data['name']) # Remove metadata before saving YAML if 'metadata' in data: del data['metadata'] # Save YAML update_yaml_file(file_path, data, category) return jsonify( {"message": f"Successfully updated {file_name}"}), 200 except Exception as e: logger.error(f"Error updating file: {e}") return jsonify({"error": str(e)}), 500 except ValueError as ve: logger.error(ve) return jsonify({"error": str(ve)}), 400 except Exception as e: logger.exception("Unexpected error occurred") return jsonify({"error": "An unexpected error occurred"}), 500 @bp.route('//test', methods=['POST']) def run_tests(category): logger.info(f"Received test request for category: {category}") try: data = request.get_json() if not data: logger.warning("Rejected test request - no JSON data provided") return jsonify({"error": "No JSON data provided"}), 400 tests = data.get('tests', []) if not tests: logger.warning("Rejected test request - no test cases provided") return jsonify({"error": "At least one test case is required"}), 400 if category == 'regex_pattern': pattern = data.get('pattern') logger.info(f"Processing regex test request - Pattern: {pattern}") if not pattern: logger.warning("Rejected test request - missing pattern") return jsonify({"error": "Pattern is required"}), 400 success, message, updated_tests = test_regex_pattern( pattern, tests) elif category == 'custom_format': conditions = data.get('conditions', []) logger.info( f"Processing format test request - Conditions: {len(conditions)}" ) if not conditions: logger.warning( "Rejected test request - no conditions provided") return jsonify({"error": "At least one condition is required"}), 400 success, message, updated_tests = test_format_conditions( conditions, tests) else: logger.warning( f"Rejected test request - invalid category: {category}") return jsonify( {"error": "Testing not supported for this category"}), 400 logger.info(f"Test execution completed - Success: {success}") if not success: logger.warning(f"Test execution failed - {message}") return jsonify({"success": False, "message": message}), 400 return jsonify({"success": True, "tests": updated_tests}), 200 except Exception as e: logger.warning(f"Unexpected error in test endpoint: {str(e)}", exc_info=True) return jsonify({"success": False, "message": str(e)}), 500