Pydantic Validation

Introducción al Módulo 4: Pydantic y Validación

Descripción

Pydantic es el corazón de FastAPI. Literalmente. Todo lo que FastAPI hace con validación automática, serialización y documentación se construye sobre Pydantic. Cuando FastAPI rechaza un request con un error 422, es Pydantic quien detectó el problema. Cuando /docs muestra los schemas de tus endpoints, es Pydantic quien los generó. Cuando un body JSON se convierte automáticamente en un objeto Python con atributos tipados, es Pydantic quien hizo la conversión. Hasta ahora usaste Pydantic sin saberlo — cada type hint que escribiste, cada Query() y Path() que definiste, todo pasa por Pydantic internamente. En este módulo vas a usarlo directamente.

Hasta ahora manejas datos con diccionarios y type hints básicos. Tus libros son dicts con keys como "title", "author" y "year". Funciona, pero tiene problemas serios: no hay garantía de que un dict tenga las keys correctas, no hay validación de tipos dentro del dict, no hay autocompletado en tu editor, y si escribes book["tilte"] en vez de book["title"], el error aparece en runtime — no cuando escribes el código. Pydantic reemplaza todo eso con modelos: clases que definen exactamente qué campos tiene un objeto, qué tipo tiene cada campo, qué valores son válidos, y qué pasa si algo falta o está mal.

Este es el módulo más transformador de la guía. No porque sea el más difícil — no lo es — sino porque cambia fundamentalmente cómo piensas sobre los datos en tu API. Después de este módulo, no vas a querer volver a usar dicts para representar entidades. Un modelo Pydantic te da validación, serialización, documentación y type safety en una sola definición. Es la diferencia entre esperar que los datos estén bien y garantizar que los datos están bien.


¿Dónde estamos en la guía?

Estás en el Módulo 4 de 6 de la guía FastAPI Fundamentals:

Módulo 1: Setup y Primera API ✅ (completado)
    → Instalación, uvicorn, endpoints GET, documentación automática

Módulo 2: Path Operations ✅ (completado)
    → GET, POST, PUT, PATCH, DELETE — CRUD completo

Módulo 3: Request y Response ✅ (completado)
    → Query params, Path(), Query(), Body(), filtros, paginación

Módulo 4: Pydantic y Validación ← ESTÁS AQUÍ
    → BaseModel, Field(), validators, modelos separados, response_model

Módulo 5: Error Handling y CORS
Módulo 6: Proyecto Final — To-Do List API

Progresión acumulativa

Cada módulo agrega una capa sobre el anterior:

Módulo 1: Servidor corriendo + endpoints GET básicos
    ↓ (base)
Módulo 2: + CRUD completo (POST, PUT, PATCH, DELETE)
    ↓ (operaciones)
Módulo 3: + Parámetros avanzados, filtros, paginación
    ↓ (datos de entrada)
Módulo 4: + Modelos Pydantic, validación estructurada, contratos  ← AQUÍ
    ↓ (datos estructurados)
Módulo 5: + Error handling + CORS
    ↓ (robustez)
Módulo 6: To-Do List API (integración total)

El Módulo 3 te dio control sobre los parámetros individuales: query params, path params, constraints con Query() y Path(). El Módulo 4 te da control sobre los datos completos: modelos que definen la forma exacta de lo que tu API acepta y retorna.


Lo que ya dominas vs Lo nuevo

Lo que ya dominas

De los Módulos 1-3 traes:

  • ✅ Virtual environment con FastAPI y uvicorn funcionando
  • ✅ Estructura de proyecto app/main.py
  • ✅ Endpoints GET, POST, PUT, PATCH, DELETE
  • ✅ Path parameters con type hints (book_id: int)
  • ✅ Query parameters con valores por defecto y Optional
  • Query(), Path() y Body() con constraints (ge, le, min_length)
  • ✅ Request body como dict
  • ✅ Filtros, búsqueda y paginación en GET /books
  • ✅ Error 422 como feature de protección
  • ✅ Datos en memoria (lista de diccionarios)

Lo nuevo de este módulo

  • 🆕 BaseModel — clases que definen la estructura de tus datos
  • 🆕 Field() — constraints y metadata para campos de modelos
  • 🆕 @field_validator — validaciones custom que van más allá de constraints
  • 🆕 Campos computados con @computed_field
  • 🆕 Modelos anidados (nested models) para datos complejos
  • 🆕 Modelos separados: BookCreate, BookUpdate, BookResponse
  • 🆕 response_model — controlar qué datos retorna un endpoint
  • 🆕 model_dump() — convertir modelo a dict
  • 🆕 model_config — configuración del modelo (ejemplos, aliases, etc.)
  • 🆕 JSON Schema auto-generado — documentación que se escribe sola

De dicts a modelos

AspectoMódulo 3 (tu API actual)Módulo 4 (tu API con Pydantic)
Datos de libros{"title": "...", "author": "..."} (dict)Book(title="...", author="...") (modelo)
Validación del bodyImplícita por tipo (dict)Explícita campo por campo (Field(min_length=1))
AutocompletadoNo — dicts no tienen autocompletadoSí — book.title con tipo conocido
Typos en camposError en runtime (book["tilte"])Error de linting antes de correr (book.tilte → tu editor lo marca)
Documentación en /docsSchema genérico del bodySchema detallado con tipos, constraints, ejemplos
Datos de respuestaRetornas todo el dictresponse_model filtra campos sensibles

El problema: validación manual con dicts

Para entender por qué Pydantic importa tanto, veamos cómo se ve la validación sin él. Imagina que tu endpoint POST necesita validar los datos de un libro nuevo:

@app.post("/books")
def create_book(book: dict):
    # ¿Tiene título?
    if "title" not in book:
        raise HTTPException(400, "title is required")
    if not isinstance(book["title"], str):
        raise HTTPException(400, "title must be a string")
    if len(book["title"]) < 1:
        raise HTTPException(400, "title cannot be empty")
    if len(book["title"]) > 200:
        raise HTTPException(400, "title too long")

    # ¿Tiene autor?
    if "author" not in book:
        raise HTTPException(400, "author is required")
    if not isinstance(book["author"], str):
        raise HTTPException(400, "author must be a string")

    # ¿Tiene año?
    if "year" not in book:
        raise HTTPException(400, "year is required")
    if not isinstance(book["year"], int):
        raise HTTPException(400, "year must be an integer")
    if book["year"] < 1450:
        raise HTTPException(400, "year too old")
    if book["year"] > 2026:
        raise HTTPException(400, "year cannot be in the future")

    # ¿Tiene género?
    if "genre" not in book:
        book["genre"] = "general"  # valor por defecto

    # ¿Tiene disponibilidad?
    if "available" not in book:
        book["available"] = True  # valor por defecto

    # ... y aún no hiciste nada con el libro

Son más de 25 líneas solo para validar 5 campos. Y esto es un ejemplo simplificado — no estás validando formatos, no estás sanitizando datos, no estás manejando campos extra que el cliente podría enviar. Multiplica esto por cada endpoint que recibe datos y tienes un problema serio de mantenimiento.

Además, esta validación tiene problemas sutiles:

  • Si agregas un campo nuevo, tienes que agregar validación en todos los endpoints que lo usen
  • Los mensajes de error no siguen un formato consistente
  • No hay documentación automática — /docs solo ve dict
  • No hay autocompletado — tu editor no sabe qué keys tiene el dict
  • Es fácil olvidar un caso: ¿qué pasa si year es un float como 2020.5?

Este código es lo que escribes cuando no tienes Pydantic. Es lo que eliminas cuando lo tienes.


La solución: Pydantic BaseModel

Ahora veamos cómo se resuelve exactamente el mismo problema con Pydantic:

from pydantic import BaseModel, Field

class BookCreate(BaseModel):
    title: str = Field(min_length=1, max_length=200)
    author: str = Field(min_length=1)
    year: int = Field(ge=1450, le=2026)
    genre: str = "general"
    available: bool = True

@app.post("/books")
def create_book(book: BookCreate):
    # book ya está validado, tipado y documentado
    # book.title, book.author, book.year — con autocompletado
    ...

Siete líneas de modelo reemplazan más de 25 líneas de validación manual. Pero no es solo menos código — es mejor código en cuatro dimensiones:

1. Validación automática. Si el cliente envía {"title": "", "year": "abc"}, Pydantic rechaza el request con un error 422 detallado antes de que tu función se ejecute. No escribiste ni un if.

2. Serialización. book.model_dump() te da un dict limpio. book.model_dump(exclude={"available"}) te da un dict sin el campo available. book.model_dump(exclude_unset=True) te da solo los campos que el cliente envió explícitamente — perfecto para PATCH.

3. Documentación. /docs ahora muestra un schema detallado: title es string, mínimo 1 carácter, máximo 200. year es entero, mínimo 1450, máximo 2026. genre es string, por defecto "general". El cliente sabe exactamente qué enviar sin leer un README.

4. Type safety. Tu editor sabe que book.title es str y book.year es int. Si escribes book.tilte, tu editor lo marca como error inmediatamente — no en producción, no en testing, sino mientras escribes.

Una definición de modelo. Cuatro beneficios. Cero validación manual.


Pydantic v2 — Lo que necesitas saber

Esto es crítico: todos los ejemplos de esta guía usan Pydantic v2. FastAPI moderno (0.100+) usa Pydantic v2 por defecto, y es la versión que instalaste en el Módulo 1. Si buscas tutoriales en internet, vas a encontrar mucho contenido con sintaxis v1 que ya no es la recomendada. Necesitas saber las diferencias para no confundirte.

¿Por qué importa la versión?

Pydantic v2 se reescribió desde cero con un core en Rust. El resultado:

  • 5-50x más rápido que v1 en validación y serialización
  • Sintaxis más limpia con métodos renombrados
  • Mejor integración con FastAPI moderno
  • Modo estricto para validación sin coerción automática

Tabla de diferencias v1 vs v2

OperaciónPydantic v1 (obsoleto)Pydantic v2 (usa esto)
Modelo a dict.dict().model_dump()
Modelo a JSON.json().model_dump_json()
Dict a modelo.parse_obj(data).model_validate(data)
JSON a modelo.parse_raw(json_str).model_validate_json(json_str)
Copiar modelo.copy(update={...}).model_copy(update={...})
Schema JSON.schema().model_json_schema()
Configuraciónclass Config: (clase interna)model_config = ConfigDict(...)
Validador campo@validator("campo")@field_validator("campo")
Validador modelo@root_validator@model_validator

¿Cómo identificar código v1?

Si ves cualquiera de estas en un tutorial, es código v1 — no lo copies tal cual:

# ❌ Pydantic v1 — NO uses esto
book.dict()
Book.parse_obj(data)
book.json()

class Book(BaseModel):
    class Config:
        schema_extra = {"example": {...}}

@validator("title")
def validate_title(cls, v):
    ...
# ✅ Pydantic v2 — USA esto
book.model_dump()
Book.model_validate(data)
book.model_dump_json()

class Book(BaseModel):
    model_config = ConfigDict(json_schema_extra={"example": {...}})

@field_validator("title")
@classmethod
def validate_title(cls, v):
    ...

Los métodos v1 todavía funcionan en Pydantic v2 (emiten deprecation warnings), pero van a desaparecer en v3. Acostúmbrate a la sintaxis v2 desde ahora.

¿Cómo verificar tu versión?

pip show pydantic

Deberías ver Version: 2.x.x. Si ves 1.x.x, actualiza:

pip install --upgrade pydantic

Objetivo del módulo

Al completar este módulo serás capaz de:

  • ✅ Crear modelos BaseModel que definen la estructura exacta de tus datos
  • ✅ Usar Field() con constraints: min_length, max_length, ge, le, pattern
  • ✅ Escribir validadores custom con @field_validator para reglas de negocio
  • ✅ Definir campos computados con @computed_field
  • ✅ Crear modelos anidados para representar datos complejos (libro con autor como objeto)
  • ✅ Separar modelos por operación: BookCreate, BookUpdate, BookResponse
  • ✅ Usar response_model para controlar qué retorna cada endpoint
  • ✅ Serializar modelos con model_dump() y sus opciones (exclude, exclude_unset)
  • ✅ Configurar modelos con model_config (ejemplos en /docs, aliases, modo estricto)
  • ✅ Leer y aprovechar el JSON Schema que Pydantic genera automáticamente
  • ✅ Migrar una API basada en dicts a modelos Pydantic completos

Prerequisitos

Para este módulo necesitas:

  • Módulos 1-3 completados — Books API con filtros, Query(), Path() y Body() funcionando
  • Tu proyecto Books API — La lista de diccionarios con al menos 5 libros, endpoints CRUD y filtros
  • uvicorn corriendo con --reload — Para probar cambios en tiempo real

Verificación rápida

cd fastapi-fundamentals
source venv/bin/activate
uvicorn app.main:app --reload

Abre http://localhost:8000/docs y verifica que puedes:

  1. Listar libros con GET /books con filtros (genre, year, search)
  2. Crear un libro con POST /books enviando un dict JSON
  3. Obtener un libro por ID con GET /books/{book_id} donde book_id tiene validación con Path()
  4. Los parámetros de query tienen constraints (limit con ge=1, le=100, etc.)

Si eso funciona, estás listo para el Módulo 4.

¿Qué pasa si no cumples algún prerequisito?

Prerequisito faltanteQué hacer
No hiciste el Módulo 3Completa las cápsulas 02-05 del Módulo 3 primero
Tu Books API no tiene filtrosVuelve a la Cápsula 05 del Módulo 3 e implementa los query parameters
No usas Query() ni Path()Revisa la Cápsula 03 del Módulo 3 — Field() de Pydantic sigue la misma lógica
Tu Books API no levantaRevisa errores de importación y verifica que FastAPI y Pydantic v2 están instalados

Roadmap del módulo

CápsulaTemaQué aprenderás
01Introducción (esta cápsula)Contexto, el problema de los dicts, Pydantic v2, roadmap
02BaseModel y Field()Crear modelos, constraints con Field(), model_dump(), model_config
03Validators y nested models@field_validator, @computed_field, modelos anidados, datos complejos
04Request vs Response modelsModelos separados por operación, response_model, filtrar datos sensibles
05Proyecto: Books API con PydanticMigración completa de dicts a modelos Pydantic

Flujo de aprendizaje

Primero aprenderás a definir modelos con BaseModel y a agregar constraints con Field() — la base de todo lo que sigue (Cápsula 02). Después explorarás validaciones custom con @field_validator para reglas que los constraints no cubren, campos computados, y modelos anidados para representar datos con estructura compleja (Cápsula 03). Luego aprenderás la práctica profesional de separar modelos por operación — un modelo para crear, otro para actualizar, otro para responder — y cómo response_model te permite controlar exactamente qué datos salen de tu API (Cápsula 04). Al final, integrarás todo migrando tu Books API completa de dicts a modelos Pydantic (Cápsula 05).

La progresión es: definir → validar → separar → integrar.

Detalle por cápsula

Cápsula 02 — BaseModel y Field(): Tu primera clase Pydantic. Crearás un modelo Book que define los campos de un libro con tipos, constraints y valores por defecto. Aprenderás Field() para agregar validación (min_length, max_length, ge, le, pattern), metadata (description, title, examples) y valores por defecto. Verás cómo model_dump() convierte tu modelo a dict (y sus opciones: exclude, include, by_alias, exclude_unset). Configurarás el modelo con model_config para agregar ejemplos que aparecen en /docs.

Cápsula 03 — Validators y nested models: Cuando Field(min_length=1) no es suficiente, necesitas @field_validator. Escribirás validadores para reglas de negocio: "el título debe empezar con mayúscula", "el año no puede ser futuro", "el género debe ser de una lista permitida." Aprenderás @computed_field para campos que se calculan a partir de otros. Y crearás modelos anidados: un Book que tiene un Author como campo, no solo un string — representando relaciones entre datos.

Cápsula 04 — Request vs Response models: En producción, no usas el mismo modelo para todo. BookCreate define qué envía el cliente (sin id). BookUpdate tiene todos los campos opcionales (para PATCH). BookResponse controla qué retorna la API (sin campos internos). Aprenderás response_model en el decorador del endpoint para que FastAPI filtre automáticamente los datos de respuesta. Verás por qué esto importa para seguridad: si tu libro tiene un campo internal_notes, response_model=BookResponse garantiza que nunca se expone al cliente.

Cápsula 05 — Proyecto Books API con Pydantic: La migración completa. Tus endpoints dejan de recibir dict y empiezan a recibir BookCreate y BookUpdate. Las respuestas pasan por BookResponse. La documentación en /docs se transforma: schemas detallados con tipos, constraints, ejemplos. El almacenamiento interno sigue siendo en memoria, pero los datos están validados y tipados. Es el upgrade más visible de toda la guía.


El concepto central: modelos como contratos

Un modelo Pydantic es un contrato entre tu API y sus consumidores. Es una declaración formal que dice: "Mi API acepta exactamente ESTO y retorna exactamente ESTO." No es una sugerencia ni una convención — es una garantía enforzada automáticamente.

¿Qué te da una sola definición de modelo?

Cuando defines un modelo como este:

class BookCreate(BaseModel):
    title: str = Field(min_length=1, max_length=200)
    author: str = Field(min_length=1)
    year: int = Field(ge=1450, le=2026)
    genre: str = "general"

Obtienes cuatro cosas simultáneamente:

1. Validación. Si alguien envía {"title": "", "year": 3000}, Pydantic rechaza el request antes de que tu código se ejecute. No escribiste ningún if. El modelo define las reglas, Pydantic las enforza.

2. Serialización. book.model_dump() te da {"title": "El Principito", "author": "Saint-Exupéry", "year": 1943, "genre": "general"}. Con exclude_unset=True, solo los campos que el cliente envió explícitamente — ideal para PATCH updates.

3. Documentación. /docs muestra automáticamente un schema interactivo: qué campos existen, qué tipo tiene cada uno, cuáles son obligatorios, cuáles opcionales, qué valores por defecto tienen, y qué constraints aplican. Sin escribir una línea de documentación.

4. Type safety. Tu editor sabe que book.title es str y book.year es int. Autocompletado funciona. Errores de typo se detectan antes de ejecutar. Refactoring es seguro — si renombras un campo, tu editor te muestra todos los lugares que necesitan cambiar.

El contrato en la práctica

Sin contrato (dicts):

Cliente envía lo que quiere → Tu código espera lo mejor → Errores en runtime

Con contrato (modelos):

Cliente envía datos → Pydantic valida contra el contrato → Solo datos válidos llegan a tu código

La diferencia no es cosmética. En una API con dicts, un typo del cliente ("tittle" en vez de "title") pasa silenciosamente y tu libro se crea sin título. Con un modelo Pydantic, ese request se rechaza inmediatamente con un error que dice exactamente qué campo esperaba la API.

Contratos en la vida del proyecto

El concepto de contrato conecta todo el módulo:

  • Cápsula 02: Defines el contrato base con BaseModel y Field()
  • Cápsula 03: Extiendas el contrato con reglas de negocio (@field_validator)
  • Cápsula 04: Creas contratos diferentes para cada operación (crear, actualizar, responder)
  • Cápsula 05: Aplicas los contratos a tu Books API real

Cuando pienses en Pydantic, piensa en contratos. "¿Qué contrato tiene este endpoint?" es la pregunta que resuelve ambigüedad, previene bugs, y hace que tu API sea predecible.


¿Qué NO se cubre en este módulo?

Es importante saber los límites para no buscar algo que viene después o que está fuera del scope:

  • SQLAlchemy models / modelos de base de datos — Pydantic models no son ORM models. Esta guía trabaja con datos en memoria. SQLAlchemy se cubre en el path de Backend Python Developer
  • Discriminated unions — Pydantic soporta Union discriminado con Discriminator, pero es un patrón avanzado fuera del scope de fundamentals
  • Generic models — Modelos genéricos con TypeVar son un patrón avanzado
  • Pydantic SettingsBaseSettings para configuración con variables de entorno se cubre en guías avanzadas
  • Custom types — Crear tus propios tipos Pydantic queda para después
  • HTTPException personalizado — Se cubre en Módulo 5. Aquí usas el 422 automático
  • CORS — Se cubre en Módulo 5

¿Por qué estos límites?

  • SQLAlchemy es otra capa. Pydantic define la forma de los datos en la API. SQLAlchemy define la forma de los datos en la base de datos. Son complementarios, no lo mismo. Mezclarlos aquí confundiría ambos conceptos
  • Discriminated unions y generics son para APIs complejas. Cuando tu API tiene un campo que puede ser "tipo A o tipo B dependiendo de un discriminador", necesitas unions. Pero tu Books API no tiene esa complejidad aún
  • Pydantic Settings resuelve un problema diferente. No es sobre validar requests, es sobre leer configuración del entorno (.env, variables de entorno). Es fundamental en producción, pero no en fundamentos
  • HTTPException viene después porque primero necesitas modelos para que tus errores tengan contexto. "El campo title es muy corto" tiene más sentido cuando tienes un modelo que define qué es "muy corto"

Cada límite es intencional. Primero dominas modelos básicos con constraints (M4), después manejas errores profesionalmente (M5), y en el proyecto final (M6) integras todo.


Conexión con el proyecto

Tu Books API va a experimentar la transformación más visible de toda la guía en este módulo. Así se ve el cambio:

Antes (Módulo 3) — dicts por todas partes

@app.post("/books")
def create_book(book: dict):
    book["id"] = next_id()
    books.append(book)
    return book  # retorna todo, sin control
  • El body es un dict — cualquier JSON es válido
  • No hay validación de campos individuales
  • No hay documentación del schema en /docs
  • Retornas todo el dict sin filtrar campos

Después (Módulo 4) — modelos Pydantic

@app.post("/books", response_model=BookResponse)
def create_book(book: BookCreate):
    new_book = book.model_dump()
    new_book["id"] = next_id()
    books.append(new_book)
    return new_book  # response_model filtra automáticamente
  • El body es BookCreate — solo los campos definidos, validados
  • Cada campo tiene constraints (title mínimo 1 carácter, year entre 1450 y 2026)
  • /docs muestra schema detallado con tipos, constraints y ejemplos
  • response_model=BookResponse controla exactamente qué retorna el endpoint

La Books API como proyecto evolutivo

Módulo 1: Hello World API (esqueleto vacío)
    ↓
Módulo 2: Books API con CRUD (operaciones básicas con dicts)
    ↓
Módulo 3: Books API con filtros y validación de params
    ↓
Módulo 4: Books API con Pydantic (dicts → modelos)  ← AQUÍ
    ↓
Módulo 5: Books API con error handling y CORS
    ↓
Módulo 6: To-Do List API (proyecto nuevo integrando todo)

La transición de dicts a modelos es el momento donde tu API deja de ser un prototipo y empieza a parecer código profesional. Los endpoints se ven más limpios, la documentación se ve más rica, y la validación pasa de ser algo que haces manualmente a algo que simplemente existe.


Cómo usar esta guía en el Módulo 4

Enfoque práctico

Cada cápsula incluye código que debes escribir y probar. El ciclo de este módulo es:

  1. Lee la explicación — Entiende el concepto antes de escribir
  2. Define el modelo — Crea la clase BaseModel con sus campos y constraints
  3. Úsalo en un endpoint — Reemplaza dict por tu modelo como type hint del parámetro
  4. Revisa /docs — Observa cómo el schema se actualiza automáticamente
  5. Prueba con datos válidos e inválidos — Verifica que la validación funcione en ambos casos

Tiempo estimado

  • Cápsula de introducción (esta): 15-20 minutos
  • Cápsulas técnicas (02-04): 30-45 minutos cada una
  • Proyecto (05): 45-60 minutos
  • Total Módulo 4: 2.5-3.5 horas

Tip de testing

Cada vez que crees o modifiques un modelo, abre /docs y haz dos cosas: primero, revisa el schema que aparece — verifica que los tipos, constraints y valores por defecto son correctos. Segundo, envía un request con datos inválidos a propósito: un título vacío, un año negativo, un campo extra que no existe en el modelo. El error 422 que recibes te confirma que Pydantic está haciendo su trabajo. Si envías datos inválidos y el request pasa, algo está mal en tu modelo.


Evidencia de éxito

Al terminar este módulo (las 5 cápsulas), sabrás que tuviste éxito si:

  • ✅ Tienes al menos 3 modelos Pydantic: BookCreate, BookUpdate, BookResponse
  • ✅ Cada modelo usa Field() con constraints relevantes (min_length, max_length, ge, le)
  • ✅ Al menos un modelo tiene un @field_validator custom
  • ✅ La documentación en /docs muestra schemas detallados con tipos, constraints y ejemplos
  • ✅ Tu endpoint POST /books rechaza datos inválidos con errores 422 descriptivos
  • ✅ Tu endpoint PATCH /books/{book_id} acepta updates parciales usando exclude_unset=True
  • response_model en tus endpoints filtra campos que no deben exponerse al cliente
  • ✅ Puedes convertir un modelo a dict con model_dump() y sus opciones
  • ✅ Ningún endpoint de tu Books API recibe dict como tipo de body — todos usan modelos
  • ✅ Entiendes la diferencia entre un modelo de request y un modelo de response
  • ✅ Usas exclusivamente sintaxis Pydantic v2 (model_dump(), model_config, @field_validator)

Resumen

  • Este es el Módulo 4 de 6, enfocado en reemplazar dicts por modelos Pydantic
  • Pydantic es el corazón de FastAPI — validación, serialización, documentación y type safety en una sola definición
  • Un modelo Pydantic es un contrato: "mi API acepta exactamente ESTO y retorna exactamente ESTO"
  • La validación manual con dicts no escala — Pydantic elimina todo ese código repetitivo
  • Todos los ejemplos usan Pydantic v2: model_dump(), @field_validator, model_config
  • Field() en modelos funciona como Query() y Path() — constraints, metadata, valores por defecto
  • Modelos separados por operación (BookCreate, BookUpdate, BookResponse) son práctica profesional
  • response_model controla qué datos salen de tu API — fundamental para seguridad
  • Tu Books API se transforma de dicts a modelos Pydantic — el upgrade más visible de la guía
  • No se cubre: SQLAlchemy/ORM models, discriminated unions, generic models, Pydantic Settings, HTTPException (M5)
  • La progresión es: definir → validar → separar → integrar

Recursos adicionales

  1. Pydantic v2 - Documentación oficial - Referencia completa de Pydantic v2 con guías de migración
  2. FastAPI - Pydantic Models (Request Body) - Tutorial oficial de cómo FastAPI usa Pydantic para request bodies
  3. FastAPI - Response Model - Cómo usar response_model para filtrar datos de respuesta
  4. Pydantic v2 - Field Types - Referencia de Field() con todos los constraints disponibles
  5. Pydantic v2 - Validators - Guía de @field_validator y @model_validator
  6. Pydantic v2 - Migration Guide - Diferencias detalladas entre v1 y v2

Siguiente cápsula: BaseModel y Field() — Crearás tu primer modelo Pydantic, agregarás constraints con Field(), y verás cómo una clase reemplaza toda la validación manual que nunca quisiste escribir.