Objetivos de Aprendizaje
- Entender qué es Pydantic y para qué sirve
- Crear modelos con BaseModel
- Validar datos automáticamente
- Usar Field para validaciones avanzadas
- Integrar Pydantic con FastAPI
¿Qué es Pydantic? (10 min)
Pydantic es una librería para validación de datos usando type hints de Python.
Ventajas
- ✅ Valida datos automáticamente
- ✅ Convierte tipos automáticamente (str → int)
- ✅ Muestra errores claros
- ✅ Integrado con FastAPI
pip install pydantic
Modelos BaseModel (20 min)
Los modelos definen la estructura de los datos.
from pydantic import BaseModel
class Usuario(BaseModel):
id: int
username: str
email: str
edad: int
# Crear instancia
usuario = Usuario(
id=1,
username="juan",
email="juan@email.com",
edad=25
)
print(usuario)
# id=1 username='juan' email='juan@email.com' edad=25
# Acceder a atributos
print(usuario.username) # "juan"
# Convertir a dict
print(usuario.model_dump())
# {'id': 1, 'username': 'juan', 'email': 'juan@email.com', 'edad': 25}
# Convertir a JSON
print(usuario.model_dump_json())
# '{"id":1,"username":"juan","email":"juan@email.com","edad":25}'
Campos Opcionales
from typing import Optional
from pydantic import BaseModel
class Usuario(BaseModel):
id: int
username: str
email: str
edad: Optional[int] = None # Opcional
telefono: Optional[str] = None # Opcional
# Puede no incluir edad ni telefono
u1 = Usuario(id=1, username="juan", email="juan@email.com")
u2 = Usuario(id=2, username="ana", email="ana@email.com", edad=30)
Valores por Defecto
class Tarea(BaseModel):
id: int
titulo: str
descripcion: str = "" # Valor por defecto
completada: bool = False # Valor por defecto
prioridad: int = 1 # Baja=1, Media=2, Alta=3
t = Tarea(id=1, titulo="Hacer ejercicio")
print(t.descripcion) # ""
print(t.completada) # False
print(t.prioridad) # 1
Validaciones (20 min)
Validación Básica
from pydantic import BaseModel, EmailStr, Field
class Usuario(BaseModel):
id: int
username: str = Field(..., min_length=3, max_length=50)
email: EmailStr # Valida formato de email
edad: int = Field(..., ge=0, le=120) # ge=greater/equal, le=less/equal
# ✅ Válido
u = Usuario(id=1, username="juan", email="juan@email.com", edad=25)
# ❌ Error: username muy corto
u = Usuario(id=1, username="ab", email="juan@email.com", edad=25)
# ValidationError: username: ensure this value has at least 3 characters
# ❌ Error: edad fuera de rango
u = Usuario(id=1, username="juan", email="juan@email.com", edad=150)
# ValidationError: edad: ensure this value is less than or equal to 120
# ❌ Error: email inválido
u = Usuario(id=1, username="juan", email="no-es-email", edad=25)
# ValidationError: email: value is not a valid email address
Field - Opciones de Validación
| Parámetro | Descripción | Ejemplo |
|---|---|---|
min_length | Longitud mínima | Field(min_length=3) |
max_length | Longitud máxima | Field(max_length=50) |
ge | Mayor o igual | Field(ge=0) |
le | Menor o igual | Field(le=100) |
gt | Mayor que | Field(gt=0) |
lt | Menor que | Field(lt=100) |
pattern | Regex | Field(pattern=r'^[a-z]+$') |
Ejemplo Completo: Tarea
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class Tarea(BaseModel):
id: int = Field(..., ge=1)
titulo: str = Field(..., min_length=1, max_length=100)
descripcion: Optional[str] = Field(None, max_length=500)
completada: bool = False
prioridad: int = Field(default=1, ge=1, le=5)
fecha_creacion: datetime = Field(default_factory=datetime.now)
class Config:
# Permite crear desde atributos (usuario.titulo)
from_attributes = True
t = Tarea(
id=1,
titulo="Aprender Pydantic",
descripcion="Estudiar validación de datos",
prioridad=3
)
print(t.model_dump())
FastAPI + Pydantic (10 min)
Request Body con Modelos
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class CrearUsuarioRequest(BaseModel):
username: str = Field(..., min_length=3)
email: str
edad: int = Field(..., ge=0, le=120)
class UsuarioResponse(BaseModel):
id: int
username: str
email: str
edad: int
# POST con modelo
@app.post("/usuarios", response_model=UsuarioResponse)
def crear_usuario(usuario: CrearUsuarioRequest):
# FastAPI valida automáticamente el request body
# Si los datos son inválidos, retorna error 422
# Simular creación en BD
nuevo_usuario = {
"id": 1,
"username": usuario.username,
"email": usuario.email,
"edad": usuario.edad
}
return nuevo_usuario
# PUT con modelo
@app.put("/usuarios/{usuario_id}")
def actualizar_usuario(
usuario_id: int,
usuario: CrearUsuarioRequest
):
return {
"id": usuario_id,
"username": usuario.username,
"message": "Usuario actualizado"
}
Errores de Validación
FastAPI retorna automáticamente errores 422 con detalles:
{
"detail": [
{
"loc": ["body", "username"],
"msg": "ensure this value has at least 3 characters",
"type": "value_error.any_str.min_length",
"ctx": {"limit_value": 3}
}
]
}
Ejercicio: API con Validación (10 min)
Crea la API de Tareas con validación completa.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
app = FastAPI(title="TaskFlow API con Validación")
class TareaBase(BaseModel):
titulo: str = Field(..., min_length=1, max_length=100)
descripcion: Optional[str] = Field(None, max_length=500)
prioridad: int = Field(default=1, ge=1, le=5)
class TareaCreate(TareaBase):
pass
class Tarea(TareaBase):
id: int
completada: bool = False
fecha_creacion: datetime
# Base de datos simulada
tareas_db = []
contador_id = 1
@app.post("/tareas", response_model=Tarea, status_code=201)
def crear_tarea(tarea: TareaCreate):
global contador_id
nueva_tarea = Tarea(
id=contador_id,
**tarea.model_dump(),
fecha_creacion=datetime.now()
)
tareas_db.append(nueva_tarea)
contador_id += 1
return nueva_tarea
@app.get("/tareas", response_model=List[Tarea])
def listar_tareas():
return tareas_db
@app.get("/tareas/{tarea_id}", response_model=Tarea)
def obtener_tarea(tarea_id: int):
for tarea in tareas_db:
if tarea.id == tarea_id:
return tarea
raise HTTPException(status_code=404, detail="Tarea no encontrada")
@app.put("/tareas/{tarea_id}", response_model=Tarea)
def actualizar_tarea(tarea_id: int, tarea_data: TareaCreate):
for i, tarea in enumerate(tareas_db):
if tarea.id == tarea_id:
# Actualizar campos
tareas_db[i].titulo = tarea_data.titulo
tareas_db[i].descripcion = tarea_data.descripcion
tareas_db[i].prioridad = tarea_data.prioridad
return tareas_db[i]
raise HTTPException(status_code=404, detail="Tarea no encontrada")
@app.delete("/tareas/{tarea_id}")
def eliminar_tarea(tarea_id: int):
for i, tarea in enumerate(tareas_db):
if tarea.id == tarea_id:
del tareas_db[i]
return {"message": "Tarea eliminada"}
raise HTTPException(status_code=404, detail="Tarea no encontrada")
Beneficio: Con Pydantic, no necesitas escribir código de validación manual. FastAPI valida automáticamente y devuelve errores claros.
Resumen
- Pydantic: Valida datos con type hints
- BaseModel: Clase base para modelos
- Field: Valida longitud, rango, regex
- Optional: Campos que pueden ser None
- EmailStr: Valida emails
- FastAPI: Usa modelos para request/response
- Errores 422: Datos inválidos retornan error automático
Práctica: Agrega validación al modelo de Usuario: username único, email válido, contraseña mínima 8 caracteres.