Juan Diego Andrés PRADA··RAMÍREZ Entrar
Lección 6 de 7
Contenido de la lección 9 tramos

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

  1. Toma el POST /notas con response_model del módulo de Pydantic en main.py.
  2. Crea test_main.py con las tres pruebas de arriba.
  3. Corre pytest -v. Verás tres líneas en verde.
  4. Rompe a propósito el rango del modelo (pon le=10) y vuelve a correr: test_valor_invalido falla, porque ahora 9 se acepta. Eso es una prueba haciendo su trabajo: avisar cuando el comportamiento cambia.
  5. 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

  1. Suite de pruebas del CRUD. Escribe pruebas con TestClient para 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?

  2. Calificador OMR como API documentada y probada. Diseña POST /calificar que reciba una hoja OMR y la clave de respuestas, y devuelva el puntaje. Documenta el endpoint con summary, tags y 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».

Tu progreso se guarda en este navegador. Inicia sesión para guardarlo en tu cuenta y verlo desde cualquier dispositivo.

¿Le sirvió esta lección?

Sirve para saber qué reescribir. Se puede cambiar cuando quiera.

0 votos
Sin nombre todavía

Su avance y su nombre se guardan en este navegador. No hace falta registrarse.

¿Va por el certificado?

Practicar y comentar no exige nada. Para el certificado sí hace falta verificar el correo, el nombre completo, la institución, el grado, porque el certificado dice quién es usted.

Comentarios (0)

Pregunte lo que no entendió. Alguien más tiene la misma duda y no se atreve.

  • Todavía no hay comentarios. Estrene usted.