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ícatePeticiones 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.
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.
Introducción A Flask
Introducción Y Entorno
Instalación Y Configuración Flask Con Venv
Introducción Y Entorno
Mysql Con Sqlalchemy En Flask
Modelos Y Migraciones
Tipos De Datos En Modelos
Modelos Y Migraciones
Operaciones Crud Y Consultas
Modelos Y Migraciones
Asociaciones De Modelos
Modelos Y Migraciones
Migraciones Con Flask-migrate
Modelos Y Migraciones
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
Autenticación Jwt Con Flask-jwt-extended
Api Rest
Controlador Mvc Con Métodos Get En Flask
Mvc
Sintaxis De Plantillas Jinja 2 En Flask
Mvc
Controlador Mvc Con Métodos Post En Flask
Mvc
Inclusión De Archivos Estáticos En Jinja
Mvc
Validación De Formularios Con Wtforms
Mvc
Subir Archivos En Formularios Jinja En Flask
Mvc
Autenticación Con Flask-login
Mvc
Autorización Con Flask-principal
Mvc
Qué Son Los Blueprints Y Cómo Crear Uno
Blueprints
Integrar Openai Api En Flask Api Rest
Aplicación Con Ia
Sqlalchemy Orm En Flask Mysql
Aplicación Con Ia
Resultados De Ia Con Jinja En Flask
Aplicación Con Ia
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.