Flask

Flask

Tutorial Flask: Manejo de errores y excepciones

Aprende a gestionar errores y excepciones en Flask con respuestas JSON estructuradas para APIs REST profesionales y robustas.

Aprende Flask y certifícate

@app.errorhandler()

Flask proporciona el decorador @app.errorhandler() para capturar y manejar errores específicos que ocurren durante el procesamiento de peticiones HTTP. Este mecanismo permite interceptar excepciones antes de que lleguen al cliente y devolver respuestas personalizadas en lugar de los mensajes de error genéricos del servidor.

El decorador @app.errorhandler() acepta como parámetro el código de estado HTTP o la clase de excepción que queremos capturar. Cuando se produce un error que coincide con el tipo especificado, Flask ejecuta automáticamente la función decorada en lugar de mostrar la página de error predeterminada.

from flask import Flask, jsonify

app = Flask(__name__)

@app.errorhandler(404)
def not_found(error):
    return jsonify({
        'error': 'Recurso no encontrado',
        'message': 'La URL solicitada no existe en el servidor'
    }), 404

Manejo de errores HTTP comunes

Los códigos de estado HTTP más frecuentes en APIs REST requieren un tratamiento específico para proporcionar información útil al cliente. Cada error debe incluir un mensaje descriptivo y mantener la consistencia en el formato de respuesta.

@app.errorhandler(400)
def bad_request(error):
    return jsonify({
        'error': 'Petición incorrecta',
        'message': 'Los datos enviados no son válidos'
    }), 400

@app.errorhandler(405)
def method_not_allowed(error):
    return jsonify({
        'error': 'Método no permitido',
        'message': 'El método HTTP utilizado no está permitido para este endpoint'
    }), 405

@app.errorhandler(500)
def internal_error(error):
    return jsonify({
        'error': 'Error interno del servidor',
        'message': 'Ha ocurrido un error inesperado'
    }), 500

Captura de excepciones personalizadas

Además de los códigos HTTP, @app.errorhandler() puede capturar excepciones específicas de Python. Esto resulta especialmente útil para manejar errores de validación o lógica de negocio de manera centralizada.

@app.errorhandler(ValueError)
def handle_value_error(error):
    return jsonify({
        'error': 'Valor inválido',
        'message': str(error)
    }), 400

@app.errorhandler(KeyError)
def handle_key_error(error):
    return jsonify({
        'error': 'Campo requerido',
        'message': f'El campo {str(error)} es obligatorio'
    }), 400

Un ejemplo práctico muestra cómo estas excepciones personalizadas se integran con los endpoints de la API:

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    
    # Estas excepciones serán capturadas por los errorhandlers
    if not data.get('email'):
        raise KeyError('email')
    
    if len(data.get('password', '')) < 8:
        raise ValueError('La contraseña debe tener al menos 8 caracteres')
    
    return jsonify({'message': 'Usuario creado correctamente'}), 201

Manejo global de errores JSON

Para APIs que trabajan exclusivamente con formato JSON, es recomendable crear un manejador que capture cualquier error no específicamente definido y lo formatee de manera consistente:

@app.errorhandler(Exception)
def handle_exception(error):
    # Si es un error HTTP conocido, mantener su código
    if hasattr(error, 'code'):
        return jsonify({
            'error': error.name,
            'message': error.description
        }), error.code
    
    # Para errores no HTTP, devolver 500
    return jsonify({
        'error': 'Error interno',
        'message': 'Ha ocurrido un error inesperado'
    }), 500

Acceso a información del error

Los manejadores de errores reciben como parámetro un objeto que contiene información detallada sobre el error ocurrido. Este objeto permite acceder a propiedades útiles para personalizar la respuesta:

@app.errorhandler(404)
def not_found(error):
    return jsonify({
        'error': 'Recurso no encontrado',
        'message': error.description,
        'path': request.path,
        'method': request.method
    }), 404

La información del contexto como la ruta solicitada y el método HTTP ayuda a los desarrolladores a identificar rápidamente la causa del error durante el desarrollo y depuración de la API.

Respuestas de error estructuradas

Una API REST profesional requiere respuestas de error consistentes y bien estructuradas que faciliten tanto el desarrollo como la depuración. La estandarización del formato de errores permite a los clientes de la API procesar las respuestas de manera predecible, independientemente del tipo de error que se produzca.

Estructura base para respuestas de error

El diseño de una estructura de error estándar debe incluir campos que proporcionen información suficiente para identificar y resolver el problema. Una estructura típica contiene el código de error, mensaje descriptivo, detalles adicionales y metadatos útiles para el contexto:

from flask import Flask, jsonify, request
from datetime import datetime

app = Flask(__name__)

def create_error_response(error_code, message, details=None, status_code=400):
    """Crea una respuesta de error estructurada y consistente"""
    error_response = {
        'success': False,
        'error': {
            'code': error_code,
            'message': message,
            'timestamp': datetime.utcnow().isoformat() + 'Z'
        }
    }
    
    if details:
        error_response['error']['details'] = details
    
    return jsonify(error_response), status_code

Implementación de errores de validación

Los errores de validación requieren un tratamiento especial ya que pueden incluir múltiples campos con problemas específicos. Una estructura detallada permite al cliente identificar exactamente qué datos necesitan corrección:

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    validation_errors = []
    
    # Validación de campos requeridos
    required_fields = ['name', 'email', 'password']
    for field in required_fields:
        if not data.get(field):
            validation_errors.append({
                'field': field,
                'message': f'El campo {field} es obligatorio'
            })
    
    # Validación de formato de email
    if data.get('email') and '@' not in data['email']:
        validation_errors.append({
            'field': 'email',
            'message': 'El formato del email no es válido'
        })
    
    # Validación de longitud de contraseña
    if data.get('password') and len(data['password']) < 8:
        validation_errors.append({
            'field': 'password',
            'message': 'La contraseña debe tener al menos 8 caracteres'
        })
    
    if validation_errors:
        return create_error_response(
            'VALIDATION_ERROR',
            'Los datos proporcionados no son válidos',
            {'fields': validation_errors},
            400
        )
    
    return jsonify({'success': True, 'message': 'Usuario creado correctamente'}), 201

Categorización de errores por tipo

La categorización de errores facilita el manejo programático en el lado del cliente. Cada categoría puede tener un prefijo específico que identifique el tipo de problema:

# Errores de autenticación
def create_auth_error(message, details=None):
    return create_error_response('AUTH_ERROR', message, details, 401)

# Errores de autorización
def create_permission_error(message, details=None):
    return create_error_response('PERMISSION_ERROR', message, details, 403)

# Errores de recursos no encontrados
def create_not_found_error(resource_type, resource_id=None):
    message = f'{resource_type} no encontrado'
    details = {'resource_type': resource_type}
    if resource_id:
        details['resource_id'] = resource_id
    return create_error_response('RESOURCE_NOT_FOUND', message, details, 404)

# Errores de conflicto
def create_conflict_error(message, details=None):
    return create_error_response('CONFLICT_ERROR', message, details, 409)

Manejo de errores con contexto de petición

Las respuestas estructuradas pueden enriquecerse con información del contexto de la petición HTTP, proporcionando datos adicionales que faciliten la depuración:

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    # Simulación de búsqueda de usuario
    users = [{'id': 1, 'name': 'Juan'}, {'id': 2, 'name': 'María'}]
    user = next((u for u in users if u['id'] == user_id), None)
    
    if not user:
        return jsonify({
            'success': False,
            'error': {
                'code': 'USER_NOT_FOUND',
                'message': 'El usuario solicitado no existe',
                'timestamp': datetime.utcnow().isoformat() + 'Z',
                'request_info': {
                    'method': request.method,
                    'path': request.path,
                    'user_id': user_id
                }
            }
        }), 404
    
    return jsonify({'success': True, 'data': user})

Respuestas de error para operaciones batch

Cuando una operación procesa múltiples elementos, la estructura de error debe reflejar tanto los éxitos como los fallos de manera granular:

@app.route('/api/users/batch', methods=['POST'])
def create_users_batch():
    data = request.get_json()
    users_data = data.get('users', [])
    
    results = []
    errors = []
    
    for index, user_data in enumerate(users_data):
        if not user_data.get('email'):
            errors.append({
                'index': index,
                'field': 'email',
                'message': 'Email es obligatorio',
                'data': user_data
            })
        elif '@' not in user_data['email']:
            errors.append({
                'index': index,
                'field': 'email',
                'message': 'Formato de email inválido',
                'data': user_data
            })
        else:
            results.append({
                'index': index,
                'status': 'created',
                'data': user_data
            })
    
    if errors:
        return jsonify({
            'success': False,
            'error': {
                'code': 'BATCH_VALIDATION_ERROR',
                'message': f'{len(errors)} elementos contienen errores',
                'timestamp': datetime.utcnow().isoformat() + 'Z',
                'details': {
                    'total_items': len(users_data),
                    'successful_items': len(results),
                    'failed_items': len(errors),
                    'errors': errors
                }
            }
        }), 400
    
    return jsonify({
        'success': True,
        'message': f'{len(results)} usuarios creados correctamente',
        'data': results
    }), 201

Integración con manejadores de errores globales

Los manejadores de errores globales pueden utilizar la estructura estandarizada para mantener la consistencia en toda la aplicación:

@app.errorhandler(404)
def handle_not_found(error):
    return create_error_response(
        'ENDPOINT_NOT_FOUND',
        'El endpoint solicitado no existe',
        {
            'available_methods': ['GET', 'POST'],
            'suggested_endpoints': ['/api/users', '/api/users/<id>']
        },
        404
    )

@app.errorhandler(405)
def handle_method_not_allowed(error):
    return create_error_response(
        'METHOD_NOT_ALLOWED',
        'Método HTTP no permitido para este endpoint',
        {
            'method_used': request.method,
            'allowed_methods': error.valid_methods if hasattr(error, 'valid_methods') else []
        },
        405
    )

Esta aproximación estructurada garantiza que todos los errores de la API mantengan un formato consistente, facilitando tanto el desarrollo del cliente como el mantenimiento del código del servidor.

Aprende Flask online

Otras lecciones de Flask

Accede a todas las lecciones de Flask y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Flask y certifícate

Ejercicios de programación de Flask

Evalúa tus conocimientos de esta lección Manejo de errores y excepciones con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.