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 :

python
@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 :

python
def test_url_encriptando_texto():
    """Prueba unitaria para encriptación de texto"""
    respuesta = client.get(
        "/encriptado?mensaje=hola&llave=UyJ4iWnYDPOcZ3sCmwNZtKbIudZRYl5n7owVTiQ4r3o="
    )
    assert respuesta.status_code == 200

El 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:

python
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:

python
@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.

python
@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 :

markdown
# Api De Cifrado y Encriptado

## Descripción

## Instalación

## Uso

## Descripción técnica

### Estructura del proyecto

### Configuraciones

### Módulo de pruebas

## Notas adicionales

Si 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 posible
python
# 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"}

Terminaste el curso

Vuelve a cualquier clase cuando lo necesites: el material sigue aquí.

Seguir el canal de WhatsApp