Skip to content

Capítulo 3.3: Modelos de datos con Pydantic

Tiempo de estudio: 50 minutos


1. Pydantic: El "plano digital" de una nave espacial

Imagina que estás construyendo una nave espacial. No puedes simplemente soldar piezas de metal al azar. Necesitas un plano detallado que defina:

  • Nombre de la nave (tipo: cadena, longitud máx.: 50 caracteres)
  • Año de lanzamiento (tipo: entero)
  • Presencia de hiperpropulsor (tipo: sí/no)

Pydantic es una librería que permite crear estos "planos digitales" para tus datos en Python. En FastAPI, cumple tres funciones clave:

  1. Declaración de estructura: Describe claramente de qué campos se componen tus datos.
  2. Validación de datos: Verifica automáticamente si los datos entrantes cumplen con el plano.
  3. Documentación: FastAPI utiliza estos planos para generar una documentación detallada e interactiva.

💡 Analogía espacial: Un modelo Pydantic es el pasaporte técnico de un objeto. Cualquier "carga" (datos) que llegue a la estación debe cumplir con las especificaciones del pasaporte. Si no, el ordenador de a bordo (Pydantic) la rechazará.


2. Creación del primer plano: Modelo Spaceship

Vamos a crear un modelo que describa nuestra nave espacial.

Paso 1: Importamos BaseModel de Pydantic Pydantic ya está instalado junto con fastapi[all]. Solo necesitamos importar la clase base para nuestros modelos.

Añade esto a main.py en la parte superior, junto a otras importaciones:

# main.py
from fastapi import FastAPI
from pydantic import BaseModel

Paso 2: Describimos el modelo Spaceship Crea una clase que herede de BaseModel. Dentro de la clase, define los campos y sus tipos, usando las anotaciones de tipo estándar de Python.

Añade este código a main.py (puedes hacerlo después de las importaciones):

class Spaceship(BaseModel):
    """
    Pasaporte técnico (modelo) de una nave espacial.
    """
    name: str
    type: str
    launch_year: int
    status: str
¡Eso es todo! Acabas de crear un "plano". Pydantic ahora sabe que cualquier objeto de tipo Spaceship debe tener cuatro campos con los tipos especificados.


3. Aplicación del modelo: Mejorando nuestros endpoints

Ahora, usemos nuestro nuevo modelo para hacer que la API sea "más inteligente".

A. Modelo como respuesta (Response Model) Podemos indicar a FastAPI que nuestro endpoint debe devolver datos que se ajusten al modelo Spaceship. Esto garantiza que la respuesta siempre tendrá la estructura correcta.

Modifica el endpoint /spaceships/{ship_id} de la siguiente manera:

# main.py

# ... código con db_spaceships y el modelo Spaceship ...

# Usamos `response_model` para especificar el "plano" de la respuesta
@app.get("/spaceships/{ship_id}", response_model=Spaceship)
def get_spaceship(ship_id: int):
    """
    Devuelve datos sobre una nave, correspondientes al modelo Spaceship.
    """
    ship = db_spaceships.get(ship_id)
    return ship
- response_model=Spaceship: Le decimos a FastAPI: "La respuesta de esta función debe ajustarse a la estructura Spaceship. Filtra cualquier campo extra y asegúrate de que los tipos sean correctos".

¿Qué proporciona esto?

  • Filtrado de datos: Si en db_spaceships hubiera campos extra (por ejemplo, "secret_code"), no se incluirían en el JSON final.
  • Garantía de estructura: El cliente de la API puede estar seguro de que siempre recibirá la respuesta en el formato esperado.
  • Documentación: En /docs ahora se mostrará un ejemplo de respuesta preciso (Example Value).

B. Modelos para colecciones ¿Y qué pasa con el endpoint /spaceships, que devuelve una lista de naves? Para ello, necesitamos usar list del módulo typing.

Modifica las importaciones y el endpoint /spaceships:

# main.py en la parte superior
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List  # <-- Importamos List

# ... código ...

# Indicamos que la respuesta es una lista (List) de objetos de tipo Spaceship
@app.get("/spaceships", response_model=List[Spaceship])
def get_spaceships():
    """
    Devuelve una lista de naves. Cada elemento de la lista se valida
    según el modelo Spaceship.
    """
    # Pydantic no puede trabajar con un diccionario cuyas claves son IDs.
    # Convertimos nuestro diccionario en una lista simple.
    return list(db_spaceships.values())

  • response_model=List[Spaceship]: Indicamos que la respuesta será una lista, donde cada elemento es un objeto que corresponde al modelo Spaceship.
  • return list(db_spaceships.values()): ¡Cambio importante! Pydantic espera un objeto iterable (una lista), no un diccionario donde las claves son IDs. Convertimos los valores de nuestro "simulador de BD" en una lista.

4. Verificación de la API mejorada

Asegúrate de que el servidor uvicorn esté ejecutándose con --reload.

  1. Verifica http://127.0.0.1:8000/spaceships: Ahora la respuesta es un array JSON, no un objeto. Esta es una estructura más correcta y estándar para colecciones.
    [
      { "name": "Voyager-1", "type": "Зонд", ... },
      { "name": "Hubble Space Telescope", ... }
    ]
    
  2. Verifica http://127.0.0.1:8000/spaceships/1: La respuesta no ha cambiado, pero ahora está garantizado que cumple con el modelo.
  3. Echa un vistazo a /docs: En la sección "Schemas" al final de la página, aparecerá tu modelo Spaceship. Y en los ejemplos de respuesta para los endpoints, ahora se mostrará un esquema de datos hermoso y estructurado.

5. Validación avanzada: El "ordenador de a bordo" en acción

Pydantic puede hacer mucho más que simplemente verificar tipos.

Añadamos validación a nuestro modelo Spaceship:

from pydantic import BaseModel, Field

class Spaceship(BaseModel):
    name: str = Field(..., min_length=3, max_length=50, description="Nombre de la nave")
    type: str
    launch_year: int = Field(..., gt=1950, description="El año de lanzamiento debe ser posterior a 1950")
    status: str

  • Field(...): Se utiliza para añadir reglas de validación adicionales.
  • ... (Ellipsis): Significa que el campo es obligatorio.
  • min_length, max_length: Restricciones para cadenas de texto.
  • gt: "Greater Than" (mayor que).

Aunque aún no estamos creando nuevas naves (esto será en el próximo capítulo), estas reglas ya se reflejarán en la documentación y se activarán cuando implementemos las peticiones POST.


Cuestionario de refuerzo

1. Pydantic en FastAPI se utiliza para...

2. Para crear un modelo de datos, es necesario heredar la clase de...

3. El parámetro `response_model` en el decorador `@app.get` es necesario para...

4. ¿Cómo indicar que un endpoint devuelve una *lista* de objetos de tipo `Item`?

5. `Field(..., gt=0)` en un modelo Pydantic significa que el campo...


🚀 Resumen del capítulo:

Has diseñado los "planos digitales" para los datos de tu API. Ahora no solo funciona, sino que funciona de manera predecible y fiable.

  • 📝 Modelo Spaceship creado usando Pydantic.
  • 🛡️ La API ahora valida y filtra los datos salientes usando response_model.
  • 📊 La documentación es mucho más informativa, mostrando esquemas de datos precisos.

¡Los planos están listos y aprobados! En el siguiente capítulo pasaremos de leer datos a crearlos — implementaremos operaciones CRUD completas para nuestra flota.

📌 Verificación:

  • Asegúrate de que el esquema del modelo Spaceship haya aparecido en /docs.
  • Verifica que el endpoint /spaceships ahora devuelva un array JSON ([...]) y no un objeto ({...}).
  • Asegúrate de que no haya errores de sintaxis en el código después de añadir los modelos.

⚠️ Si hay errores:

  • NameError: name 'BaseModel' is not defined: Asegúrate de haber importado BaseModel de pydantic.
  • NameError: name 'List' is not defined: Asegúrate de haber importado List de typing.
  • La respuesta a /spaceships está vacía ([]): Asegúrate de haber cambiado return db_spaceships a return list(db_spaceships.values()).