
Validaciones de campo
Las validaciones de campo en SQLAlchemy permiten garantizar la integridad de los datos directamente en el modelo, antes de que lleguen a la base de datos. Estas validaciones se ejecutan automáticamente cuando intentamos crear o modificar registros, proporcionando una capa de seguridad adicional que complementa las restricciones de la base de datos.
El siguiente diagrama resume visualmente los conceptos clave introducidos en esta sección:
graph TD
A[Validación SQLAlchemy] --> B[Restricciones declarativas]
A --> C[validates decorator]
A --> D[Eventos before_insert]
B --> E[nullable False]
B --> F[unique True]
B --> G[CheckConstraint]
C --> H[Función custom por columna]
H --> I[ValueError si falla]
D --> J[Antes de INSERT]
D --> K[Antes de UPDATE]
A --> L[Marshmallow capa serializa]

SQLAlchemy ofrece múltiples mecanismos para implementar validaciones, desde constraints básicos hasta validaciones personalizadas más complejas. La ventaja de implementar estas validaciones a nivel de modelo es que se aplican consistentemente independientemente de cómo se acceda a los datos.
Validaciones con el decorador validates
El decorador @validates es la forma más directa de implementar validaciones personalizadas en SQLAlchemy. Este decorador permite definir funciones que se ejecutan automáticamente cuando se asigna un valor a un campo específico.
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import validates
from datetime import datetime, timezone
import re
class Usuario(db.Model):
__tablename__ = 'usuarios'
id = Column(Integer, primary_key=True)
email = Column(String(120), nullable=False, unique=True)
edad = Column(Integer)
telefono = Column(String(15))
fecha_registro = Column(DateTime, default=lambda: datetime.now(timezone.utc))
@validates('email')
def validar_email(self, key, email):
patron = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(patron, email):
raise ValueError("El formato del email no es válido")
return email.lower()
@validates('edad')
def validar_edad(self, key, edad):
if edad is not None and (edad < 0 or edad > 120):
raise ValueError("La edad debe estar entre 0 y 120 años")
return edad
En este ejemplo, las funciones de validación reciben tres parámetros: self (la instancia del modelo), key (el nombre del campo) y el valor que se está asignando. La función debe retornar el valor validado o lanzar una excepción si la validación falla.
Validaciones múltiples y transformaciones
El decorador @validates también permite validar múltiples campos con una sola función y realizar transformaciones de datos durante la asignación:
class Producto(db.Model):
__tablename__ = 'productos'
id = Column(Integer, primary_key=True)
nombre = Column(String(100), nullable=False)
precio = Column(Integer) # Precio en centavos
codigo = Column(String(20), unique=True)
@validates('nombre', 'codigo')
def validar_texto(self, key, valor):
if not valor or not valor.strip():
raise ValueError(f"El campo {key} no puede estar vacío")
# Transformación: convertir a mayúsculas para códigos
if key == 'codigo':
return valor.upper().strip()
return valor.strip()
@validates('precio')
def validar_precio(self, key, precio):
if precio is not None and precio <= 0:
raise ValueError("El precio debe ser mayor que cero")
return precio
Validaciones con hybrid_property
Los hybrid properties ofrecen una alternativa elegante para campos que requieren validaciones complejas o cálculos dinámicos:
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import Column, String, Integer
class Empleado(db.Model):
__tablename__ = 'empleados'
id = Column(Integer, primary_key=True)
_salario_base = Column('salario_base', Integer) # Almacenado en centavos
dni = Column(String(9), unique=True)
@hybrid_property
def salario_base(self):
return self._salario_base / 100 if self._salario_base else 0
@salario_base.setter
def salario_base(self, valor):
if valor < 0:
raise ValueError("El salario no puede ser negativo")
if valor > 1000000: # 10,000 euros
raise ValueError("El salario excede el límite máximo")
self._salario_base = int(valor * 100)
@validates('dni')
def validar_dni(self, key, dni):
if not dni or len(dni) != 9:
raise ValueError("El DNI debe tener exactamente 9 caracteres")
# Validación básica del formato DNI español
numeros = dni[:8]
letra = dni[8].upper()
if not numeros.isdigit():
raise ValueError("Los primeros 8 caracteres del DNI deben ser números")
letras_validas = "TRWAGMYFPDXBNJZSQVHLCKE"
letra_esperada = letras_validas[int(numeros) % 23]
if letra != letra_esperada:
raise ValueError("La letra del DNI no es correcta")
return dni.upper()
Validaciones condicionales y dependientes
En ocasiones necesitamos validaciones que dependen del valor de otros campos o del estado del objeto:
from sqlalchemy import Column, String, Integer, Boolean, DateTime
from datetime import datetime, timedelta
class Reserva(db.Model):
__tablename__ = 'reservas'
id = Column(Integer, primary_key=True)
fecha_inicio = Column(DateTime, nullable=False)
fecha_fin = Column(DateTime, nullable=False)
confirmada = Column(Boolean, default=False)
precio_total = Column(Integer)
descuento_aplicado = Column(Integer, default=0)
@validates('fecha_fin')
def validar_fecha_fin(self, key, fecha_fin):
if self.fecha_inicio and fecha_fin <= self.fecha_inicio:
raise ValueError("La fecha de fin debe ser posterior a la fecha de inicio")
# No permitir reservas de más de 30 días
if self.fecha_inicio and (fecha_fin - self.fecha_inicio).days > 30:
raise ValueError("Las reservas no pueden exceder 30 días")
return fecha_fin
@validates('descuento_aplicado')
def validar_descuento(self, key, descuento):
if descuento < 0 or descuento > 100:
raise ValueError("El descuento debe estar entre 0 y 100")
# Solo permitir descuentos altos en reservas confirmadas
if descuento > 50 and not self.confirmada:
raise ValueError("Descuentos superiores al 50% requieren confirmación previa")
return descuento
Manejo de errores en validaciones
Es importante capturar y manejar adecuadamente los errores de validación en nuestras rutas de Flask:
from flask import request, jsonify
from sqlalchemy.exc import IntegrityError
@app.route('/usuarios', methods=['POST'])
def crear_usuario():
try:
datos = request.get_json()
usuario = Usuario(
email=datos.get('email'),
edad=datos.get('edad'),
telefono=datos.get('telefono')
)
db.session.add(usuario)
db.session.commit()
return jsonify({
'mensaje': 'Usuario creado exitosamente',
'id': usuario.id
}), 201
except ValueError as e:
db.session.rollback()
return jsonify({'error': str(e)}), 400
except IntegrityError as e:
db.session.rollback()
return jsonify({
'error': 'Error de integridad en la base de datos',
'detalle': 'Posible duplicado en campo único'
}), 409
Las validaciones de campo proporcionan una capa sólida de protección que garantiza la calidad de los datos almacenados. Al combinar el decorador @validates con hybrid properties y un manejo adecuado de errores, podemos crear modelos que no solo almacenen datos, sino que también los validen y transformen según las reglas de negocio de nuestra aplicación.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Flask
Documentación oficial de Flask
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Flask es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Flask
Explora más contenido relacionado con Flask y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender la importancia de las validaciones de campo en SQLAlchemy para mantener la integridad de los datos. Aprender a utilizar el decorador @validates para implementar validaciones personalizadas en modelos. Conocer cómo validar múltiples campos y realizar transformaciones durante la asignación de valores. Entender el uso de hybrid_property para validaciones complejas y cálculos dinámicos. Saber manejar errores de validación y excepciones para asegurar un flujo robusto en aplicaciones Flask.