Flask

Flask

Tutorial Flask: Métodos DELETE

Aprende a implementar el método DELETE en Flask para eliminar recursos en APIs REST con validaciones y respuestas adecuadas.

Aprende Flask y certifícate

Método DELETE

El método DELETE en Flask permite eliminar recursos específicos de nuestra API REST. A diferencia de los métodos GET, POST y PUT que hemos visto anteriormente, DELETE está diseñado exclusivamente para operaciones de eliminación y sigue el principio de idempotencia: realizar la misma operación DELETE múltiples veces debe producir el mismo resultado.

En el contexto de una API REST, el método DELETE se utiliza para eliminar recursos identificados por su URL. Por ejemplo, DELETE /usuarios/123 eliminaría el usuario con ID 123. La implementación en Flask es directa y mantiene la coherencia con el resto de métodos HTTP que ya conoces.

Implementación básica del método DELETE

Para crear un endpoint DELETE en Flask, utilizamos el decorador @app.route() especificando el método correspondiente. Veamos un ejemplo práctico con nuestra simulación de usuarios en memoria:

from flask import Flask, jsonify

app = Flask(__name__)

# Simulación de datos en memoria
usuarios = [
    {"id": 1, "nombre": "Ana García", "email": "ana@email.com"},
    {"id": 2, "nombre": "Carlos López", "email": "carlos@email.com"},
    {"id": 3, "nombre": "María Rodríguez", "email": "maria@email.com"}
]

@app.route('/usuarios/<int:usuario_id>', methods=['DELETE'])
def eliminar_usuario(usuario_id):
    # Buscar el usuario por ID
    usuario = next((u for u in usuarios if u['id'] == usuario_id), None)
    
    if not usuario:
        return jsonify({"error": "Usuario no encontrado"}), 404
    
    # Eliminar el usuario de la lista
    usuarios.remove(usuario)
    
    return jsonify({"mensaje": "Usuario eliminado correctamente"}), 200

En este ejemplo, el endpoint recibe el ID del usuario como parámetro de la URL y busca el elemento correspondiente en nuestra lista. Si encuentra el usuario, lo elimina y devuelve una confirmación; si no lo encuentra, retorna un error 404.

Manejo de parámetros y validaciones

Los endpoints DELETE deben incluir validaciones robustas para evitar eliminaciones accidentales o no autorizadas. Es fundamental verificar que el recurso existe antes de intentar eliminarlo:

@app.route('/productos/<int:producto_id>', methods=['DELETE'])
def eliminar_producto(producto_id):
    # Validar que el ID sea positivo
    if producto_id <= 0:
        return jsonify({"error": "ID de producto inválido"}), 400
    
    # Buscar el producto
    producto_encontrado = None
    indice = -1
    
    for i, producto in enumerate(productos):
        if producto['id'] == producto_id:
            producto_encontrado = producto
            indice = i
            break
    
    if not producto_encontrado:
        return jsonify({
            "error": "Producto no encontrado",
            "codigo": "PRODUCTO_NO_EXISTE"
        }), 404
    
    # Verificar si el producto puede eliminarse
    if producto_encontrado.get('activo', True):
        # Eliminar el producto
        productos.pop(indice)
        return jsonify({
            "mensaje": "Producto eliminado correctamente",
            "producto_eliminado": producto_encontrado
        }), 200
    else:
        return jsonify({
            "error": "No se puede eliminar un producto inactivo"
        }), 409

Eliminación con condiciones específicas

En aplicaciones reales, frecuentemente necesitamos aplicar lógica de negocio antes de permitir una eliminación. Esto puede incluir verificar dependencias, permisos o estados específicos:

# Simulación de pedidos que dependen de productos
pedidos = [
    {"id": 1, "producto_id": 1, "cantidad": 2},
    {"id": 2, "producto_id": 2, "cantidad": 1}
]

@app.route('/productos/<int:producto_id>', methods=['DELETE'])
def eliminar_producto_con_validaciones(producto_id):
    # Verificar si existen pedidos asociados
    pedidos_asociados = [p for p in pedidos if p['producto_id'] == producto_id]
    
    if pedidos_asociados:
        return jsonify({
            "error": "No se puede eliminar el producto",
            "motivo": "Existen pedidos asociados",
            "pedidos_count": len(pedidos_asociados)
        }), 409
    
    # Buscar y eliminar el producto
    for i, producto in enumerate(productos):
        if producto['id'] == producto_id:
            producto_eliminado = productos.pop(i)
            return jsonify({
                "mensaje": "Producto eliminado correctamente",
                "producto": producto_eliminado
            }), 200
    
    return jsonify({"error": "Producto no encontrado"}), 404

Eliminación múltiple

Aunque menos común, a veces necesitamos eliminar múltiples recursos en una sola operación. Flask permite manejar esto mediante parámetros de consulta o datos en el cuerpo de la petición:

from flask import request

@app.route('/usuarios/batch', methods=['DELETE'])
def eliminar_usuarios_multiples():
    # Obtener IDs desde parámetros de consulta
    ids_str = request.args.get('ids', '')
    
    if not ids_str:
        return jsonify({"error": "Debe proporcionar IDs para eliminar"}), 400
    
    try:
        # Convertir string de IDs a lista de enteros
        ids_eliminar = [int(id_str.strip()) for id_str in ids_str.split(',')]
    except ValueError:
        return jsonify({"error": "IDs inválidos proporcionados"}), 400
    
    usuarios_eliminados = []
    usuarios_no_encontrados = []
    
    # Procesar cada ID
    for usuario_id in ids_eliminar:
        usuario = next((u for u in usuarios if u['id'] == usuario_id), None)
        
        if usuario:
            usuarios.remove(usuario)
            usuarios_eliminados.append(usuario)
        else:
            usuarios_no_encontrados.append(usuario_id)
    
    return jsonify({
        "eliminados": usuarios_eliminados,
        "no_encontrados": usuarios_no_encontrados,
        "total_eliminados": len(usuarios_eliminados)
    }), 200

Códigos de estado HTTP apropiados

El método DELETE debe retornar códigos de estado específicos según el resultado de la operación:

  • 200 OK: Eliminación exitosa con información del recurso eliminado
  • 204 No Content: Eliminación exitosa sin contenido de respuesta
  • 404 Not Found: El recurso a eliminar no existe
  • 409 Conflict: No se puede eliminar debido a conflictos (dependencias, estado, etc.)
  • 400 Bad Request: Parámetros inválidos en la petición
@app.route('/categorias/<int:categoria_id>', methods=['DELETE'])
def eliminar_categoria(categoria_id):
    categoria = next((c for c in categorias if c['id'] == categoria_id), None)
    
    if not categoria:
        return jsonify({"error": "Categoría no encontrada"}), 404
    
    # Verificar si tiene productos asociados
    productos_en_categoria = [p for p in productos if p.get('categoria_id') == categoria_id]
    
    if productos_en_categoria:
        return jsonify({
            "error": "No se puede eliminar la categoría",
            "motivo": "Contiene productos asociados",
            "productos_count": len(productos_en_categoria)
        }), 409
    
    # Eliminar categoría
    categorias.remove(categoria)
    
    # Retornar 204 No Content (sin cuerpo de respuesta)
    return '', 204

La implementación del método DELETE completa el conjunto básico de operaciones CRUD en nuestra API REST. Con GET para consultar, POST para crear, PUT/PATCH para actualizar y DELETE para eliminar, tenemos todas las herramientas necesarias para gestionar recursos de manera completa y profesional.

Confirmaciones y respuestas

Las confirmaciones y respuestas en operaciones DELETE requieren un diseño cuidadoso para proporcionar información útil al cliente sin comprometer la seguridad o el rendimiento de la API. A diferencia de otros métodos HTTP, DELETE debe equilibrar la confirmación de la operación con la eficiencia de la respuesta.

Estructura de respuestas exitosas

Una respuesta DELETE bien diseñada debe incluir información suficiente para que el cliente confirme que la operación se realizó correctamente. Existen diferentes enfoques según las necesidades de la aplicación:

@app.route('/tareas/<int:tarea_id>', methods=['DELETE'])
def eliminar_tarea(tarea_id):
    tarea = next((t for t in tareas if t['id'] == tarea_id), None)
    
    if not tarea:
        return jsonify({"error": "Tarea no encontrada"}), 404
    
    # Guardar información antes de eliminar
    tarea_eliminada = tarea.copy()
    tareas.remove(tarea)
    
    # Respuesta con información completa
    return jsonify({
        "success": True,
        "mensaje": "Tarea eliminada correctamente",
        "tarea_eliminada": {
            "id": tarea_eliminada['id'],
            "titulo": tarea_eliminada['titulo'],
            "fecha_eliminacion": "2024-01-15T10:30:00Z"
        },
        "total_tareas_restantes": len(tareas)
    }), 200

Respuestas minimalistas con código 204

Para operaciones DELETE que no requieren información detallada en la respuesta, el código 204 No Content es la opción más eficiente. Este enfoque es ideal cuando el cliente solo necesita confirmar que la eliminación fue exitosa:

@app.route('/notificaciones/<int:notificacion_id>', methods=['DELETE'])
def eliminar_notificacion(notificacion_id):
    notificacion = next((n for n in notificaciones if n['id'] == notificacion_id), None)
    
    if not notificacion:
        return jsonify({"error": "Notificación no encontrada"}), 404
    
    notificaciones.remove(notificacion)
    
    # Respuesta sin contenido
    return '', 204

Confirmaciones con metadatos de operación

En aplicaciones empresariales, es común incluir metadatos adicionales que ayuden en la auditoría y el seguimiento de operaciones. Estos datos proporcionan contexto valioso sin exponer información sensible:

from datetime import datetime

@app.route('/documentos/<int:documento_id>', methods=['DELETE'])
def eliminar_documento(documento_id):
    documento = next((d for d in documentos if d['id'] == documento_id), None)
    
    if not documento:
        return jsonify({
            "error": "Documento no encontrado",
            "timestamp": datetime.now().isoformat(),
            "operacion": "DELETE",
            "recurso": f"/documentos/{documento_id}"
        }), 404
    
    # Información para auditoría
    documento_info = {
        "id": documento['id'],
        "nombre": documento['nombre'],
        "tipo": documento.get('tipo', 'desconocido')
    }
    
    documentos.remove(documento)
    
    return jsonify({
        "success": True,
        "mensaje": "Documento eliminado correctamente",
        "documento": documento_info,
        "operacion": {
            "tipo": "DELETE",
            "timestamp": datetime.now().isoformat(),
            "recurso": f"/documentos/{documento_id}"
        },
        "estadisticas": {
            "documentos_restantes": len(documentos),
            "espacio_liberado_mb": documento.get('tamaño_mb', 0)
        }
    }), 200

Manejo de eliminaciones con dependencias

Cuando una eliminación afecta recursos relacionados, la respuesta debe informar claramente sobre las consecuencias de la operación. Esto ayuda al cliente a entender el impacto completo:

@app.route('/proyectos/<int:proyecto_id>', methods=['DELETE'])
def eliminar_proyecto(proyecto_id):
    proyecto = next((p for p in proyectos if p['id'] == proyecto_id), None)
    
    if not proyecto:
        return jsonify({"error": "Proyecto no encontrado"}), 404
    
    # Contar recursos relacionados
    tareas_asociadas = [t for t in tareas if t.get('proyecto_id') == proyecto_id]
    archivos_asociados = [a for a in archivos if a.get('proyecto_id') == proyecto_id]
    
    # Eliminar proyecto y recursos relacionados
    proyectos.remove(proyecto)
    
    # Eliminar tareas asociadas
    for tarea in tareas_asociadas:
        tareas.remove(tarea)
    
    # Eliminar archivos asociados
    for archivo in archivos_asociados:
        archivos.remove(archivo)
    
    return jsonify({
        "success": True,
        "mensaje": "Proyecto eliminado correctamente",
        "proyecto_eliminado": {
            "id": proyecto['id'],
            "nombre": proyecto['nombre']
        },
        "recursos_eliminados": {
            "tareas": len(tareas_asociadas),
            "archivos": len(archivos_asociados)
        },
        "impacto": {
            "proyectos_restantes": len(proyectos),
            "operacion_cascada": True
        }
    }), 200

Respuestas de error detalladas

Las respuestas de error en operaciones DELETE deben ser específicas y proporcionar información útil para resolver el problema. Esto incluye códigos de error personalizados y sugerencias de acción:

@app.route('/cuentas/<int:cuenta_id>', methods=['DELETE'])
def eliminar_cuenta(cuenta_id):
    cuenta = next((c for c in cuentas if c['id'] == cuenta_id), None)
    
    if not cuenta:
        return jsonify({
            "error": "Cuenta no encontrada",
            "codigo_error": "CUENTA_NO_EXISTE",
            "mensaje": f"No existe una cuenta con ID {cuenta_id}",
            "sugerencia": "Verifique el ID de la cuenta y vuelva a intentar",
            "recursos_disponibles": "/cuentas para listar todas las cuentas"
        }), 404
    
    # Verificar si la cuenta tiene saldo
    if cuenta.get('saldo', 0) > 0:
        return jsonify({
            "error": "No se puede eliminar la cuenta",
            "codigo_error": "CUENTA_CON_SALDO",
            "mensaje": "La cuenta tiene saldo pendiente",
            "detalles": {
                "saldo_actual": cuenta['saldo'],
                "moneda": cuenta.get('moneda', 'EUR')
            },
            "accion_requerida": "Transfiera el saldo antes de eliminar la cuenta",
            "endpoints_relacionados": {
                "transferir": f"/cuentas/{cuenta_id}/transferir",
                "consultar_saldo": f"/cuentas/{cuenta_id}/saldo"
            }
        }), 409
    
    cuentas.remove(cuenta)
    
    return jsonify({
        "success": True,
        "mensaje": "Cuenta eliminada correctamente",
        "cuenta_eliminada": {
            "id": cuenta['id'],
            "numero": cuenta['numero']
        }
    }), 200

Confirmaciones para eliminaciones masivas

Las operaciones de eliminación masiva requieren respuestas estructuradas que detallen el resultado de cada elemento procesado. Esto permite al cliente identificar qué operaciones fueron exitosas y cuáles fallaron:

@app.route('/comentarios/batch', methods=['DELETE'])
def eliminar_comentarios_masivo():
    data = request.get_json()
    
    if not data or 'ids' not in data:
        return jsonify({
            "error": "Datos inválidos",
            "mensaje": "Debe proporcionar una lista de IDs",
            "formato_esperado": {"ids": [1, 2, 3]}
        }), 400
    
    ids_eliminar = data['ids']
    resultados = {
        "exitosos": [],
        "fallidos": [],
        "no_encontrados": []
    }
    
    for comentario_id in ids_eliminar:
        comentario = next((c for c in comentarios if c['id'] == comentario_id), None)
        
        if not comentario:
            resultados["no_encontrados"].append({
                "id": comentario_id,
                "motivo": "Comentario no encontrado"
            })
            continue
        
        # Verificar si se puede eliminar
        if comentario.get('autor_id') == 1:  # Admin
            resultados["fallidos"].append({
                "id": comentario_id,
                "motivo": "No se pueden eliminar comentarios de administrador"
            })
            continue
        
        # Eliminar comentario
        comentarios.remove(comentario)
        resultados["exitosos"].append({
            "id": comentario_id,
            "texto": comentario['texto'][:50] + "..." if len(comentario['texto']) > 50 else comentario['texto']
        })
    
    return jsonify({
        "mensaje": "Operación de eliminación masiva completada",
        "resumen": {
            "total_procesados": len(ids_eliminar),
            "exitosos": len(resultados["exitosos"]),
            "fallidos": len(resultados["fallidos"]),
            "no_encontrados": len(resultados["no_encontrados"])
        },
        "detalles": resultados,
        "comentarios_restantes": len(comentarios)
    }), 200

Personalización de respuestas según contexto

La personalización de respuestas permite adaptar la información devuelta según el contexto de la aplicación o las necesidades específicas del cliente. Esto mejora la experiencia de uso de la API:

@app.route('/eventos/<int:evento_id>', methods=['DELETE'])
def eliminar_evento(evento_id):
    # Obtener parámetros opcionales
    incluir_detalles = request.args.get('detalles', 'false').lower() == 'true'
    formato_respuesta = request.args.get('formato', 'completo')
    
    evento = next((e for e in eventos if e['id'] == evento_id), None)
    
    if not evento:
        return jsonify({"error": "Evento no encontrado"}), 404
    
    # Información básica
    respuesta_base = {
        "success": True,
        "mensaje": "Evento eliminado correctamente"
    }
    
    # Respuesta mínima
    if formato_respuesta == 'minimo':
        eventos.remove(evento)
        return jsonify(respuesta_base), 200
    
    # Respuesta con detalles
    if incluir_detalles:
        participantes_afectados = [p for p in participantes if p.get('evento_id') == evento_id]
        
        respuesta_base.update({
            "evento_eliminado": {
                "id": evento['id'],
                "titulo": evento['titulo'],
                "fecha": evento['fecha']
            },
            "impacto": {
                "participantes_notificados": len(participantes_afectados),
                "eventos_restantes": len(eventos) - 1
            }
        })
    
    eventos.remove(evento)
    
    return jsonify(respuesta_base), 200

El diseño de confirmaciones y respuestas efectivas en operaciones DELETE mejora significativamente la experiencia del desarrollador que consume la API, proporcionando la información necesaria para manejar correctamente los resultados de las operaciones de eliminación.

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 Métodos DELETE con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.