Validaciones y constraints

Intermedio
FastAPI
FastAPI
Actualizado: 01/07/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Constraints a nivel de base de datos (unique, not null)

Los constraints o restricciones de base de datos son reglas que se aplican directamente en la estructura de la base de datos para garantizar la integridad de los datos. Estas restricciones actúan como una primera línea de defensa, validando los datos antes de que se almacenen permanentemente.

En SQLAlchemy, podemos definir constraints directamente en nuestros modelos utilizando parámetros específicos en las columnas. Los dos constraints más fundamentales son nullable y unique, que nos permiten controlar si una columna puede contener valores nulos y si debe mantener valores únicos respectivamente.

Constraint NOT NULL con nullable=False

El constraint NOT NULL garantiza que una columna siempre contenga un valor. En SQLAlchemy, esto se controla mediante el parámetro nullable:

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class Usuario(Base):
    __tablename__ = "usuarios"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String(50), nullable=False)  # Campo obligatorio
    email = Column(String(100), nullable=False)  # Campo obligatorio
    telefono = Column(String(20), nullable=True)  # Campo opcional

Cuando intentamos insertar un registro sin proporcionar valores para las columnas marcadas como nullable=False, la base de datos rechazará la operación:

# Esto generará un error de base de datos
usuario_incompleto = Usuario(telefono="123456789")
# Error: NOT NULL constraint failed: usuarios.nombre

Constraint UNIQUE para valores únicos

El constraint UNIQUE asegura que no existan valores duplicados en una columna específica. Esto es especialmente útil para campos como emails, nombres de usuario o códigos identificadores:

class Usuario(Base):
    __tablename__ = "usuarios"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String(50), nullable=False)
    email = Column(String(100), nullable=False, unique=True)  # Email único
    username = Column(String(30), nullable=False, unique=True)  # Username único
    telefono = Column(String(20), nullable=True)

Si intentamos crear dos usuarios con el mismo email, la base de datos impedirá la operación:

# Primer usuario - se crea correctamente
usuario1 = Usuario(
    nombre="Ana García",
    email="ana@ejemplo.com",
    username="ana_garcia"
)

# Segundo usuario con email duplicado - generará error
usuario2 = Usuario(
    nombre="Carlos López",
    email="ana@ejemplo.com",  # Email duplicado
    username="carlos_lopez"
)
# Error: UNIQUE constraint failed: usuarios.email

Combinando múltiples constraints

Es común combinar varios constraints en una misma columna para crear reglas de validación más estrictas:

class Producto(Base):
    __tablename__ = "productos"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String(100), nullable=False)
    codigo_sku = Column(String(20), nullable=False, unique=True)  # Obligatorio y único
    precio = Column(Integer, nullable=False)  # Precio en céntimos
    descripcion = Column(String(500), nullable=True)  # Opcional

Ventajas de los constraints de base de datos

Los constraints a nivel de base de datos ofrecen garantías absolutas de integridad de datos, independientemente de cómo se acceda a la base de datos:

  • Protección completa: Funcionan aunque se acceda a la base de datos directamente, sin pasar por la aplicación
  • Rendimiento: Se evalúan de forma muy eficiente por el motor de base de datos
  • Consistencia: Garantizan que las reglas se apliquen siempre, sin excepciones
# Ejemplo completo de modelo con constraints
class Empleado(Base):
    __tablename__ = "empleados"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String(100), nullable=False)
    email_corporativo = Column(String(150), nullable=False, unique=True)
    numero_empleado = Column(String(10), nullable=False, unique=True)
    departamento = Column(String(50), nullable=False)
    salario = Column(Integer, nullable=True)  # Puede ser confidencial

Los constraints de base de datos actúan como la última línea de defensa en la validación de datos. Mientras que las validaciones de Pydantic verifican los datos antes de procesarlos en la API, los constraints de SQLAlchemy garantizan que solo los datos válidos lleguen finalmente a la base de datos, creando un sistema de validación robusto y confiable.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Validaciones en Pydantic vs SQLAlchemy

Cuando trabajamos con FastAPI y SQLAlchemy, tenemos dos niveles diferentes de validación que actúan en momentos distintos del flujo de datos. Comprender cuándo y dónde aplicar cada tipo de validación es fundamental para crear aplicaciones robustas y eficientes.

Diferencias fundamentales entre ambos sistemas

Pydantic se encarga de validar los datos que llegan a través de la API, mientras que SQLAlchemy valida los datos justo antes de almacenarlos en la base de datos. Esta separación nos permite crear un sistema de validación en capas que protege nuestra aplicación desde múltiples puntos.

Las validaciones de Pydantic son más flexibles y expresivas, permitiendo reglas complejas, transformaciones de datos y mensajes de error personalizados. Por otro lado, las validaciones de SQLAlchemy son más básicas pero garantizadas, ya que se aplican directamente en la base de datos.

Validaciones de entrada con Pydantic

Pydantic nos permite definir reglas de validación sofisticadas en nuestros modelos de entrada. Estas validaciones se ejecutan cuando los datos llegan a través de las peticiones HTTP:

from pydantic import BaseModel, Field, EmailStr, validator
from typing import Optional

class UsuarioCreate(BaseModel):
    nombre: str = Field(..., min_length=2, max_length=50)
    email: EmailStr
    edad: int = Field(..., ge=18, le=120)  # Entre 18 y 120 años
    telefono: Optional[str] = Field(None, regex=r'^\+?[1-9]\d{8,14}$')
    
    @validator('nombre')
    def validar_nombre(cls, v):
        if not v.strip():
            raise ValueError('El nombre no puede estar vacío')
        return v.title()  # Capitaliza el nombre

Validaciones de persistencia con SQLAlchemy

SQLAlchemy proporciona validaciones más básicas pero esenciales que se aplican al nivel de base de datos. Estas actúan como la última barrera de protección:

from sqlalchemy import Column, Integer, String, CheckConstraint

class Usuario(Base):
    __tablename__ = "usuarios"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String(50), nullable=False)
    email = Column(String(100), nullable=False, unique=True)
    edad = Column(Integer, nullable=False)
    telefono = Column(String(20), nullable=True)
    
    # Constraint adicional a nivel de base de datos
    __table_args__ = (
        CheckConstraint('edad >= 18', name='check_edad_minima'),
    )

Cuándo usar cada tipo de validación

Usa validaciones de Pydantic para:

  • Formato y estructura de datos de entrada
  • Transformaciones automáticas (como capitalizar nombres)
  • Validaciones complejas que requieren lógica de negocio
  • Mensajes de error amigables para el usuario
class ProductoCreate(BaseModel):
    nombre: str = Field(..., min_length=3, max_length=100)
    precio: float = Field(..., gt=0, description="Precio debe ser mayor a 0")
    categoria: str = Field(..., regex=r'^[A-Za-z\s]+$')
    
    @validator('precio')
    def redondear_precio(cls, v):
        return round(v, 2)  # Redondea a 2 decimales

Usa constraints de SQLAlchemy para:

  • Integridad de datos crítica (unique, not null)
  • Restricciones que deben cumplirse siempre, independientemente del origen de los datos
  • Validaciones simples que puede manejar eficientemente la base de datos
class Producto(Base):
    __tablename__ = "productos"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String(100), nullable=False)
    precio = Column(Integer, nullable=False)  # Precio en céntimos
    categoria = Column(String(50), nullable=False)
    codigo_sku = Column(String(20), nullable=False, unique=True)

Ejemplo práctico: validación en capas

Veamos cómo implementar un sistema completo de validación que combina ambos enfoques:

# Modelo Pydantic para validación de entrada
class EmpleadoCreate(BaseModel):
    nombre: str = Field(..., min_length=2, max_length=100)
    email: EmailStr
    numero_empleado: str = Field(..., regex=r'^EMP\d{4}$')
    salario: Optional[int] = Field(None, ge=30000, le=200000)
    
    @validator('numero_empleado')
    def validar_formato_empleado(cls, v):
        if not v.startswith('EMP'):
            raise ValueError('El número de empleado debe comenzar con EMP')
        return v.upper()

# Modelo SQLAlchemy para persistencia
class Empleado(Base):
    __tablename__ = "empleados"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String(100), nullable=False)
    email = Column(String(150), nullable=False, unique=True)
    numero_empleado = Column(String(10), nullable=False, unique=True)
    salario = Column(Integer, nullable=True)

Flujo de validación completo

En una petición típica, los datos pasan por ambos niveles de validación:

@app.post("/empleados/", response_model=EmpleadoResponse)
def crear_empleado(empleado: EmpleadoCreate, db: Session = Depends(get_db)):
    # 1. Pydantic ya validó los datos de entrada
    # 2. Creamos el objeto SQLAlchemy
    db_empleado = Empleado(**empleado.dict())
    
    # 3. SQLAlchemy validará constraints al hacer commit
    db.add(db_empleado)
    db.commit()  # Aquí se aplican los constraints de BD
    db.refresh(db_empleado)
    
    return db_empleado

Esta arquitectura en capas nos proporciona máxima flexibilidad en la entrada de datos y máxima seguridad en el almacenamiento. Pydantic maneja la experiencia del usuario con validaciones expresivas y mensajes claros, mientras que SQLAlchemy garantiza la integridad absoluta de los datos en la base de datos.

Aprendizajes de esta lección

  • Comprender qué son los constraints a nivel de base de datos y su importancia.
  • Aprender a definir constraints básicos como NOT NULL y UNIQUE en modelos SQLAlchemy.
  • Diferenciar entre validaciones en Pydantic y constraints en SQLAlchemy y cuándo usar cada uno.
  • Implementar validaciones en capas combinando Pydantic para entrada y SQLAlchemy para persistencia.
  • Entender el flujo completo de validación en aplicaciones web con FastAPI y bases de datos relacionales.

Completa FastAPI y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración