IF0100 - Programación OO II

Unidad 3: Desarrollo Web con FastAPI

Clase 2: Pydantic y Validación

Martes, 07 de abril de 2026 Semana 10 - Martes (120 minutos) 60 min Teoría 60 min Práctica

E4: Lab Persistencia - Jueves 23/04/2026 (16 días)

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ámetroDescripciónEjemplo
min_lengthLongitud mínimaField(min_length=3)
max_lengthLongitud máximaField(max_length=50)
geMayor o igualField(ge=0)
leMenor o igualField(le=100)
gtMayor queField(gt=0)
ltMenor queField(lt=100)
patternRegexField(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.
← Anterior: FastAPI Intro
Clase 12 de 25
Siguiente: Dependencias →