
Pydantic v2: la base de FastAPI modernizada
Pydantic es la librería que FastAPI utiliza internamente para validar todos los datos: bodies de peticiones, parámetros de ruta, query params y modelos de respuesta. La versión 2 de Pydantic fue publicada en 2023 e introduce mejoras sustanciales en rendimiento (hasta 50x más rápida en algunos casos) y en la API de definición de modelos.
FastAPI a partir de la versión 0.100.0 incluye soporte nativo para Pydantic v2, y es la versión activa en 2026.
Cambios principales respecto a Pydantic v1
model_config en lugar de la clase Config
En Pydantic v1, la configuración del modelo se hacía con una clase interna Config. En Pydantic v2 se usa el atributo de clase model_config:
# Pydantic v1 (obsoleto)
class UsuarioV1(BaseModel):
nombre: str
email: str
class Config:
orm_mode = True
str_strip_whitespace = True
# Pydantic v2 (forma actual)
from pydantic import BaseModel, ConfigDict
class Usuario(BaseModel):
model_config = ConfigDict(
from_attributes=True, # Antes: orm_mode = True
str_strip_whitespace=True,
str_min_length=1,
)
nombre: str
email: str
from_attributes en lugar de orm_mode
El cambio más relevante para FastAPI con SQLAlchemy es que orm_mode = True se renombra a from_attributes = True:
from pydantic import BaseModel, ConfigDict
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# Modelo SQLAlchemy (sin cambios)
class ProductoModel(Base):
__tablename__ = "productos"
id = Column(Integer, primary_key=True)
nombre = Column(String(100))
precio = Column(Integer)
# Schema Pydantic v2 para respuestas
class ProductoSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
nombre: str
precio: float
Con from_attributes=True, Pydantic puede leer los atributos de objetos ORM directamente, no solo de diccionarios.
@field_validator: el nuevo validador de campos
En Pydantic v2, el decorador @validator fue reemplazado por @field_validator, con una API más clara y explícita:
from pydantic import BaseModel, field_validator
class Producto(BaseModel):
nombre: str
precio: float
stock: int
@field_validator("nombre")
@classmethod
def nombre_no_vacio(cls, v: str) -> str:
v = v.strip()
if len(v) < 2:
raise ValueError("El nombre debe tener al menos 2 caracteres")
return v.title() # Convierte a Title Case
@field_validator("precio")
@classmethod
def precio_positivo(cls, v: float) -> float:
if v <= 0:
raise ValueError("El precio debe ser mayor que cero")
return round(v, 2) # Redondear a 2 decimales
@field_validator("stock")
@classmethod
def stock_no_negativo(cls, v: int) -> int:
if v < 0:
raise ValueError("El stock no puede ser negativo")
return v
mode="before" vs mode="after"
Los validadores pueden ejecutarse antes o después de la conversión de tipos:
from pydantic import BaseModel, field_validator
class Pedido(BaseModel):
codigo: str
cantidad: int
# Se ejecuta ANTES de la conversión de tipos
# Recibe el valor tal como llegó (puede ser str, int, etc.)
@field_validator("cantidad", mode="before")
@classmethod
def convertir_cantidad(cls, v):
# Acepta "5 unidades" y extrae el número
if isinstance(v, str):
return v.split()[0] # Extrae "5" de "5 unidades"
return v
# Se ejecuta DESPUÉS de la conversión de tipos
# Siempre recibe el tipo ya validado
@field_validator("codigo", mode="after")
@classmethod
def normalizar_codigo(cls, v: str) -> str:
return v.upper().strip()
Validar múltiples campos con un solo validador
from pydantic import BaseModel, field_validator
from typing import Annotated
class Rango(BaseModel):
minimo: float
maximo: float
@field_validator("minimo", "maximo", mode="after")
@classmethod
def no_negativos(cls, v: float) -> float:
if v < 0:
raise ValueError("Los valores no pueden ser negativos")
return v
@model_validator: validación entre campos
Cuando necesitas validar la relación entre varios campos, usa @model_validator:
from pydantic import BaseModel, model_validator
from datetime import date
class Reserva(BaseModel):
fecha_entrada: date
fecha_salida: date
precio_por_noche: float
@model_validator(mode="after")
def validar_fechas(self) -> "Reserva":
if self.fecha_salida <= self.fecha_entrada:
raise ValueError("La fecha de salida debe ser posterior a la de entrada")
return self
@property
def noches(self) -> int:
return (self.fecha_salida - self.fecha_entrada).days
@property
def precio_total(self) -> float:
return self.noches * self.precio_por_noche
Field() con metadata avanzada
Field() permite enriquecer los campos con validaciones declarativas, ejemplos y metadata para la documentación OpenAPI:
from pydantic import BaseModel, Field
from typing import Annotated
class Usuario(BaseModel):
nombre: Annotated[str, Field(
min_length=2,
max_length=50,
description="Nombre completo del usuario",
examples=["Ana García", "Carlos López"]
)]
email: Annotated[str, Field(
pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
description="Correo electrónico válido",
examples=["usuario@ejemplo.com"]
)]
edad: Annotated[int, Field(
ge=0,
le=150,
description="Edad en años"
)]
puntuacion: Annotated[float, Field(
ge=0.0,
le=10.0,
description="Puntuación entre 0 y 10"
)]
Los valores de ge (mayor o igual), le (menor o igual), gt (mayor que), lt (menor que) y pattern se reflejan automáticamente en la documentación Swagger UI y en el esquema OpenAPI.
Serialización: model_dump() y model_dump_json()
En Pydantic v2, dict() y json() fueron reemplazados por model_dump() y model_dump_json():
from pydantic import BaseModel
from datetime import datetime
class Evento(BaseModel):
titulo: str
fecha: datetime
activo: bool = True
evento = Evento(titulo="FastAPI Workshop", fecha=datetime.now())
# Antes (v1): evento.dict()
datos = evento.model_dump()
print(datos)
# {'titulo': 'FastAPI Workshop', 'fecha': datetime(...), 'activo': True}
# Serialización JSON
json_str = evento.model_dump_json()
print(json_str)
# {"titulo":"FastAPI Workshop","fecha":"2026-03-30T...","activo":true}
# Opciones útiles
datos_parciales = evento.model_dump(exclude={"fecha"})
datos_sin_nulos = evento.model_dump(exclude_none=True)
datos_incluidos = evento.model_dump(include={"titulo", "activo"})
Modelos de respuesta en FastAPI con Pydantic v2
FastAPI usa Pydantic v2 automáticamente. Solo hay que asegurarse de usar la nueva sintaxis:
from fastapi import FastAPI
from pydantic import BaseModel, ConfigDict, Field
from typing import Annotated
app = FastAPI()
class ProductoCrear(BaseModel):
nombre: Annotated[str, Field(min_length=2, max_length=100)]
precio: Annotated[float, Field(gt=0)]
descripcion: str | None = None
class ProductoRespuesta(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
nombre: str
precio: float
descripcion: str | None = None
@app.post("/productos/", response_model=ProductoRespuesta, status_code=201)
async def crear_producto(producto: ProductoCrear):
# Simula guardado en BD y retorno del objeto
return {"id": 1, **producto.model_dump()}
@app.get("/productos/{producto_id}", response_model=ProductoRespuesta)
async def obtener_producto(producto_id: int):
return {"id": producto_id, "nombre": "Laptop", "precio": 999.99}
Manejo de campos opcionales con None vs ausencia
Pydantic v2 distingue correctamente entre un campo con valor None y un campo no enviado en el body:
from pydantic import BaseModel
from typing import Optional
class ActualizarUsuario(BaseModel):
nombre: Optional[str] = None
email: Optional[str] = None
bio: Optional[str] = None
# Ejemplo de uso en endpoint PATCH
@app.patch("/usuarios/{usuario_id}")
async def actualizar_usuario(usuario_id: int, datos: ActualizarUsuario):
# model_dump(exclude_unset=True) solo incluye los campos
# que el cliente envió explícitamente
campos_a_actualizar = datos.model_dump(exclude_unset=True)
print(campos_a_actualizar)
# Si el cliente envió {"nombre": "Ana"}, solo aparece {"nombre": "Ana"}
# El email y la bio no se sobrescriben porque no fueron enviados
return {"id": usuario_id, **campos_a_actualizar}
El uso de exclude_unset=True en model_dump() es un patrón esencial para implementar operaciones PATCH correctas en FastAPI.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en FastAPI
Documentación oficial de FastAPI
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, FastAPI 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 FastAPI
Explora más contenido relacionado con FastAPI y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender las diferencias clave entre Pydantic v1 y v2 relevantes para FastAPI. Usar @field_validator y @model_validator para validaciones personalizadas en Pydantic v2. Configurar modelos con model_config en lugar de la clase Config interna. Aprovechar from_attributes (antes orm_mode) para integrar modelos con SQLAlchemy. Usar Field() con metadata avanzada para anotaciones, ejemplos y validaciones declarativas.