Flask
Tutorial Flask: Métodos PUT y PATCH
Aprende a implementar y diferenciar los métodos PUT y PATCH en Flask para actualizar recursos en APIs REST de forma eficiente y segura.
Aprende Flask y certifícateImplementación de PUT
El método PUT en Flask permite actualizar recursos completos en una API REST. A diferencia de POST, que crea nuevos recursos, PUT está diseñado para reemplazar completamente un recurso existente con los datos proporcionados en la petición.
Para implementar un endpoint PUT en Flask, utilizamos el decorador @app.route()
especificando el método HTTP correspondiente. La estructura básica requiere que identifiquemos el recurso mediante un parámetro de ruta y procesemos los datos JSON enviados en el cuerpo de la petición.
from flask import Flask, request, jsonify
app = Flask(__name__)
# Simulamos una base de datos en memoria
usuarios = [
{"id": 1, "nombre": "Ana García", "email": "ana@email.com", "edad": 28},
{"id": 2, "nombre": "Carlos López", "email": "carlos@email.com", "edad": 35}
]
@app.route('/usuarios/<int:usuario_id>', methods=['PUT'])
def actualizar_usuario(usuario_id):
# Buscar el usuario existente
usuario = next((u for u in usuarios if u['id'] == usuario_id), None)
if not usuario:
return jsonify({"error": "Usuario no encontrado"}), 404
# Obtener datos del cuerpo de la petición
datos = request.get_json()
if not datos:
return jsonify({"error": "No se proporcionaron datos"}), 400
# Reemplazar completamente el usuario
usuario.update({
"nombre": datos.get("nombre", ""),
"email": datos.get("email", ""),
"edad": datos.get("edad", 0)
})
return jsonify(usuario), 200
La validación de datos es fundamental en las operaciones PUT. Debemos verificar que los campos obligatorios estén presentes y tengan el formato correcto antes de proceder con la actualización.
@app.route('/usuarios/<int:usuario_id>', methods=['PUT'])
def actualizar_usuario_validado(usuario_id):
usuario = next((u for u in usuarios if u['id'] == usuario_id), None)
if not usuario:
return jsonify({"error": "Usuario no encontrado"}), 404
datos = request.get_json()
# Validaciones específicas
if not datos:
return jsonify({"error": "Datos requeridos"}), 400
if not datos.get("nombre") or len(datos["nombre"].strip()) == 0:
return jsonify({"error": "El nombre es obligatorio"}), 400
if not datos.get("email") or "@" not in datos["email"]:
return jsonify({"error": "Email inválido"}), 400
if not isinstance(datos.get("edad"), int) or datos["edad"] < 0:
return jsonify({"error": "La edad debe ser un número positivo"}), 400
# Actualización completa del recurso
usuario["nombre"] = datos["nombre"].strip()
usuario["email"] = datos["email"].strip()
usuario["edad"] = datos["edad"]
return jsonify({
"mensaje": "Usuario actualizado correctamente",
"usuario": usuario
}), 200
Una característica importante del método PUT es su naturaleza idempotente. Esto significa que realizar la misma operación PUT múltiples veces debe producir el mismo resultado. Para garantizar esto, nuestro endpoint debe manejar consistentemente los datos de entrada.
@app.route('/productos/<int:producto_id>', methods=['PUT'])
def actualizar_producto(producto_id):
# Simulamos productos en memoria
productos = [
{"id": 1, "nombre": "Laptop", "precio": 899.99, "stock": 10},
{"id": 2, "nombre": "Mouse", "precio": 25.50, "stock": 50}
]
producto = next((p for p in productos if p['id'] == producto_id), None)
if not producto:
return jsonify({"error": "Producto no encontrado"}), 404
datos = request.get_json()
# Validación de estructura completa
campos_requeridos = ["nombre", "precio", "stock"]
for campo in campos_requeridos:
if campo not in datos:
return jsonify({
"error": f"Campo '{campo}' es obligatorio para PUT"
}), 400
# Actualización idempotente
producto["nombre"] = str(datos["nombre"])
producto["precio"] = float(datos["precio"])
producto["stock"] = int(datos["stock"])
return jsonify(producto), 200
El manejo de códigos de estado HTTP en PUT sigue convenciones específicas. Utilizamos 200 para actualizaciones exitosas, 404 cuando el recurso no existe, y 400 para datos inválidos. En algunos casos, podemos usar 201 si el endpoint permite crear el recurso cuando no existe.
@app.route('/configuracion/<string:clave>', methods=['PUT'])
def actualizar_configuracion(clave):
# Simulamos configuraciones del sistema
configuraciones = {
"tema": "claro",
"idioma": "es",
"notificaciones": True
}
datos = request.get_json()
if not datos or "valor" not in datos:
return jsonify({"error": "Se requiere el campo 'valor'"}), 400
# PUT puede crear o actualizar
configuraciones[clave] = datos["valor"]
# Determinar si fue creación o actualización
if clave in configuraciones:
codigo_estado = 200 # Actualización
mensaje = "Configuración actualizada"
else:
codigo_estado = 201 # Creación
mensaje = "Configuración creada"
return jsonify({
"mensaje": mensaje,
"clave": clave,
"valor": configuraciones[clave]
}), codigo_estado
Para recursos complejos con relaciones, PUT debe manejar la actualización de manera coherente, asegurándose de que todas las propiedades del recurso se actualicen correctamente.
@app.route('/pedidos/<int:pedido_id>', methods=['PUT'])
def actualizar_pedido(pedido_id):
pedidos = [
{
"id": 1,
"cliente": "María Rodríguez",
"items": [
{"producto": "Libro", "cantidad": 2, "precio": 15.99},
{"producto": "Bolígrafo", "cantidad": 5, "precio": 1.50}
],
"total": 39.48,
"estado": "pendiente"
}
]
pedido = next((p for p in pedidos if p['id'] == pedido_id), None)
if not pedido:
return jsonify({"error": "Pedido no encontrado"}), 404
datos = request.get_json()
# Validar estructura completa del pedido
if not all(campo in datos for campo in ["cliente", "items", "estado"]):
return jsonify({
"error": "PUT requiere todos los campos: cliente, items, estado"
}), 400
# Calcular total automáticamente
total = sum(item["cantidad"] * item["precio"] for item in datos["items"])
# Reemplazar completamente el pedido
pedido.update({
"cliente": datos["cliente"],
"items": datos["items"],
"total": round(total, 2),
"estado": datos["estado"]
})
return jsonify(pedido), 200
Diferencias PUT vs PATCH
Aunque tanto PUT como PATCH permiten actualizar recursos en una API REST, representan filosofías diferentes sobre cómo debe realizarse la modificación de datos. Comprender estas diferencias es fundamental para diseñar APIs coherentes y predecibles.
La diferencia principal radica en el alcance de la actualización. PUT reemplaza completamente el recurso con los datos proporcionados, mientras que PATCH aplica modificaciones parciales únicamente a los campos especificados.
from flask import Flask, request, jsonify
app = Flask(__name__)
# Recurso de ejemplo
usuario = {
"id": 1,
"nombre": "Laura Martín",
"email": "laura@email.com",
"edad": 30,
"telefono": "123456789",
"activo": True
}
# PUT: Reemplaza completamente el recurso
@app.route('/usuario/<int:usuario_id>', methods=['PUT'])
def put_usuario(usuario_id):
datos = request.get_json()
# PUT requiere TODOS los campos
campos_obligatorios = ["nombre", "email", "edad", "telefono", "activo"]
for campo in campos_obligatorios:
if campo not in datos:
return jsonify({
"error": f"PUT requiere el campo '{campo}'"
}), 400
# Reemplaza completamente
usuario.update(datos)
return jsonify(usuario), 200
# PATCH: Modifica solo campos específicos
@app.route('/usuario/<int:usuario_id>', methods=['PATCH'])
def patch_usuario(usuario_id):
datos = request.get_json()
if not datos:
return jsonify({"error": "No se proporcionaron datos"}), 400
# Solo actualiza campos presentes
for campo, valor in datos.items():
if campo in usuario and campo != "id":
usuario[campo] = valor
return jsonify(usuario), 200
La semántica HTTP establece que PUT debe ser idempotente y reemplazar el recurso completo, mientras que PATCH puede ser no idempotente y aplicar transformaciones específicas. Esta diferencia afecta cómo los clientes interactúan con la API.
# Ejemplo práctico de las diferencias
@app.route('/perfil/<int:perfil_id>', methods=['PUT'])
def actualizar_perfil_completo(perfil_id):
perfil = {
"id": 1,
"nombre": "Ana Sánchez",
"biografia": "Desarrolladora Python",
"ubicacion": "Madrid",
"sitio_web": "https://ana.dev",
"publico": True
}
datos = request.get_json()
# PUT: Si falta un campo, se debe proporcionar explícitamente
if "biografia" not in datos:
# En PUT, campos faltantes pueden interpretarse como vacíos
datos["biografia"] = ""
# Reemplaza todo el perfil
perfil.update({
"nombre": datos.get("nombre", ""),
"biografia": datos.get("biografia", ""),
"ubicacion": datos.get("ubicacion", ""),
"sitio_web": datos.get("sitio_web", ""),
"publico": datos.get("publico", False)
})
return jsonify(perfil), 200
@app.route('/perfil/<int:perfil_id>', methods=['PATCH'])
def actualizar_perfil_parcial(perfil_id):
perfil = {
"id": 1,
"nombre": "Ana Sánchez",
"biografia": "Desarrolladora Python",
"ubicacion": "Madrid",
"sitio_web": "https://ana.dev",
"publico": True
}
datos = request.get_json()
# PATCH: Solo modifica campos enviados
campos_modificables = ["nombre", "biografia", "ubicacion", "sitio_web", "publico"]
for campo in campos_modificables:
if campo in datos:
perfil[campo] = datos[campo]
return jsonify({
"mensaje": "Perfil actualizado parcialmente",
"campos_modificados": list(datos.keys()),
"perfil": perfil
}), 200
En términos de validación, PUT requiere validar la estructura completa del recurso, mientras que PATCH solo necesita validar los campos que se están modificando. Esto hace que PATCH sea más flexible para actualizaciones específicas.
@app.route('/producto/<int:producto_id>', methods=['PUT'])
def put_producto(producto_id):
datos = request.get_json()
# Validación completa para PUT
validaciones = {
"nombre": lambda x: isinstance(x, str) and len(x.strip()) > 0,
"precio": lambda x: isinstance(x, (int, float)) and x > 0,
"categoria": lambda x: isinstance(x, str) and x in ["electronica", "ropa", "hogar"],
"disponible": lambda x: isinstance(x, bool)
}
for campo, validador in validaciones.items():
if campo not in datos:
return jsonify({"error": f"Campo '{campo}' requerido"}), 400
if not validador(datos[campo]):
return jsonify({"error": f"Valor inválido para '{campo}'"}), 400
return jsonify({"mensaje": "Producto reemplazado completamente"}), 200
@app.route('/producto/<int:producto_id>', methods=['PATCH'])
def patch_producto(producto_id):
datos = request.get_json()
# Validación selectiva para PATCH
validaciones = {
"nombre": lambda x: isinstance(x, str) and len(x.strip()) > 0,
"precio": lambda x: isinstance(x, (int, float)) and x > 0,
"categoria": lambda x: isinstance(x, str) and x in ["electronica", "ropa", "hogar"],
"disponible": lambda x: isinstance(x, bool)
}
# Solo valida campos presentes
for campo, valor in datos.items():
if campo in validaciones and not validaciones[campo](valor):
return jsonify({"error": f"Valor inválido para '{campo}'"}), 400
return jsonify({
"mensaje": "Producto actualizado parcialmente",
"campos_actualizados": list(datos.keys())
}), 200
Las implicaciones de seguridad también difieren entre ambos métodos. PUT puede sobrescribir accidentalmente campos importantes si no se envían, mientras que PATCH permite actualizaciones más controladas.
@app.route('/cuenta/<int:cuenta_id>', methods=['PUT'])
def put_cuenta(cuenta_id):
cuenta = {
"id": 1,
"nombre": "Usuario Demo",
"email": "demo@email.com",
"rol": "admin",
"ultimo_acceso": "2024-01-15",
"activa": True
}
datos = request.get_json()
# PUT: Riesgo de sobrescribir campos críticos
if "rol" not in datos:
return jsonify({
"error": "PUT requiere especificar el rol explícitamente"
}), 400
# Verificar permisos para cambios de rol
if datos["rol"] != cuenta["rol"]:
return jsonify({"error": "Sin permisos para cambiar rol"}), 403
cuenta.update(datos)
return jsonify(cuenta), 200
@app.route('/cuenta/<int:cuenta_id>', methods=['PATCH'])
def patch_cuenta(cuenta_id):
cuenta = {
"id": 1,
"nombre": "Usuario Demo",
"email": "demo@email.com",
"rol": "admin",
"ultimo_acceso": "2024-01-15",
"activa": True
}
datos = request.get_json()
# PATCH: Control granular de modificaciones
campos_restringidos = ["rol", "ultimo_acceso"]
for campo in campos_restringidos:
if campo in datos:
return jsonify({
"error": f"No se permite modificar '{campo}' via PATCH"
}), 403
# Solo actualiza campos permitidos
campos_permitidos = ["nombre", "email", "activa"]
for campo in campos_permitidos:
if campo in datos:
cuenta[campo] = datos[campo]
return jsonify(cuenta), 200
En cuanto a casos de uso, PUT es ideal cuando necesitas garantizar que el recurso tenga un estado específico completo, mientras que PATCH es perfecto para modificaciones incrementales o cuando solo algunos campos necesitan cambiar.
PUT es apropiado cuando:
- Necesitas reemplazar completamente un recurso
- Quieres garantizar un estado final específico
- El cliente tiene todos los datos del recurso
- La operación debe ser completamente idempotente
PATCH es apropiado cuando:
- Solo necesitas modificar campos específicos
- Quieres evitar sobrescribir datos no relacionados
- El cliente no tiene todos los datos del recurso
- Necesitas aplicar transformaciones específicas
# Ejemplo comparativo: actualizar configuración de usuario
@app.route('/config/<int:user_id>', methods=['PUT'])
def put_configuracion(user_id):
# PUT: Establece configuración completa
config_completa = request.get_json()
# Debe incluir TODAS las configuraciones
configuracion_default = {
"tema": "claro",
"idioma": "es",
"notificaciones_email": True,
"notificaciones_push": False,
"privacidad_perfil": "publico",
"mostrar_actividad": True
}
# Reemplaza toda la configuración
configuracion_default.update(config_completa)
return jsonify(configuracion_default), 200
@app.route('/config/<int:user_id>', methods=['PATCH'])
def patch_configuracion(user_id):
# PATCH: Modifica solo configuraciones específicas
cambios = request.get_json()
configuracion_actual = {
"tema": "claro",
"idioma": "es",
"notificaciones_email": True,
"notificaciones_push": False,
"privacidad_perfil": "publico",
"mostrar_actividad": True
}
# Solo actualiza configuraciones enviadas
for clave, valor in cambios.items():
if clave in configuracion_actual:
configuracion_actual[clave] = valor
return jsonify({
"configuracion": configuracion_actual,
"cambios_aplicados": list(cambios.keys())
}), 200
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 PUT y PATCH con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.