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ícateMé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.
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.
Introducción A Flask
Introducción Y Entorno
Instalación Y Configuración Flask Con Venv
Introducción Y Entorno
Rutas Endpoints Rest Get
Api Rest
Respuestas Con Esquemas Flask Marshmallow
Api Rest
Rutas Endpoints Rest Post, Put Y Delete
Api Rest
Manejo De Errores Y Códigos De Estado Http
Api Rest
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.