Aprende a Crear APIs REST con FastAPI y Pydantic: Guía Completa para Desarrolladores Python
Este tutorial te guiará a través del proceso de construcción de APIs RESTful utilizando FastAPI, un framework web moderno, y Pydantic para la validación de datos. Aprenderás desde la configuración inicial hasta la implementación de operaciones CRUD y la documentación automática. Ideal para desarrolladores Python que buscan crear servicios web de alto rendimiento y fácil mantenimiento.
✨ Introducción a FastAPI y Pydantic: El Dúo Dinámico para tus APIs
En el mundo del desarrollo web, la eficiencia y la velocidad son cruciales. Python, con su vasta comunidad y ecosistema, ofrece herramientas poderosas para la creación de APIs. Entre ellas, FastAPI ha emergido como un cambio de juego, destacándose por su increíble rendimiento, facilidad de uso y características de desarrollo modernas.
FastAPI es un framework web de alto rendimiento para construir APIs con Python 3.7+ basado en type hints estándar de Python. Su principal ventaja es que te permite construir APIs robustas y listas para producción en muy poco tiempo, ¡y con menos código!
Pero, ¿qué hace a FastAPI tan especial? Su magia reside en su integración nativa con Pydantic. Pydantic es una librería de validación y configuración de datos que utiliza los type hints de Python para validar los datos que entran y salen de tu API. Esto no solo garantiza la integridad de tus datos, sino que también facilita la generación automática de documentación interactiva (Swagger UI y ReDoc).
En este tutorial exhaustivo, vamos a sumergirnos en el fascinante mundo de FastAPI y Pydantic. Desde la configuración inicial hasta la construcción de una API RESTful completa con operaciones CRUD (Crear, Leer, Actualizar, Borrar), aprenderás todo lo necesario para empezar a crear tus propias APIs potentes y escalables.
🛠️ Configuración del Entorno de Desarrollo
Antes de empezar a escribir código, necesitamos preparar nuestro entorno. Una buena práctica es utilizar entornos virtuales para gestionar las dependencias de nuestros proyectos de Python.
1. Crear un Entorno Virtual
Abre tu terminal o línea de comandos y navega hasta el directorio donde quieres crear tu proyecto. Luego, ejecuta los siguientes comandos:
python3 -m venv venv
source venv/bin/activate # En Linux/macOS
# venv\Scripts\activate # En Windows
Verás (venv) en el prompt de tu terminal, lo que indica que el entorno virtual está activo.
2. Instalar FastAPI y Uvicorn
FastAPI es el framework, pero necesitamos un servidor ASGI (Asynchronous Server Gateway Interface) para ejecutarlo. Uvicorn es un servidor ASGI rápido, de código abierto, para Python, que es altamente recomendado para FastAPI.
Instala ambos con pip:
pip install fastapi "uvicorn[standard]"
3. Verificar la Instalación
Para asegurarte de que todo se instaló correctamente, puedes verificar las versiones:
pip show fastapi
pip show uvicorn
pip show pydantic
🚀 Tu Primera API con FastAPI: ¡Hola Mundo!
Vamos a crear el clásico programa "Hola Mundo" para familiarizarnos con la estructura básica de una aplicación FastAPI.
1. Crear el Archivo main.py
Crea un archivo llamado main.py en la raíz de tu proyecto con el siguiente contenido:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hola Mundo desde FastAPI!"}
2. Explicación del Código
from fastapi import FastAPI: Importamos la claseFastAPI.app = FastAPI(): Creamos una instancia de la aplicación FastAPI. Esta es la puerta de entrada a todas las funcionalidades del framework.@app.get("/"): Esto se llama un decorador. Le dice a FastAPI que la funciónread_rootdebe manejar las solicitudes HTTP GET a la ruta raíz (/).async def read_root(): Definimos una función asíncrona. FastAPI está diseñado para aprovechar al máximo las características asíncronas de Python, lo que permite manejar múltiples solicitudes concurrentemente de manera eficiente.return {"message": "Hola Mundo desde FastAPI!"}: La función devuelve un diccionario Python. FastAPI lo convierte automáticamente en una respuesta JSON para el cliente.
3. Ejecutar la Aplicación
Guarda el archivo y ejecuta tu API usando Uvicorn desde la terminal:
uvicorn main:app --reload
main: Se refiere al archivomain.py.app: Se refiere a la instancia deFastAPIque creamos dentro demain.py.--reload: Esta bandera es muy útil durante el desarrollo. Hace que Uvicorn recargue automáticamente el servidor cada vez que detecta un cambio en tus archivos de código.
Verás una salida similar a esta:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [xxxxx] using WatchFiles
INFO: Started server process [xxxxx]
INFO: Waiting for application startup.
INFO: Application startup complete.
Ahora, abre tu navegador web y visita http://127.0.0.1:8000. Deberías ver la respuesta JSON: {"message": "Hola Mundo desde FastAPI!"}.
4. La Documentación Interactiva (¡La Magia de FastAPI!)
¡Aquí viene una de las características más asombrosas de FastAPI! Sin que hayas escrito ni una sola línea de documentación, tu API ya tiene documentación interactiva. Abre tu navegador y visita:
- Swagger UI:
http://127.0.0.1:8000/docs - ReDoc:
http://127.0.0.1:8000/redoc
En http://127.0.0.1:8000/docs, verás una interfaz interactiva donde puedes probar tus endpoints directamente desde el navegador. Esta documentación se genera automáticamente a partir de tus type hints de Python y los modelos Pydantic.
📝 Validando Datos con Pydantic: Modelos y Type Hints
Pydantic es el corazón de la validación de datos en FastAPI. Nos permite definir la estructura y el tipo de los datos que esperamos recibir en nuestras solicitudes (request bodies) y los que enviaremos en nuestras respuestas (response bodies).
1. Creando un Modelo Pydantic
Imaginemos que queremos crear una API para gestionar ítems. Cada ítem tendrá un name, una description opcional, un price y un is_offer opcional.
Modifica tu archivo main.py:
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
is_offer: Optional[bool] = None
@app.get("/")
async def read_root():
return {"message": "Hola Mundo desde FastAPI!"}
@app.post("/items/")
async def create_item(item: Item):
return item
2. Explicación del Modelo Item
from pydantic import BaseModel: ImportamosBaseModelde Pydantic. Todas nuestras clases de modelos de datos heredarán de ella.class Item(BaseModel):: Definimos la claseItem.name: str: Declaramos quenamees de tipostr(cadena de texto). Es un campo obligatorio.description: Optional[str] = None: Declaramos quedescriptiones de tipostryOptional.Nonecomo valor por defecto lo convierte en un campo opcional.price: float:pricees de tipofloat(número decimal). Es obligatorio.is_offer: Optional[bool] = None:is_offeres de tipobooly opcional.
Cuando FastAPI recibe una solicitud POST a /items/ con un cuerpo JSON, automáticamente intentará validar ese JSON contra el modelo Item. Si los datos no coinciden con la estructura o los tipos definidos, FastAPI devolverá un error 422 (Unprocessable Entity) con detalles sobre la validación.
3. Probando la Creación de Ítems
Con el servidor Uvicorn ejecutándose (uvicorn main:app --reload), ve a http://127.0.0.1:8000/docs.
Ahora verás un nuevo endpoint /items/ con el método POST. Expándelo, haz clic en "Try it out" y copia el siguiente JSON en el campo "Request body":
{
"name": "Manzana",
"description": "Una deliciosa manzana roja",
"price": 1.20,
"is_offer": true
}
Haz clic en "Execute". Deberías obtener una respuesta con el código de estado 200 OK y el mismo JSON que enviaste. Ahora, prueba enviar un JSON inválido (por ejemplo, "price": "dos euros") y verás el error de validación 422 Unprocessable Entity.
🔄 Operaciones CRUD: Construyendo una API Completa
Ahora vamos a extender nuestra API para implementar las operaciones CRUD completas sobre nuestros ítems.
Necesitaremos un lugar para almacenar nuestros ítems. Para este tutorial, usaremos una simple lista en memoria. En un proyecto real, esto sería una base de datos (SQL, NoSQL, etc.).
1. Almacenamiento en Memoria y Generación de IDs
Modifica main.py. Agregaremos un diccionario para simular una base de datos y un contador para los IDs.
from typing import Optional, List, Dict
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
is_offer: Optional[bool] = None
class ItemInDB(Item):
id: int
# Simulación de una base de datos en memoria
items_db: Dict[int, Item] = {}
next_item_id = 0
@app.get("/")
async def read_root():
return {"message": "Bienvenido a la API de Ítems!"}
Hemos añadido ItemInDB para representar el ítem con un id cuando se almacena. Esto es útil para separar el modelo de lo que se recibe del cliente de lo que se guarda o se devuelve.
2. Crear un Ítem (POST)
Ya tenemos el esqueleto. Ahora, completemos la lógica para crear un nuevo ítem:
# ... (código anterior)
@app.post("/items/", response_model=ItemInDB, status_code=201)
async def create_item(item: Item):
global next_item_id
new_item_id = next_item_id
next_item_id += 1
item_in_db = ItemInDB(id=new_item_id, **item.dict())
items_db[new_item_id] = item_in_db
return item_in_db
response_model=ItemInDB: Le dice a FastAPI que la respuesta de este endpoint se ajustará al modeloItemInDB. Esto es crucial para la documentación automática y la serialización de la respuesta.status_code=201: Establece el código de estado HTTP de éxito a 201 (Created).item_in_db = ItemInDB(id=new_item_id, **item.dict()): Creamos una instancia deItemInDBusando elidgenerado y desempacando los atributos delitemrecibido.
3. Leer Todos los Ítems (GET)
# ... (código anterior)
@app.get("/items/", response_model=List[ItemInDB])
async def read_items():
return list(items_db.values())
response_model=List[ItemInDB]: Especificamos que la respuesta será una lista de objetosItemInDB.
4. Leer un Ítem por ID (GET)
# ... (código anterior)
@app.get("/items/{item_id}", response_model=ItemInDB)
async def read_item(item_id: int):
if item_id not in items_db:
raise HTTPException(status_code=404, detail="Item no encontrado")
return items_db[item_id]
{item_id}: Define un parámetro de ruta. FastAPI automáticamente validará queitem_idsea un entero.HTTPException: Si el ítem no existe, levantamos unaHTTPException, lo que resulta en una respuesta HTTP 404 (Not Found).
5. Actualizar un Ítem (PUT)
# ... (código anterior)
@app.put("/items/{item_id}", response_model=ItemInDB)
async def update_item(item_id: int, item: Item):
if item_id not in items_db:
raise HTTPException(status_code=404, detail="Item no encontrado")
# Actualizar solo los campos proporcionados, o todos si el modelo Item los incluye
stored_item_data = items_db[item_id].dict()
update_data = item.dict(exclude_unset=True) # Excluye campos no seteados en la petición
for key, value in update_data.items():
stored_item_data[key] = value
updated_item = ItemInDB(id=item_id, **stored_item_data)
items_db[item_id] = updated_item
return updated_item
item: Item: Recibimos el cuerpo de la solicitud como un objetoItemde Pydantic para su validación.item.dict(exclude_unset=True): Esto es muy importante para operaciones PUT/PATCH.exclude_unset=Trueasegura que solo los campos que fueron explícitamente enviados en la solicitud JSON se incluyan enupdate_data. Esto previene que los campos no proporcionados se sobrescriban con sus valores por defecto si no se especifican.
6. Eliminar un Ítem (DELETE)
# ... (código anterior)
@app.delete("/items/{item_id}", status_code=204)
async def delete_item(item_id: int):
if item_id not in items_db:
raise HTTPException(status_code=404, detail="Item no encontrado")
del items_db[item_id]
return # No content, hence 204 status code
status_code=204: Un código de estado 204 (No Content) es apropiado para operaciones DELETE exitosas que no devuelven cuerpo de respuesta.
🔑 Parámetros de Consulta y Rutas
FastAPI hace que trabajar con parámetros de consulta (query parameters) y parámetros de ruta (path parameters) sea increíblemente fácil, aprovechando de nuevo los type hints.
1. Parámetros de Ruta (path parameters)
Ya los usamos en /items/{item_id}. El tipo que le das en la firma de la función (ej. item_id: int) es automáticamente validado.
# ... (código anterior)
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: str):
return {"user_id": user_id, "item_id": item_id}
Aquí, user_id debe ser un entero y item_id una cadena.
2. Parámetros de Consulta (query parameters)
Son los parámetros que van después del signo de interrogación en la URL (ej. /items/?skip=0&limit=10).
# ... (código anterior)
@app.get("/items_filtered/")
async def read_items_filtered(
skip: int = 0,
limit: int = 10,
description_contains: Optional[str] = None
):
filtered_items = list(items_db.values())
if description_contains:
filtered_items = [item for item in filtered_items if item.description and description_contains.lower() in item.description.lower()]
return filtered_items[skip : skip + limit]
skip: int = 0: Define un parámetro de consultaskipde tipo entero con un valor por defecto de 0. Si el cliente no lo envía, se usará 0.limit: int = 10: Definelimitcon un valor por defecto de 10.description_contains: Optional[str] = None: Este es un parámetro de consulta opcional. Si se envía, filtra los ítems por descripción.
Todos estos parámetros aparecerán en la documentación interactiva (/docs) y podrás probarlos allí.
🔒 Seguridad (Primeros Pasos con OAuth2)
FastAPI tiene soporte incorporado para múltiples esquemas de seguridad, incluyendo OAuth2 (con JWT), HTTP Basic, y API Keys. Aquí veremos un ejemplo básico de cómo añadir seguridad con OAuth2 usando un esquema de Password Bearer.
1. Importar OAuth2PasswordBearer
# ... (código anterior)
from fastapi.security import OAuth2PasswordBearer
# ... (código anterior)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # El endpoint donde se obtiene el token
2. Crear un Endpoint de Autenticación (/token)
Necesitamos un endpoint para que los clientes puedan obtener un token.
# ... (código anterior)
from fastapi import Depends, status
from fastapi.security import OAuth2PasswordRequestForm
# ... (código anterior)
# Función simulada para verificar credenciales de usuario
def fake_authenticate_user(username: str, password: str):
if username == "admin" and password == "secret": # ¡Nunca hagas esto en prod!
return {"username": username, "email": "admin@example.com"}
return None
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Credenciales inválidas",
headers={"WWW-Authenticate": "Bearer"},
)
# Aquí deberías generar un JWT real
access_token = f"fake_token_for_{user['username']}"
return {"access_token": access_token, "token_type": "bearer"}
OAuth2PasswordRequestForm = Depends(): FastAPI se encarga de parsear los datos del formulario (username y password) del cuerpo de la solicitud.
3. Proteger un Endpoint
Ahora, podemos proteger cualquier endpoint requiriendo un token.
# ... (código anterior)
@app.get("/secure_item/", response_model=ItemInDB)
async def get_secure_item(token: str = Depends(oauth2_scheme)):
# En un escenario real, aquí validarías el token para obtener el usuario actual
if token != "fake_token_for_admin":
raise HTTPException(status_code=403, detail="Token inválido o expirado")
# Suponiendo que el token es válido, devolvemos un ítem ficticio
return ItemInDB(id=999, name="Item Secreto", description="Solo para usuarios autenticados", price=99.99)
token: str = Depends(oauth2_scheme): Esto le dice a FastAPI que este endpoint depende de un token OAuth2. FastAPI buscará el token en el encabezadoAuthorization: Bearer <token>y lo pasará a la función. Si no lo encuentra o no es válido según el esquema, devolverá un error 401.
En la documentación (/docs), verás un botón "Authorize" donde puedes introducir el token "fake_token_for_admin" (obtenido primero del endpoint /token con user admin y pass secret) y luego probar el endpoint /secure_item/.
📊 Testing de tu API con pytest
Es fundamental probar tu API para asegurar que funciona como se espera. FastAPI facilita mucho el testing con pytest y la librería httpx.
1. Instalar pytest y httpx
pip install pytest httpx
2. Crear un Archivo de Tests
Crea un archivo llamado test_main.py en la raíz de tu proyecto:
from fastapi.testclient import TestClient
from main import app, items_db, next_item_id # Importa tu aplicación y datos si es necesario
import pytest
# Crea un cliente de prueba para tu aplicación FastAPI
client = TestClient(app)
# Hook para resetear la base de datos simulada antes de cada test
@pytest.fixture(autouse=True)
def run_before_tests():
global next_item_id
items_db.clear()
next_item_id = 0
yield
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Bienvenido a la API de Ítems!"}
def test_create_item():
response = client.post(
"/items/",
json={
"name": "Test Item",
"description": "This is a test item",
"price": 10.50
}
)
assert response.status_code == 201
assert response.json() == {
"id": 0, # El primer ítem tendrá ID 0
"name": "Test Item",
"description": "This is a test item",
"price": 10.50,
"is_offer": None # Se devuelve None si no se especifica
}
assert 0 in items_db
assert items_db[0].name == "Test Item"
def test_read_single_item():
# Primero crea un ítem para poder leerlo
client.post("/items/", json={"name": "Item1", "price": 10.0})
response = client.get("/items/0")
assert response.status_code == 200
assert response.json()["name"] == "Item1"
def test_read_non_existent_item():
response = client.get("/items/999")
assert response.status_code == 404
assert response.json() == {"detail": "Item no encontrado"}
def test_update_item():
client.post("/items/", json={"name": "Old Name", "price": 5.0})
response = client.put(
"/items/0",
json={"name": "New Name", "price": 15.0, "is_offer": True}
)
assert response.status_code == 200
assert response.json()["name"] == "New Name"
assert response.json()["price"] == 15.0
assert response.json()["is_offer"] == True
def test_delete_item():
client.post("/items/", json={"name": "Item to delete", "price": 1.0})
response = client.delete("/items/0")
assert response.status_code == 204
assert 0 not in items_db
3. Ejecutar los Tests
Desde tu terminal, en el directorio del proyecto, ejecuta:
pytest
Verás una salida indicando que todos tus tests han pasado. Los tests son esenciales para garantizar la calidad y el comportamiento esperado de tu API a medida que evoluciona.
📈 Despliegue de tu API FastAPI
Una vez que tu API esté lista, querrás desplegarla para que sea accesible. FastAPI es muy flexible y puede desplegarse en casi cualquier lugar donde puedas ejecutar Python.
1. Consideraciones de Producción
- Servidor ASGI: En producción, no uses
uvicorn --reload. Deberías usar Uvicorn directamente o con un manejador de procesos como Gunicorn. Uvicorn está diseñado para ser muy rápido, pero Gunicorn puede gestionar procesos de Uvicorn, balanceo de carga, etc. - HTTPS: Siempre usa HTTPS para proteger tus comunicaciones.
- Base de Datos: Sustituye la base de datos en memoria por una real (PostgreSQL, MySQL, MongoDB, etc.) utilizando ORMs como SQLAlchemy con
SQLModel(una extensión de Pydantic para bases de datos) oPeeWee, o clientes directamente. - Manejo de Errores: Implementa un manejo de errores más robusto.
- Variables de Entorno: No codifiques credenciales ni configuraciones sensibles en tu código. Usa variables de entorno (por ejemplo, con
python-dotenvo configuraciones de Pydantic).
2. Ejemplo de Despliegue con Gunicorn y Uvicorn Workers
Para un despliegue sencillo en un servidor Linux, puedes usar Gunicorn para gestionar los procesos de Uvicorn:
-
Instalar Gunicorn:
pip install gunicorn -
Ejecutar:
gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000main:app: Indica a Gunicorn dónde encontrar tu aplicación FastAPI.--workers 4: Ejecuta 4 procesos de trabajador. Ajusta esto según el número de núcleos de CPU de tu servidor.--worker-class uvicorn.workers.UvicornWorker: Le dice a Gunicorn que use los trabajadores de Uvicorn, que son compatibles con ASGI.--bind 0.0.0.0:8000: Escucha en todas las interfaces de red en el puerto 8000.
3. Otros Métodos de Despliegue
- Docker: Es una opción muy popular para encapsular tu aplicación y sus dependencias. Puedes crear un
Dockerfiley desplegarlo en cualquier entorno que soporte contenedores. - Cloud Providers: Plataformas como Heroku, AWS Elastic Beanstalk, Google Cloud Run, Azure App Service, o DigitalOcean App Platform tienen excelente soporte para aplicaciones Python y Docker.
Ejemplo de Dockerfile básico para FastAPI
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
🎯 Conclusión y Próximos Pasos
¡Felicidades! Has completado una inmersión profunda en FastAPI y Pydantic. Ahora tienes las herramientas y el conocimiento para empezar a construir tus propias APIs RESTful de alto rendimiento con Python.
Hemos cubierto:
- La configuración del entorno.
- Tu primera API "Hola Mundo".
- La validación de datos con modelos Pydantic.
- La implementación de operaciones CRUD completas.
- El manejo de parámetros de ruta y consulta.
- Una introducción a la seguridad con OAuth2.
- Conceptos básicos de testing y despliegue.
FastAPI es una tecnología emocionante que combina la productividad de Python con el rendimiento que exigen las aplicaciones modernas. Su integración nativa con Pydantic y la generación automática de documentación son características que te ahorrarán incontables horas de desarrollo y mantenimiento.
✨ Próximos Pasos para Seguir Aprendiendo
Para llevar tus habilidades al siguiente nivel, te recomiendo explorar los siguientes temas:
- Bases de Datos Reales: Integra tu API con PostgreSQL o MongoDB utilizando librerías como
SQLModel(para SQL) oMotor(para MongoDB conasyncio). - Autenticación y Autorización Avanzadas: Implementa JWT (JSON Web Tokens) para una seguridad más robusta.
- Dependencias e Inyección de Dependencias: Comprende cómo FastAPI maneja las dependencias para estructurar mejor tu código.
- Middleware: Aprende a añadir lógica que se ejecuta antes o después de que tu API procese una solicitud (logging, CORS, etc.).
- Tareas en Segundo Plano (Background Tasks): Para operaciones que no necesitan una respuesta inmediata al cliente.
- WebSockets: Si tu aplicación requiere comunicación en tiempo real.
- Despliegue Avanzado: Explora opciones de despliegue en la nube con Kubernetes o Serverless.
La comunidad de FastAPI es muy activa y la documentación oficial es excelente. ¡No dudes en consultarla a medida que avanzas en tus proyectos!
Tutoriales relacionados
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!