Clase 08 · 31 minGratis
API completa, documentación automática y README
Clase 8 del curso gratis de buenas prácticas en Python: exponemos el encriptado y el hash como rutas de la API, aprovechamos la documentación automática de FastAPI en /docs y cerramos con un README que cualquiera pueda seguir.
Toca cualquier marca ▸ 0:00 de los apuntes y el video salta a ese momento. Abrir en YouTube
Para qué sirve una API en un equipo
Tu función de encriptado funciona en Python, pero otro equipo trabaja en PHP, Node.js o Go. En vez de obligar a todos a usar el mismo lenguaje, la función se publica como una API: cualquiera le hace una petición y recibe JSON. Esa es la idea detrás de los microservicios : cada servicio hace una cosa pequeña, y da igual en qué esté escrito mientras hable HTTP y JSON.
La ruta de encriptado
Lo que llega por la URL es texto (str), pero Fernet trabaja con bytes. La
ruta hace la conversión con .encode("utf-8") y, de paso, las pistas de tipo
de encriptado.py pasan de str a bytes para decir la verdad
:
@app.get("/encriptado")
def url_encriptando_texto(mensaje, llave):
"""En esta url se podrá pedir la encriptación simetrica de un mensaje
Args:
mensaje (str): mensaje a ser encriptado
llave (str): llave con la que se hará encriptación simetrica
Returns:
(json): respuesta de la api con mensaje encriptado
"""
salida = encriptando_texto(mensaje.encode("utf-8"), llave.encode("utf-8"))
return {"mensaje_encriptado": salida}Recarga la página dos veces con el mismo mensaje: el resultado cambia, como vimos en la clase 4.
Su prueba no comprueba el texto encriptado (eso ya lo prueba
test_encriptando_texto.py), sino que la ruta responda :
def test_url_encriptando_texto():
"""Prueba unitaria para encriptación de texto"""
respuesta = client.get(
"/encriptado?mensaje=hola&llave=UyJ4iWnYDPOcZ3sCmwNZtKbIudZRYl5n7owVTiQ4r3o="
)
assert respuesta.status_code == 200El primer intento da 404: la URL tenía = donde iba ?
. Un carácter cambia la ruta entera.
La ruta de hash, ahora sí con TDD
La ruta de encriptado se escribió antes que su prueba («mil disculpas a los puristas»). Para el hash se respeta el orden: primero la prueba, que verifica el código y el contenido:
def test_text_hash():
"""Esta es la prueba unitaria del modulo de hashing"""
respuesta = client.get("/text_hash?mensaje=Hola")
esperado = "e633f4fc79badea1dc5db970cf397c8248bac47cc3acf9915ba60b5d76b0e88f"
assert respuesta.status_code == 200
assert respuesta.json() == {"mensaje_hash": esperado}Después, la ruta:
@app.get("/text_hash")
def url_text_hash(mensaje):
"""Url para hacer hashing de un mensaje con SHA256
Args:
mensaje (str): mensaje a ser hasheado
"""
salida = text_hash(mensaje)
return {"mensaje_hash": salida}La cobertura de main.py queda casi completa ; el
reporte dice cuántas líneas faltan, y pytest --cov=app --cov-report=term-missing tests/
dice además cuáles.
En un equipo no se escribe directo en main
En el curso todo va a main porque hay una sola persona. En una empresa lo
normal es trabajar en una rama propia y abrir un pull request (o merge
request): otra persona revisa línea por línea qué cambió y aprueba o pide
cambios antes de que llegue a la rama principal. La vista de diferencias de
GitHub es justo lo que mira quien revisa .
Documentación automática en /docs
FastAPI genera una página interactiva (Swagger) con todas las rutas en
http://localhost:8000/docs. Ahí se puede probar cada una con un formulario,
sin escribir la URL a mano.
Lo mejor: los docstrings que escribimos por Pylint aparecen ahí . Documentar bien la función es, a la vez, documentar la API para quien la use.
@app.get("/cifrado-cesar")
def url_cifrado_cesar(texto, clave):
"""
URL para hacer encriptado CESAR
Se debe ingresar el texto y la clave
texto (str)
clave (int)
"""
salida = cifrado_cesar(texto, int(clave))
return {"mensaje_cifrado": salida}El README: un regalo para tu yo del futuro
Dentro de seis meses no vas a recordar cómo se instala ni qué hace cada
carpeta. Los docstrings explican el código; el README.md explica el
proyecto, para alguien técnico y para alguien de negocio. Una estructura que
sirve de punto de partida :
# Api De Cifrado y Encriptado
## Descripción
## Instalación
## Uso
## Descripción técnica
### Estructura del proyecto
### Configuraciones
### Módulo de pruebas
## Notas adicionalesSi tu equipo ya tiene una plantilla de README, úsala; si no, propón una.
El reto final
Completa la API con la ruta que falta: /desencriptado, que reciba un mensaje
encriptado y la llave y devuelva el texto original. Escribe primero su prueba:
encripta con Fernet, llama a la ruta y compara.
Ver una solución posibleOcultar la solución
# app/main.py
from funciones.encriptado import encriptando_texto, desencriptando_texto
@app.get("/desencriptado")
def url_desencriptando_texto(mensaje_encriptado, llave):
"""En esta url se podrá desencriptar un mensaje con su llave simetrica
Args:
mensaje_encriptado (str): mensaje encriptado con Fernet
llave (str): la misma llave con la que se encriptó
"""
salida = desencriptando_texto(mensaje_encriptado.encode("utf-8"), llave.encode("utf-8"))
return {"mensaje": salida.decode("utf-8")}
# tests/test_main.py
from cryptography.fernet import Fernet
def test_url_desencriptando_texto():
"""Prueba unitaria para desencriptación de texto"""
llave = Fernet.generate_key()
encriptado = Fernet(llave).encrypt(b"hola").decode("utf-8")
respuesta = client.get(
"/desencriptado", params={"mensaje_encriptado": encriptado, "llave": llave.decode()}
)
assert respuesta.status_code == 200
assert respuesta.json() == {"mensaje": "hola"}