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.
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:
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
cd app
uvicorn main:app --reloadmain: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
"""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 &:
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.
"""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.pyimportafuncionescomo si estuviera al lado, pero la prueba corre desde la raíz del proyecto. Elsys.path.appendle 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
:
# 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:
pycryptodome
cryptography
fastapi
httpxEse 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 posibleOcultar la solución
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"}