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:
- Declaración de estructura: Describe claramente de qué campos se componen tus datos.
- Validación de datos: Verifica automáticamente si los datos entrantes cumplen con el plano.
- 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:
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
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 modeloSpaceship
.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
.
- 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. - Verifica
http://127.0.0.1:8000/spaceships/1
: La respuesta no ha cambiado, pero ahora está garantizado que cumple con el modelo. - Echa un vistazo a
/docs
: En la sección "Schemas" al final de la página, aparecerá tu modeloSpaceship
. 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
🚀 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 importadoBaseModel
depydantic
.NameError: name 'List' is not defined
: Asegúrate de haber importadoList
detyping
.- La respuesta a
/spaceships
está vacía ([]
): Asegúrate de haber cambiadoreturn db_spaceships
areturn list(db_spaceships.values())
.