Flask

Flask

Tutorial Flask: Rutas endpoints REST POST, PUT y DELETE

Aprende a implementar peticiones POST en Flask usando Marshmallow para validar y deserializar datos, optimizando la seguridad e integridad de tus APIs REST.

Aprende Flask GRATIS y certifícate

Peticiones POST con Marshmallow

En las APIs REST, las peticiones POST se utilizan para crear nuevos recursos en el servidor. Al igual que con las peticiones GET, es esencial validar y deserializar los datos de entrada para garantizar la integridad de nuestra aplicación. Marshmallow nos permite definir esquemas de validación que simplifican este proceso.

Para manejar una petición POST en Flask, primero definimos una ruta que acepte el método POST. Utilizamos un esquema de Marshmallow para validar y deserializar los datos entrantes y, si todo es correcto, creamos un nuevo registro en la base de datos con SQLAlchemy.

A continuación, se presenta un ejemplo práctico:

from flask import request, jsonify
from models import db, Usuario
from schemas import UsuarioSchema
from marshmallow import ValidationError
from flask import Blueprint

usuarios_bp = Blueprint('usuarios_bp', __name__)

@usuarios_bp.route('/usuarios', methods=['POST'])
def crear_usuario():
    datos = request.get_json()
    esquema = UsuarioSchema()
    try:
        usuario_validado = esquema.load(datos)
        nuevo_usuario = Usuario(**usuario_validado)
        db.session.add(nuevo_usuario)
        db.session.commit()
        resultado = esquema.dump(nuevo_usuario)
        return jsonify(resultado), 201
    except ValidationError as err:
        return jsonify(err.messages), 400

En este ejemplo, obtenemos los datos JSON de la petición con request.get_json(). Luego, utilizamos el método load del esquema UsuarioSchema para validar y deserializar los datos. Si la validación es exitosa, creamos una instancia de Usuario con los datos validados y la guardamos en la base de datos. Finalmente, serializamos el nuevo usuario con el método dump y lo devolvemos en la respuesta con un código de estado 201 Created.

Es crucial manejar las excepciones que puedan surgir durante el proceso de validación. Si ocurre un ValidationError, devolvemos los mensajes de error con un código de estado 400 Bad Request. De esta manera, informamos al cliente de que hubo un problema con los datos enviados.

El esquema UsuarioSchema podría definirse de la siguiente manera:

from marshmallow import Schema, fields, validate

class UsuarioSchema(Schema):
    id = fields.Int(dump_only=True)
    nombre = fields.Str(required=True, validate=validate.Length(min=1))
    email = fields.Email(required=True)
    edad = fields.Int(required=True, validate=validate.Range(min=18))

En este esquema, especificamos que los campos nombre, email y edad son requeridos. Además, aplicamos validaciones adicionales, como asegurar que nombre no esté vacío y que edad sea al menos 18. El campo id se define como dump_only=True para que solo se incluya en la serialización pero no se espere en los datos de entrada.

Al utilizar Marshmallow para validar las peticiones POST, nos aseguramos de que los datos que almacenamos en la base de datos cumplen con los requisitos establecidos. Esto mejora la integridad de nuestra aplicación y facilita el manejo de errores.

Es importante recordar que, antes de guardar un nuevo registro, debemos comprobar que cumple con todas las restricciones de nuestra base de datos, como unicidad de ciertos campos. Por ejemplo, podríamos verificar si ya existe un usuario con el mismo correo electrónico antes de crear uno nuevo.

Peticiones PUT y PATCH con Marshmallow

En las APIs REST, las peticiones PUT y PATCH se utilizan para actualizar recursos existentes en el servidor. Es esencial validar los datos de entrada para garantizar que los cambios sean coherentes y seguros. Marshmallow nos ayuda a definir esquemas de validación y deserialización, simplificando el manejo de datos en estas peticiones.

Para manejar una petición PUT en Flask, primero definimos una ruta que acepte el método PUT. Utilizamos un esquema de Marshmallow para validar y deserializar los datos proporcionados por el cliente. Si la validación es exitosa, actualizamos el registro correspondiente en la base de datos utilizando SQLAlchemy.

A continuación, se muestra un ejemplo práctico de una ruta que maneja una petición PUT para actualizar un usuario:

from flask import request, jsonify
from models import db, Usuario
from schemas import UsuarioSchema
from marshmallow import ValidationError
from flask import Blueprint

usuarios_bp = Blueprint('usuarios_bp', __name__)

@usuarios_bp.route('/usuarios/<int:id>', methods=['PUT'])
def actualizar_usuario(id):
    datos = request.get_json()
    esquema = UsuarioSchema()
    try:
        usuario_validado = esquema.load(datos)
        usuario = Usuario.query.get_or_404(id)
        usuario.nombre = usuario_validado['nombre']
        usuario.email = usuario_validado['email']
        usuario.edad = usuario_validado['edad']
        db.session.commit()
        resultado = esquema.dump(usuario)
        return jsonify(resultado), 200
    except ValidationError as err:
        return jsonify(err.messages), 400

En este ejemplo, obtenemos los datos JSON de la petición y los validamos con UsuarioSchema. Luego, buscamos el usuario existente por su id usando get_or_404. Si el usuario existe, actualizamos sus atributos con los datos validados y guardamos los cambios en la base de datos. Finalmente, devolvemos el usuario actualizado en la respuesta.

Es importante destacar que la petición PUT suele esperar que todos los campos del recurso sean enviados, ya que representa una actualización completa. Si queremos permitir actualizaciones parciales, normalmente utilizamos el método PATCH.

El manejo de una petición PATCH es similar, pero nos enfocamos en actualizar solo los campos proporcionados en la solicitud. Podemos modificar el esquema de Marshmallow para hacer que todos los campos sean opcionales, o utilizar el parámetro partial=True en el método load:

@usuarios_bp.route('/usuarios/<int:id>', methods=['PATCH'])
def modificar_usuario(id):
    datos = request.get_json()
    esquema = UsuarioSchema()
    try:
        usuario_validado = esquema.load(datos, partial=True)
        usuario = Usuario.query.get_or_404(id)
        if 'nombre' in usuario_validado:
            usuario.nombre = usuario_validado['nombre']
        if 'email' in usuario_validado:
            usuario.email = usuario_validado['email']
        if 'edad' in usuario_validado:
            usuario.edad = usuario_validado['edad']
        db.session.commit()
        resultado = esquema.dump(usuario)
        return jsonify(resultado), 200
    except ValidationError as err:
        return jsonify(err.messages), 400

Al utilizar partial=True, Marshmallow permite que los campos del esquema no sean obligatorios durante la validación, permitiendo así actualizaciones parciales. En el controlador, actualizamos solo los atributos que están presentes en usuario_validado.

Es fundamental manejar correctamente los posibles errores durante el proceso de actualización. Si los datos proporcionados no pasan la validación, devolvemos un código de estado 400 Bad Request con los mensajes de error correspondientes. Si el recurso no existe, get_or_404 devolverá un 404 Not Found automáticamente.

El esquema UsuarioSchema puede mantenerse igual que en las peticiones POST:

from marshmallow import Schema, fields, validate

class UsuarioSchema(Schema):
    id = fields.Int(dump_only=True)
    nombre = fields.Str(required=True, validate=validate.Length(min=1))
    email = fields.Email(required=True)
    edad = fields.Int(required=True, validate=validate.Range(min=18))

Sin embargo, para las peticiones PATCH, podríamos modificar el esquema para que los campos no sean obligatorios, eliminando el parámetro required o pasando partial=True al cargar los datos.

Otra alternativa es definir un esquema específico para las actualizaciones parciales:

class UsuarioParcialSchema(Schema):
    nombre = fields.Str(validate=validate.Length(min=1))
    email = fields.Email()
    edad = fields.Int(validate=validate.Range(min=18))

Y utilizar este esquema en la ruta PATCH:

@usuarios_bp.route('/usuarios/<int:id>', methods=['PATCH'])
def modificar_usuario(id):
    datos = request.get_json()
    esquema = UsuarioParcialSchema()
    try:
        usuario_validado = esquema.load(datos)
        usuario = Usuario.query.get_or_404(id)
        if 'nombre' in usuario_validado:
            usuario.nombre = usuario_validado['nombre']
        if 'email' in usuario_validado:
            usuario.email = usuario_validado['email']
        if 'edad' in usuario_validado:
            usuario.edad = usuario_validado['edad']
        db.session.commit()
        resultado = esquema.dump(usuario)
        return jsonify(resultado), 200
    except ValidationError as err:
        return jsonify(err.messages), 400

De esta manera, el esquema refleja que todos los campos son opcionales en una actualización parcial.

Al implementar peticiones PUT y PATCH con Marshmallow, garantizamos que los datos utilizados para actualizar los recursos cumplen con las validaciones establecidas. Esto mejora la seguridad y la consistencia de nuestra aplicación.

Es buena práctica proporcionar respuestas claras y códigos de estado HTTP adecuados. Por ejemplo, devolver 200 OK cuando la actualización se realiza correctamente, o 400 Bad Request si hay errores de validación. Además, informar al cliente de los problemas encontrados facilita el manejo de errores en el lado del cliente.

Peticiones DELETE

En las APIs REST, las peticiones DELETE se utilizan para eliminar recursos existentes en el servidor. Al implementar este tipo de peticiones en Flask, es fundamental asegurar que el recurso a eliminar realmente existe y manejar adecuadamente los posibles errores que puedan surgir.

Para crear una ruta que maneje una petición DELETE, definimos un endpoint que acepta el método DELETE. Utilizamos SQLAlchemy para interactuar con la base de datos y eliminar el registro correspondiente. Es importante devolver una respuesta apropiada que indique el resultado de la operación.

A continuación, se muestra un ejemplo práctico de cómo implementar una ruta DELETE para eliminar un usuario por su identificador:

from flask import jsonify
from models import db, Usuario
from flask import Blueprint

usuarios_bp = Blueprint('usuarios_bp', __name__)

@usuarios_bp.route('/usuarios/<int:id>', methods=['DELETE'])
def eliminar_usuario(id):
    usuario = Usuario.query.get_or_404(id)
    db.session.delete(usuario)
    db.session.commit()
    return jsonify({'mensaje': 'Usuario eliminado exitosamente'}), 200

En este ejemplo, la función eliminar_usuario recibe el id del usuario a eliminar. Utilizamos get_or_404 para obtener el usuario de la base de datos; si el usuario no existe, Flask automáticamente devuelve una respuesta con código de estado 404 Not Found. Si el usuario existe, procedemos a eliminarlo con db.session.delete(usuario) y confirmamos el cambio con db.session.commit(). Finalmente, devolvemos una respuesta JSON indicando que el usuario fue eliminado exitosamente.

Es importante destacar que, al eliminar un recurso, debemos considerar las restricciones de integridad referencial en la base de datos. Si el usuario tiene relaciones con otros registros, como publicaciones o comentarios, es necesario manejar adecuadamente estas dependencias para evitar errores de integridad.

Para mejorar la robustez de nuestra aplicación, podemos agregar un manejo de excepciones que capture posibles errores durante el proceso de eliminación:

@usuarios_bp.route('/usuarios/<int:id>', methods=['DELETE'])
def eliminar_usuario(id):
    try:
        usuario = Usuario.query.get_or_404(id)
        db.session.delete(usuario)
        db.session.commit()
        return jsonify({'mensaje': 'Usuario eliminado exitosamente'}), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': 'Ocurrió un error al eliminar el usuario'}), 500

En este fragmento, utilizamos un bloque try-except para capturar cualquier excepción que pueda ocurrir durante la operación. Si ocurre un error, hacemos un db.session.rollback() para revertir cualquier cambio pendiente en la sesión y devolvemos una respuesta con código de estado 500 Internal Server Error.

Es recomendable utilizar códigos de estado HTTP apropiados en las respuestas. Al eliminar un recurso exitosamente, podemos devolver 200 OK o 204 No Content. Si preferimos no devolver contenido en la respuesta, podemos optar por el código 204 y omitir el cuerpo de la respuesta:

@usuarios_bp.route('/usuarios/<int:id>', methods=['DELETE'])
def eliminar_usuario(id):
    usuario = Usuario.query.get_or_404(id)
    db.session.delete(usuario)
    db.session.commit()
    return '', 204

En este caso, devolvemos una respuesta vacía con código de estado 204, lo que indica que la petición se ha procesado correctamente pero no hay contenido que enviar en la respuesta.

Es crucial asegurar que nuestra aplicación maneje correctamente las peticiones concurrentes y las posibles condiciones de carrera. Por ejemplo, si dos clientes intentan eliminar el mismo recurso al mismo tiempo, debemos garantizar que la aplicación se comporte de manera consistente y segura.

Además, debemos ser conscientes de las implicaciones de seguridad al permitir la eliminación de recursos. Es necesario implementar mecanismos de autenticación y autorización para asegurarnos de que solo los usuarios autorizados puedan eliminar recursos. Aunque este tema se aborda en secciones posteriores, es fundamental tenerlo en cuenta al diseñar nuestros endpoints.

Para finalizar, es buena práctica confirmar al cliente que el recurso ha sido eliminado y proporcionar información útil en la respuesta, manteniendo la comunicación clara y efectiva.

Precauciones al guardar y borrar modelos de SQLAlchemy

Al utilizar SQLAlchemy con Flask para manipular modelos y realizar operaciones en la base de datos, es importante tomar ciertas precauciones al guardar y borrar registros. Estas medidas ayudan a garantizar la integridad de los datos y el correcto funcionamiento de la aplicación.

Una consideración clave es el manejo adecuado de la sesión de SQLAlchemy. La sesión actúa como una caché intermedia entre la aplicación y la base de datos, lo que significa que los cambios realizados en los objetos del modelo no se aplican hasta que se realiza un commit de la sesión. Es esencial asegurarse de que la sesión se gestione correctamente, evitando problemas como fugas de memoria o condiciones de carrera.

Al guardar nuevos registros o actualizar existentes, es importante validar los datos previamente. Aunque Marshmallow realiza una validación inicial, es aconsejable manejar posibles excepciones que puedan surgir al interactuar con la base de datos. Por ejemplo, al guardar un registro con una clave única duplicada, la base de datos generará un error de integridad. Para manejar estas situaciones, se recomienda utilizar bloques try-except y capturar excepciones como IntegrityError.

from sqlalchemy.exc import IntegrityError

try:
    db.session.add(nuevo_usuario)
    db.session.commit()
except IntegrityError:
    db.session.rollback()
    return jsonify({'error': 'El correo electrónico ya está registrado'}), 400

En este ejemplo, si ocurre un IntegrityError, se realiza un rollback de la sesión para deshacer cualquier cambio pendiente y se informa al cliente del problema. Es fundamental siempre llamar a session.rollback() en caso de excepción para mantener la coherencia de la sesión.

Al borrar modelos, se deben considerar las relaciones existentes entre tablas. Si un modelo tiene relaciones con otros registros, eliminarlo sin las precauciones adecuadas puede provocar errores de integridad referencial o dejar registros huérfanos. Para evitar estos problemas, es importante configurar correctamente las relaciones en los modelos utilizando el parámetro cascade.

Por ejemplo, al definir una relación uno a muchos:

class Usuario(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    publicaciones = db.relationship('Publicacion', backref='autor', cascade='all, delete-orphan')

class Publicacion(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    usuario_id = db.Column(db.Integer, db.ForeignKey('usuario.id'))

Con el parámetro cascade='all, delete-orphan', al eliminar un Usuario, todas sus Publicaciones asociadas también se eliminarán. Esto asegura que no queden registros sin su referente principal y mantiene la integridad referencial de la base de datos.

Es esencial también tener cuidado con el uso de db.session.delete(). Al eliminar un objeto de la sesión, este se marcará para eliminación en la próxima operación de commit. Es recomendable verificar previamente si el objeto existe y manejar posibles excepciones.

Además, al realizar operaciones de borrado, especialmente en endpoints que afectan a datos sensibles, es importante implementar mecanismos de autorización para asegurarse de que solo los usuarios autorizados puedan realizar dichas acciones.

Otra precaución es el manejo de transacciones. Al ejecutar varias operaciones que deben ser atómicas, es decir, que todas se ejecuten correctamente o ninguna, se pueden utilizar transacciones explícitas con contextos de gestión de sesión:

from sqlalchemy.orm import scoped_session

session = scoped_session(db.session)

with session.begin():
    usuario.nombre = datos_actualizados['nombre']
    db.session.delete(publicacion_a_eliminar)

Si ocurre alguna excepción dentro del bloque with, la transacción se revierte automáticamente, garantizando que la base de datos no quede en un estado inconsistente.

Es pertinente mencionar que el uso de operaciones en bloque, como db.session.bulk_save_objects() o db.session.bulk_delete_mappings(), aunque pueden ser más eficientes, no respetan los eventos del ORM ni las cascadas definidas en las relaciones. Por lo tanto, deben utilizarse con precaución y solo cuando se comprende bien su funcionamiento.

Finalmente, es aconsejable mantener las sesiones de SQLAlchemy lo más cortas posible. Abrir una sesión, realizar las operaciones necesarias y cerrarla mediante un commit o rollback ayuda a liberar recursos y evita posibles bloqueos en la base de datos. Además, es recomendable manejar las sesiones en el contexto de las peticiones, utilizando extensiones como Flask-SQLAlchemy que facilitan esta gestión.

Al seguir estas precauciones al guardar y borrar modelos con SQLAlchemy, se mejora la estabilidad y fiabilidad de la aplicación, garantizando que las operaciones de base de datos se realizan de manera segura y eficiente.

Para seguir leyendo hazte Plus

¿Ya eres Plus? Accede a la app

20 % DE DESCUENTO

Plan mensual

19.00 /mes

15.20 € /mes

Precio normal mensual: 19 €
58 % DE DESCUENTO

Plan anual

10.00 /mes

8.00 € /mes

Ahorras 132 € al año
Precio normal anual: 120 €
Aprende Flask GRATIS online

Todas las 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

En esta lección

Objetivos de aprendizaje de esta lección

  • Entender la importancia de las peticiones POST en APIs REST.
  • Implementar validación y deserialización de datos con Marshmallow.
  • Configurar y manejar rutas POST en Flask.
  • Utilizar SQLAlchemy para interactuar con la base de datos.
  • Gestionar excepciones y errores al validar peticiones POST.