Async, docs y testing
¿Qué es?
Este módulo reúne tres habilidades finales. Async (asíncrono) es una forma de programar para atender muchas peticiones a la vez mientras unas esperan respuestas externas. Docs es la documentación de tu API: la página interactiva que FastAPI genera sola para que otros entiendan y prueben tus endpoints. Testing es escribir pruebas automáticas que verifican que tu API se comporta como esperas, sin que tengas que revisarlo todo a mano.
¿Cómo funciona?
Con async def y await, un endpoint puede ceder el control mientras espera una operación de entrada/salida, y el servidor aprovecha para atender otras peticiones; para trabajo posterior a la respuesta existen las BackgroundTasks. La documentación se enriquece con metadatos como title, summary y tags, y se alimenta sola de tus tipos y modelos. Las pruebas usan TestClient, que llama a tu app sin levantar un servidor real, y comprueban con assert que cada respuesta tenga el estado y el contenido correctos.
¿Para qué sirve?
Estas tres piezas separan una API de juguete de una lista para usarse: la asincronía la hace rendir bajo carga, la documentación la vuelve usable por otros, y las pruebas avisan cuando algo se rompe. Juntas te dan confianza de que el sistema funciona y de que seguirá funcionando al cambiarlo. Con ellas entregarás tu proyecto final, «API para tus herramientas», sabiendo que corre de forma concurrente, se documenta sola y se prueba automáticamente.
Cierras el curso con tres habilidades que separan una API de juguete de una lista para usarse: ejecutar trabajo de forma asíncrona, documentar profesionalmente y probar automáticamente. Con esto entregarás tu «API para tus herramientas» con confianza de que funciona y de que otros sabrán usarla.
async/await: cuándo y por qué
FastAPI es asíncrono de raíz. Un endpoint puede ser normal (def) o asíncrono (async def). La diferencia importa cuando esperas operaciones de entrada/salida: una llamada HTTP a otro servicio, una consulta a base de datos asíncrona, leer un archivo grande.
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/clima/{ciudad}")
async def clima(ciudad: str):
async with httpx.AsyncClient() as client:
resp = await client.get(f"https://api.ejemplo.com/clima/{ciudad}")
return resp.json()
Mientras una petición espera la respuesta externa (await), el servidor atiende otras. Eso es concurrencia: más peticiones simultáneas con los mismos recursos. La regla práctica: usa async def cuando dentro hagas await; usa def normal para código que no espera nada o que usa librerías síncronas.
Nunca pongas trabajo bloqueante dentro de un async def. Un time.sleep(5) o un cálculo pesado dentro de una corrutina congela todo el servidor para todos. Si tu función no usa await, decláralala con def normal: FastAPI la corre en un hilo aparte y no bloquea el bucle de eventos.
Para tareas que deben ocurrir después de responder (enviar un correo, escribir un log), usa BackgroundTasks:
from fastapi import BackgroundTasks
def registrar(mensaje: str):
with open("log.txt", "a") as f:
f.write(mensaje + "\n")
@app.post("/notas")
async def crear(valor: float, tareas: BackgroundTasks):
tareas.add_task(registrar, f"Nota creada: {valor}")
return {"valor": valor}
El cliente recibe la respuesta de inmediato y el log se escribe en segundo plano.
Documentación profesional
FastAPI genera /docs (Swagger UI) y /redoc solo, a partir de tus tipos. Tú la enriqueces con metadatos:
app = FastAPI(
title="API para tus herramientas",
description="Expone un calificador OMR y un sistema de notas.",
version="1.0.0",
)
@app.post("/notas", summary="Registrar una nota", tags=["notas"])
def crear_nota(valor: float):
"""
Crea una nota nueva.
- **valor**: número de 0.0 a 5.0
"""
return {"valor": valor}
El summary y el docstring aparecen en /docs; los tags agrupan endpoints por sección. Documentar respuestas de error también ayuda:
@app.get(
"/notas/{nota_id}",
responses={404: {"description": "Nota no encontrada"}},
)
def obtener(nota_id: int):
...
La mejor documentación es la que se escribe sola. Cada modelo Pydantic, cada response_model y cada tipo que declaras ya alimenta /docs. Invertir en buenos modelos te ahorra mantener documentación aparte que se desactualiza.
Testing con TestClient
FastAPI incluye un cliente de pruebas que llama a tu app sin levantar un servidor real. Se apoya en pytest:
pip install pytest httpx
Supongamos main.py con la app. Creas test_main.py:
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_crear_nota():
resp = client.post("/notas", json={"estudiante": "Ana", "valor": 4.5})
assert resp.status_code == 201
assert resp.json()["aprobada"] is True
def test_valor_invalido():
resp = client.post("/notas", json={"estudiante": "Ana", "valor": 9})
assert resp.status_code == 422
def test_nota_inexistente():
resp = client.get("/notas/9999")
assert resp.status_code == 404
Cada prueba envía una petición y verifica el resultado con assert. Corres todo con pytest. Fíjate en qué probamos: el camino feliz (201), la validación (422) y los errores (404). Una buena API se prueba por su comportamiento visible, no por su código interno.
Ejemplo guiado: del endpoint a la prueba
- Toma el
POST /notasconresponse_modeldel módulo de Pydantic enmain.py. - Crea
test_main.pycon las tres pruebas de arriba. - Corre
pytest -v. Verás tres líneas en verde. - Rompe a propósito el rango del modelo (pon
le=10) y vuelve a correr:test_valor_invalidofalla, porque ahora9se acepta. Eso es una prueba haciendo su trabajo: avisar cuando el comportamiento cambia. - Repara el modelo y confirma que todo vuelve a verde.
Para probar endpoints protegidos, primero pides un token y lo mandas en la cabecera:
def test_crear_requiere_auth():
assert client.post("/notas", json={"valor": 4.0}).status_code == 401
login = client.post("/login", data={"username": "profe", "password": "clave123"})
token = login.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
assert client.post("/notas", json={"valor": 4.0}, headers=headers).status_code == 201
Tech English: concurrency = concurrencia (atender varias cosas a la vez). blocking = bloqueante. fixture = preparación reutilizable de una prueba en pytest. assertion = afirmación que la prueba verifica. coverage = cobertura del código probado.
Ejercicios
-
Suite de pruebas del CRUD. Escribe pruebas con
TestClientpara tu API de notas que cubran: crear (201), leer existente (200), leer inexistente (404), validación fallida (422) y borrar (204). Evalúa: ¿cada prueba comprueba código de estado y contenido relevante? ¿se cubren los caminos de error, no solo el feliz? -
Calificador OMR como API documentada y probada. Diseña
POST /calificarque reciba una hoja OMR y la clave de respuestas, y devuelva el puntaje. Documenta el endpoint consummary,tagsy descripción de la respuesta 422; añade pruebas para una hoja perfecta, una a medias y una con una letra inválida. Evalúa el diseño completo: recursos claros, validación en el modelo, errores útiles y pruebas que defienden el comportamiento. Este es el corazón del proyecto final, «API para tus herramientas».