Pydantic Validation

Módulo 4: Pydantic y Validación

Descripción de la cápsula

Hasta ahora construiste APIs que reciben dicts con Body(...) y aceptan cualquier dato que llegue. Si alguien envía "year": "abc" o "title": "", tu código o falla en runtime o tienes que validar a mano con decenas de if. Este módulo cambia eso de raíz: Pydantic es la librería que convierte validación, serialización y documentación en algo automático.

Pydantic es lo que hace especial a FastAPI. No es un add-on opcional: está en el core. Define modelos con type hints, y Pydantic se encarga de validar los tipos, convertir valores compatibles ("1967"1967), rechazar datos inválidos con mensajes claros, y generar la documentación de OpenAPI. En una frase: Pydantic = validación automática + serialización + documentación.


Verificación de Prerequisitos

Antes de empezar, asegúrate de tener todo listo. Este módulo asume que completaste los módulos 1, 2 y 3.

Tu API de productos del Módulo 3 debe estar funcionando

En el Módulo 3 construiste una API de productos con filtros, paginación y request body. ¿La tienes corriendo?

  1. Abre el proyecto del Módulo 3.
  2. Activa el entorno virtual (source venv/bin/activate o .\venv\Scripts\activate).
  3. Arranca el servidor: uvicorn main:app --reload (o el nombre de tu archivo).
  4. Comprueba que http://127.0.0.1:8000/docs carga la documentación.
  5. Haz un POST a /products (o la ruta que uses) con un JSON válido y verifica que responde correctamente.

Si algo falla, vuelve a la Cápsula 04 del Módulo 3 (Request Body) y repasa. Este módulo partirá de ese proyecto y sustituiremos los dict por modelos Pydantic.

Recuerdo: ¿qué pasa con dict = Body(...) sin validación?

En el Módulo 3 usaste algo así:

@app.post("/products")
def create_product(product: dict = Body(...)):
    product["id"] = generate_id()
    products.append(product)
    return product

Con dict, FastAPI acepta cualquier JSON que llegue. No hay validación de tipos ni de campos. Eso implica:

  • Puedes recibir {"name": "Laptop", "price": "gratis"} — y tu código podría fallar más tarde al hacer price * 1.21 porque "gratis" no es un número.
  • Puedes recibir {"price": 999} sin name — y guardarás un producto sin nombre.
  • Puedes recibir {"name": "", "year": -500} — datos técnicamente "presentes" pero inválidos.

La validación manual con if se vuelve infinita. Pydantic resuelve esto de forma declarativa.

Test rápido: ¿qué hace Body(...) y qué es un type hint?

Si tienes dudas, repasa estos conceptos:

  • Body(...) — Le dice a FastAPI: "este parámetro viene del cuerpo del request (JSON), es obligatorio". El ... (Ellipsis) significa "requerido". Sin Body(), con un dict la interpretación puede ser ambigua.
  • Type hint — Anotación de tipo en Python: def foo(x: int) significa "x debería ser un int". Los type hints no ejecutan validación por sí solos; Pydantic sí los usa para validar automáticamente.

Con Pydantic, no necesitas Body() para el request body: FastAPI detecta que el parámetro es un BaseModel y asume que viene del body.


Setup Técnico del Módulo

Base del proyecto

Usa el proyecto del Módulo 3 como punto de partida. Si no lo tienes, crea uno nuevo con FastAPI y uvicorn. No hace falta crear un proyecto desde cero: vamos a ir reemplazando progresivamente los dict por modelos Pydantic.

Pydantic ya viene con FastAPI

No necesitas instalar Pydantic por separado: FastAPI lo incluye como dependencia. Pero conviene verificar la versión:

python -c "import pydantic; print(pydantic.__version__)"

Deberías ver algo como 2.9.0 o superior (2.x). Esta guía usa Pydantic v2.

Pydantic v1 vs v2: diferencias de sintaxis

Si en algún tutorial antiguo ves cosas como @validator o .dict(), eso es Pydantic v1. En esta guía usamos Pydantic v2, donde:

  • Los validadores se escriben con @field_validator
  • Para convertir un modelo a diccionario usas model_dump() en vez de .dict()
  • La configuración del modelo usa model_config en lugar de class Config

Si encuentras código v1 en la web, busca la equivalencia en la documentación oficial de Pydantic v2.


Contexto del Módulo

¿Dónde estamos?

Este es el Módulo 4 de la guía FastAPI Fundamentals.

FastAPI Fundamentals Guide
├── Módulo 1: Setup y Primera API ✅
├── Módulo 2: Path Operations ✅
├── Módulo 3: Request y Response ✅
├── Módulo 4: Pydantic y Validación ← ESTÁS AQUÍ
├── Módulo 5: Error Handling y CORS
└── Módulo 6: Proyecto — To-Do List API

Lo que ya dominas

De los módulos anteriores traes: endpoints CRUD, path params, query params, request body con Body(), y datos en memoria. Pero sin validación real: aceptas cualquier dict que llegue.

Lo nuevo de este módulo

  • 🆕 BaseModel: modelos tipados en vez de dicts
  • 🆕 Validación automática (tipo incorrecto → 422 sin ejecutar tu código)
  • 🆕 Field() con constraints (min_length, ge, le, pattern)
  • 🆕 Validadores personalizados con @field_validator
  • 🆕 Modelos anidados y listas de modelos
  • 🆕 Modelos separados para request vs response (no devolver contraseñas)

Conceptos Clave del Módulo

Antes de entrar en código, define estos términos para que no te suenen a chino:

ConceptoQué significa
BaseModelClase de Pydantic que valida datos automáticamente. Declaras campos con type hints y Pydantic los valida al instanciar.
Field()Función para añadir restricciones a un campo: min_length, ge, le, pattern, description, etc.
ValidatorLógica de validación personalizada. Con @field_validator escribes funciones que transforman o validan un campo antes de aceptarlo.
SerializaciónConvertir un objeto Python (ej. tu modelo Pydantic) a un formato que se pueda enviar por la red: JSON, dict, etc.
DeserializaciónConvertir datos que llegan (JSON, dict) a objetos Python. Pydantic deserializa el JSON del request a tu modelo.
model_dump()Método de Pydantic v2 para convertir un modelo a diccionario. Equivale al antiguo .dict() de v1.

En resumen: el cliente envía JSON → Pydantic deserializa y valida → tu función recibe un objeto con tipos correctos → al devolver, FastAPI serializa el modelo a JSON.


El Problema que Pydantic Resuelve

Sin Pydantic: errores en runtime y bugs silenciosos

Imagina que tienes un endpoint que crea productos:

@app.post("/products")
def create_product(product: dict = Body(...)):
    # Supongamos que más tarde haces esto:
    tax = product["price"] * 0.21
    products.append(product)
    return product

El cliente envía:

{"name": "Laptop", "price": "free", "category": "Electrónica"}

Tu código llega a la línea tax = product["price"] * 0.21 y explota con TypeError: unsupported operand type(s) for *: 'str' and 'float'. El producto ya podría estar parcialmente procesado. El error 500 no le dice al cliente que price debe ser un número.

Peor aún: si el cliente envía {"price": 999} sin name, guardas un producto con name ausente. No hay error inmediato, pero más tarde tu frontend o tu lógica pueden fallar de formas extrañas.

Con Pydantic: validación antes de ejecutar

Pydantic valida antes de que tu función se ejecute. Si el dato no cumple las reglas, FastAPI responde con 422 (Unprocessable Entity) y un mensaje detallado indicando qué campo falló y por qué. Tu función ni siquiera se llama.

from pydantic import BaseModel, Field

class ProductCreate(BaseModel):
    name: str = Field(min_length=1)
    price: float = Field(gt=0)
    category: str | None = None

@app.post("/products")
def create_product(product: ProductCreate):
    tax = product.price * 0.21  # Siempre float, ya validado
    # ...

Si llega {"price": "free"}, Pydantic rechaza antes de entrar a tu función. El cliente recibe 422 con un mensaje claro: el tipo de price no es válido. Si llega {"price": 999} sin name, también 422: campo requerido faltante.


Por qué importa Pydantic

En Flask o frameworks sin Pydantic, validar un request body implica escribir funciones como:

def validate_book(data):
    if "title" not in data or not data["title"]:
        raise ValueError("title required")
    if "year" not in data or not isinstance(data["year"], int):
        raise ValueError("year must be int")
    if data["year"] < 1000 or data["year"] > 2030:
        raise ValueError("year out of range")
    # ... 20 líneas más por cada campo

Con Pydantic:

class Book(BaseModel):
    title: str = Field(min_length=1)
    year: int = Field(ge=1000, le=2030)

Una declaración, validación completa, documentación automática.


Modelo Mental: Flujo de Validación

Visualiza así lo que ocurre en cada request:

JSON del cliente  →  Pydantic BaseModel  →  Validación
                                              │
                    ┌─────────────────────────┼─────────────────────────┐
                    │                         │                         │
                    ▼                         ▼                         ▼
              ✅ Todo OK              ❌ Dato inválido          ❌ Tipo incorrecto
                    │                         │                         │
                    ▼                         ▼                         ▼
        Tu función recibe             FastAPI retorna            FastAPI retorna
        datos limpios y              422 con mensaje             422 con detalle
        tipados correctamente        detallado del error         (tu función NO se ejecuta)

Punto clave: si la validación falla, tu endpoint no se ejecuta. FastAPI se encarga de devolver el 422. Tú no tienes que hacer if para comprobar tipos; Pydantic ya lo hizo.


Objetivo del Módulo

Al completar este módulo serás capaz de definir modelos Pydantic para tus APIs, aplicar validaciones declarativas, anidar estructuras complejas y separar modelos de entrada y salida. Es el módulo más importante de la guía porque Pydantic es el corazón de FastAPI.


Mapa del Módulo

CápsulaTemaQué aprenderás
02BaseModelQué hace Pydantic, crear clases, usar en endpoints, 422 automático
03Field y validatorsConstraints, @field_validator, mensajes de error
04Modelos anidadosAddress dentro de User, listas, estructuras complejas
05Request vs ResponseUserCreate vs UserResponse, response_model, excluir campos
06Proyecto: API de contactosCRUD con validación completa y rúbrica

Conexión con el proyecto final

El To-Do List API del Módulo 6 usa modelos Pydantic para tareas: TaskCreate, TaskResponse, validación de prioridades y fechas. Los conceptos que aprendes aquí se aplican directamente. Además, cualquier API real que construyas — usuarios, productos, pedidos — requerirá Pydantic para datos confiables y documentación precisa.

Cuando implementes autenticación (JWT, OAuth), los modelos Pydantic seguirán presentes: un TokenRequest para el login, un TokenResponse para la respuesta. Cuando integres bases de datos, los modelos ORM o los DTOs para las APIs usarán la misma filosofía. Este módulo te da los cimientos que se repiten en todo el ecosistema FastAPI.

El proyecto de la Cápsula 06 (API de contactos) es tu oportunidad de integrar todo: crearás ContactCreate y ContactUpdate para el request, ContactResponse para la respuesta, validarás emails y teléfonos con Field y validators, y verás cómo la documentación en /docs refleja automáticamente tus esquemas. Es un mini-proyecto que replica lo que harás en APIs de producción.


Qué esperar en cada cápsula

  • Cápsula 02 (BaseModel): Crearás tu primera clase que hereda de BaseModel, la usarás en un endpoint en lugar de dict, y verás cómo FastAPI genera el esquema en /docs y devuelve 422 automáticamente cuando envías tipos incorrectos.
  • Cápsula 03 (Field y validators): Añadirás Field() con restricciones como min_length, ge, le y pattern, y escribirás un @field_validator para lógica personalizada (por ejemplo, normalizar un email o validar que dos campos sean coherentes).
  • Cápsula 04 (Modelos anidados): Definirás un modelo Address y lo incluirás dentro de User, trabajarás con listas de modelos y estructuras JSON más complejas.
  • Cápsula 05 (Request vs Response): Separarás modelos de entrada (UserCreate, con contraseña) y de salida (UserResponse, sin contraseña), usarás response_model y model_dump(exclude_unset=True) para actualizaciones parciales.
  • Cápsula 06 (Proyecto): Construirás una API de contactos con CRUD completo, validación en todos los endpoints y una rúbrica de evaluación.

Cómo usar este módulo

  1. Usa tu proyecto del Módulo 3 (o crea uno nuevo con FastAPI y uvicorn)
  2. Reemplaza progresivamente los dict y Body() por modelos BaseModel
  3. Prueba en /docs: verás cómo los esquemas aparecen automáticamente
  4. Envía datos inválidos a propósito para observar los errores 422
  5. Haz los ejercicios de cada cápsula antes de avanzar

Consejos prácticos antes de empezar

Para sacar el máximo partido a este módulo, ten en cuenta lo siguiente:

  1. No copies y pegues sin entender. Cada vez que definas un modelo, pausa y piensa: "¿qué tipos tiene cada campo? ¿qué restricciones tiene sentido aplicar?" La práctica de diseñar modelos te servirá siempre.

  2. Prueba a propósito con datos malos. En /docs, envía {"price": "gratis"}, {"name": ""}, o falta un campo obligatorio. Observa los mensajes 422. Aprende a leerlos: te indican exactamente qué falló.

  3. Revisa la pestaña Schemas en /docs. FastAPI genera esquemas automáticamente a partir de tus modelos. Si algo no aparece o no tiene el formato que esperas, ajusta tu modelo.

  4. Si usas un IDE (VS Code, PyCharm), aprovecha el autocompletado. Con product: ProductCreate, tu IDE sabe que product tiene .name, .price, etc. Los type hints no son solo documentación.

  5. Guarda una versión "antes" de tu API. Antes de sustituir los dict por modelos, haz un backup o commit. Así puedes comparar el antes y el después con claridad.


Límites: Qué NO se hará en este módulo

  • Autenticación y JWT — Guías específicas de seguridad
  • Integración con base de datos (SQLAlchemy) — Módulos avanzados
  • HTTPException y error handling detallado — Módulo 5
  • Testing automatizado — Guías de testing

Aquí te centras en Pydantic puro: modelos, validación y serialización.


Errores comunes al empezar

Cuando empieces con Pydantic, es probable que te encuentres con alguno de estos casos:

  • "ImportError: cannot import BaseModel" — Asegúrate de tener Pydantic instalado. Si usas FastAPI, ya viene incluido. Si trabajas en un proyecto standalone, ejecuta pip install pydantic.
  • "ValidationError" al hacer request — Es normal. Pydantic está rechazando datos inválidos. Lee el mensaje de error: indica el campo que falló y el motivo. Ajusta el JSON que envías o revisa las restricciones de tu modelo.
  • El IDE no reconoce los atributos del modelo — Verifica que tu clase herede de BaseModel y que los campos estén declarados con type hints. Los IDEs modernos infieren los tipos correctamente.
  • Usé @validator y no funciona — Eso es sintaxis de Pydantic v1. En v2 usa @field_validator. Si sigues un tutorial antiguo, busca la guía de migración.
  • Quiero devolver un campo pero no aparece en la respuesta — Revisa si usas response_model y si ese modelo incluye el campo. O si el campo está excluido con model_config o model_dump(exclude=...).

No te bloquees: la mayoría de errores se resuelven leyendo el mensaje de validación y ajustando el modelo o el JSON.


Tiempo estimado

  • Cápsulas 02 y 03 (BaseModel, Field, validators): ~2–2.5 h
  • Cápsulas 04 y 05 (modelos anidados, request vs response): ~1.5–2 h
  • Proyecto 06 (API de contactos): ~1.5–2 h
  • Total módulo: ~5–6.5 horas

Preguntas Frecuentes

¿Pydantic es solo para FastAPI?

No. Pydantic es una librería independiente. Se usa en FastAPI porque encaja perfectamente con el sistema de type hints, pero puedes usarla en scripts, procesos ETL, carga de configuraciones, etc. Cualquier lugar donde necesites validar y parsear datos estructurados.

¿Qué es Pydantic v2 y por qué importa?

Pydantic v2 es una reescritura con mejor rendimiento y API más clara. FastAPI moderno usa v2. La sintaxis cambió: @validator@field_validator, .dict()model_dump(), etc. Si sigues esta guía, estarás en v2.

¿Puedo usar dataclasses en vez de Pydantic?

Los dataclasses de Python son útiles para estructurar datos, pero no validan tipos ni convierten strings a números. Pydantic sí. Para request/response con validación automática, Pydantic es la opción estándar en FastAPI.

¿Pydantic ralentiza mi API?

En la práctica, el impacto es mínimo. Pydantic v2 está optimizado en Rust. La validación ocurre una vez por request; el coste suele ser insignificante frente al tiempo de red o de base de datos. Los beneficios (datos correctos, menos bugs) compensan con creces.

¿Tengo que validar todo? ¿Y los query params?

Para el body, usa modelos Pydantic siempre que los datos sean complejos. Para query params y path params, los type hints de Python (int, str, etc.) ya dan validación básica en FastAPI. Puedes combinarlos: body con Pydantic, query con type hints.


Resumen

  • ✅ Pydantic = validación automática + serialización + documentación
  • ✅ BaseModel convierte type hints en reglas de validación
  • ✅ Tipo incorrecto o dato inválido → FastAPI retorna 422 antes de ejecutar tu endpoint
  • ✅ Este módulo es fundamental: Pydantic es lo que diferencia a FastAPI
  • ✅ Pydantic v2 usa model_dump() y @field_validator; verifica tu versión antes de empezar

Próxima cápsula: BaseModel — tu primer modelo Pydantic y su uso en endpoints FastAPI.


Recursos Adicionales

  1. Pydantic Documentation - Documentación oficial
  2. FastAPI - Request Body - Body con Pydantic
  3. Pydantic Data Validation - Validación en profundidad
  4. Pydantic V2 Migration Guide - Migrar de v1 a v2
  5. FastAPI - Response Model - response_model y exclusión de campos
  6. Pydantic Field Constraints - Referencia de Field() y constraints

Módulo 4 — FastAPI Fundamentals Guide