Files
profilarr/backend/app/data/__init__.py
santiagosayshey 0729ac0a62 feature: Include Format when Renaming (#143)
- New: Field inside format general tab to enable include format in rename
- New: Database migration that adds format renames table
- New: Queries to get / update rename status for a format
- Update: Format compiler checks for rename entries and add include rename field when found
- Update: Parsing improvements for incoming commit messages
2025-02-18 01:49:38 +10:30

289 lines
11 KiB
Python

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('/<string:category>', 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('/<string:category>/<string:name>',
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('/<string:category>/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