Clase 07 · 24 minGratis

Tu API con FastAPI y sus pruebas

Clase 7 del curso gratis de buenas prácticas en Python: publicamos el cifrado César como una API con FastAPI, la probamos con TestClient y vemos cómo GitHub Actions detecta una dependencia que faltaba.

Toca cualquier marca ▸ 0:00 de los apuntes y el video salta a ese momento. Abrir en YouTube

Por qué FastAPI

Hasta aquí las funciones solo se usan desde Python. Una API las pone a disposición de cualquiera que pueda hacer una petición HTTP. Hay otras opciones (Django REST, Flask); FastAPI es fácil de usar, rápida y trae documentación automática, que veremos en la clase 8.

bash
pip install fastapi
pip install "uvicorn[standard]"

GET, POST y compañía

Cuando visitas una página haces una petición GET; cuando envías un formulario, casi siempre un POST; para borrar, DELETE; para actualizar, PUT. En FastAPI, cada ruta es una función con un decorador que dice el método y la URL:

python
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def home():
    """
    Funcion principal de API
    """
    return {"Hola": "Mundo123"}

Lo que devuelves es un diccionario, y FastAPI lo convierte en JSON, el idioma común de las API.

Levantar el servidor

bash
cd app
uvicorn main:app --reload

main:app significa «el objeto app del archivo main.py»; --reload reinicia el servidor cada vez que guardas. Abre http://localhost:8000 y verás la respuesta.

El cifrado César como ruta

python
"""Este es el modulo principal donde habrá la creacion y disposición de una API"""
from fastapi import FastAPI
from funciones.cifrado_cesar import cifrado_cesar

app = FastAPI()


@app.get("/")
def home():
    """
    Funcion principal de API
    """
    return {"Hola": "Mundo123"}


@app.get("/cifrado-cesar")
def url_cifrado_cesar(texto, clave):
    """
    URL para hacer encriptado CESAR
    """
    salida = cifrado_cesar(texto, int(clave))
    return {"mensaje_cifrado": salida}

Con GET, los datos viajan en la URL después de un ?, separados por &:

text
http://localhost:8000/cifrado-cesar?texto=hola&clave=1
→ {"mensaje_cifrado": "IPMB"}

El primer intento da error : todo lo que llega por la URL es texto, así que clave hay que convertirla con int(clave) antes de sumarla.

Probar una API sin abrir el navegador

FastAPI trae un TestClient que simula las peticiones HTTP. Se comprueban dos cosas: el código de estado (200 es «todo bien»; 404, «no existe»; 403, «sin permiso») y el JSON de la respuesta.

python
"""Aqui se colocan los test para el archivo main.py"""
import sys
import os
from fastapi.testclient import TestClient

# pylint: disable=C0413
sys.path.append(os.path.dirname(os.path.dirname("app/funciones/")))
from app.main import app

client = TestClient(app)


def test_url_cifrado_cesar():
    """
    Prueba unitaria para verificar api con cifrado cesar
    """
    respuesta = client.get("/cifrado-cesar?texto=hello&clave=3")
    assert respuesta.status_code == 200
    assert respuesta.json() == {"mensaje_cifrado": "KHÑÑR"}

Dos tropiezos que vale la pena ver en el video:

  • No module named 'httpx' — TestClient lo necesita: pip install httpx.
  • No module named 'funciones' — main.py importa funciones como si estuviera al lado, pero la prueba corre desde la raíz del proyecto. El sys.path.append le da a Python un atajo hacia esa carpeta.

Cobertura y la excepción a Pylint

Con la prueba nueva, pytest --cov=app tests/ sube la cobertura de app al 97 %. Pylint, en cambio, se queja (C0413): los import deberían ir arriba, pero from app.main import app tiene que ir después del sys.path.append o la prueba no encuentra el módulo. Cuando cumplir la regla rompe la funcionalidad, se desactiva esa regla y solo esa, a la vista :

python
# pylint: disable=C0413

«En mi computador funciona»

El push sale en rojo en las tres versiones de Python: httpx estaba instalado en el computador, pero no en requirements.txt. La máquina limpia de GitHub Actions no lo tiene, y la prueba falla. El repositorio tiene que ser autocontenido:

text
pycryptodome
cryptography
fastapi
httpx

Ese error lo atrapó la automatización, no un usuario.

El reto de esta clase

main.py quedó al 97 % de cobertura porque nadie prueba la ruta /. Escribe la prueba que la lleva al 100 %.

Ver una solución posible
python
def test_home():
    """
    Prueba unitaria para verificar la ruta principal de la api
    """
    respuesta = client.get("/")
    assert respuesta.status_code == 200
    assert respuesta.json() == {"Hola": "Mundo123"}

Sigue con la clase 8

API completa, documentación automática y README

Seguir el canal de WhatsApp