Iniciamos con un poco de contexto, FastAPI es un framework de alto desempeño en python para desarrollo de APIS además tiene un montón de características interesantes a destacar como que esta desarrollado basado en diferentes estándares como OpenAPI,  JSON Schema y que nativamente permite trabajar de manera asíncrona lo cual esta super cool

Aveces en el proceso de desarrollo software en el backend cuando se trabaja con APIS ocurren algunos casos relacionados con documentación:

  • La documentación de los endpoints en el mejor de los casos no es tan buena
  • Aveces se desactualiza al cambiar el codigo y tipos de datos en los parámetros
  • En el peor de los casos es un archivo formal o alguna wiki que nadie se interesa en leer

Por este motivo utilizamos estas herramientas y estandares (OpenAPI, Swagger) para que nuestra documentación de APIS sea la mejor, sin embargo en python yo he trabajado con Django y con Flask que por cierto son los dos frameworks mas usados segun la ultima encuesta de devs de python 2020 (ver Figura 1) donde ademas FastAPI introducido como opcion por primera vez es el tercero mas usado y subiendo . Volviendo al tema principal estos dos framework permiten realizar la documentación con estos estandares utilizando Swagger pero no son nativas y ademas no son tan automaticas o acopladas al codigo, por lo tanto requieren trabajo adicional (lo cual no esta mal) pero en FastAPI como mostrare acontinuacion aparte de ser propio del framework es muy facil de implementar la documentación

Figura 1 : Frameworks de desarrollo web en python mas usados

Bueno, pasemos al código como siempre utilizaremos poetry para instalar nuestras dependencias

Instalamos FastAPI

poetry add fastapi

Instalamos nuestro servidor ASGI (recordemos que necesitamos manejar asincronismo)

poetry add uvicorn[standard]

con las dependencias listas podemos empezar a escribir código

from typing import  Optional
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}

levantamos el servidor

uvicorn main:app --reload

y nos dirigimos por defecto a locahost:8000/docs

Figura 2: Documentación generada automáticamente

y como por arte de magia en esta url vemos nuestro endpoint /items/{item_id} con los parámetros que debemos recibir informandonos a la par el tipo requerido,  pero bueno vayamos por partes

@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}

en la porcion de codigo anterior tenemos el decorador @app el cual le decimos el método por el cual responderá nuestra llamada http en este caso GET, como parámetro recibe una variable (utilizada con el formato string f) que es item_id , esta variable se conoce como parámetro de url (seria algo como esto /items/1) esto va unido a un método el cual debe recibir el mismo nombre de la variable que recibe por parametro url, aqui le asignamos un tipo (int) esto es muy importante por que apartir de aqui FastAPI empieza  a realizar el trabajo por nosotros, después recibimos una variable q: Optional[str] = None en este caso esta variable se conoce como query parameter (en el ejemplo se vería algo como esto /items/1?q=example) aqui le decimos a FastAPI que esta variable es opcional, finalmente retornamos un dict con los datos que recibimos algo super genial es que FastAPI automáticamente nos "serializa" este dict a JSON, ya en la imagen a continuación vemos como los parámetros que recibimos se documentan de acuerdo a nuestro código escrito

Figura 3: Parámetros recibidos por el endpoint

Genial este ejemplo es bastante sencillo sacado de la documentación oficial, veamos  un ejemplo mas elaborado, para ello crearemos una clase Item con unos atributos ademas crearemos un método para instanciar un item, lo guardaremos en una lista y la retornaremos, FastAPI por su parte nos ayudara documentando nuestro endpoint

from typing import  List
from fastapi import FastAPI, Query, HTTPException
from fastapi import status
from pydantic import BaseModel, conint

app = FastAPI()

class Item(BaseModel):
    id:int
    name:str
    price:conint(gt=1000, lt=20000) 
    
items: List[Item] = []
items_discount : List[Item] = []


@app.post("/items", response_model=List[Item], name="items:create")
def create_item(
    item: Item,
    discount: bool = Query(False, description="El item tiene descuento habilitado"),
) -> List[Item]:
    for item_save in items:
        if item_save.id == item.id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="el id ya existe"
            )
    if discount:
        items_discount.append(item)
    else:
        items.append(item)
    return items+items_discount

La documentación generada por nuestra herramienta se aprecia en la siguiente imagen

Figura 4: Parámetros y cuerpo de la petición
Figura 5: Posibles respuestas del endpoint

ok  recapitulemos y analicemos un poco el código

class Item(BaseModel):
    id:int
    name:str
    price:conint(gt=1000, lt=20000) 
    
items: List[Item] = []
items_discount: List[Item] = []

FastAPI utiliza para sus validaciones por debajo pydantic el cual es una librería que ya he recomendado en anteriores post, aquí utilizando esta librería creamos la clase Item lo importante a destacar es el atributo de tipo conint es un atributo propio de la librería que permite agregar validaciones, en este caso la variable precio debe ser mayor a 1000 y menor a 20000

@app.post("/items", response_model=List[Item], name="items:create")
def create_item(
    item: Item,
    discount: bool = Query(False, description="El item tiene descuento habilitado"),
) -> List[Item]:
    for item_save in items:
        if item_save.id == item.id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="el id ya existe"
            )
    if discount:
        items_discount.append(item)
    else:
        items.append(item)
    return items+items_discount

creamos una petición por POST en donde adicionalmente aqui vemos que pasamos algunos parámetros

  • response_model nos indica el tipo de la data que el endpoint va a responder si todo sale bien, por eso en la imagen de la documentación se observa un schema de ejemplo generado por la herramienta
Figura 6: Schema de ejemplo de respuesta
  • name es un atributo descriptivo que se agrega al lado del endpoint para entender mejor su función
def create_item(
    item: Item,
    discount: bool = Query(False, description="El item tiene descuento habilitado"),
)

El método create_item recibe tres parámetros, el primero de tipo Item el cual es la data de nuestro body de la petición POST, el siguiente es discount este es un query parameter pero vemos que utilizamos la clase Query con ella podemos agregar una mejor descripción para la documentación como se ve en la imagen

Figura 7: Descripción adicionada al parametro de query

Finalmente en el código tenemos una lógica en donde validamos el id del ítem para que no se repita, y una validación con la variable discount para agregar a diferentes listas, lo importante de nuevo a destacar es como FastAPI serializa la lista de respuesta utilizando el estándar JSON Schema y genera la documentación para esta respuesta sin importar que tan compleja sea la clase o el objeto este lo convierte a Json, en el ejemplo se tiene una variable items y items_discount las cuales son listas de objetos de tipo Item.

Igualmente podemos probar y hacer peticiones desde la herramienta que nos documenta (Swagger) como vemos en la siguiente imagen

Figura 7 : Prueba satisfactoria del endpoint

Si se incumple la validación del precio>1000 que indicamos en la clase Item  genera automáticamente un error

Figura 8: Prueba con datos invalidos del endpoint 

Al final del día aparte de la documentación, tenemos también  estas validaciones  que nos permiten enfocarnos en el problema a resolver y no en pensar en estar validando estos parámetros

como es costumbre quiero cerrar con tres puntos a manera de conclusión

  1. Integrar documentación a nuestra API como se vio anteriormente se convierte en algo bastante sencillo ya que es nativo y por lo tanto a medida que cambia el código la documentación se actualiza
  2. Aparte FastAPI integra otras visualizaciones para la documentación dependiendo de los requerimientos que se tengan (recomiendo revisar la documentación)
  3. Junto con la documentación ademas se integran unas validaciones que realiza FastAPI las cuales se complementan, por lo tanto a mejor documentación se va generando una mejora en las diferentes validaciones automáticas (que por cierto esto ahorra muchísimo tiempo en el desarrollo)

Ha sido un post bastante largo ya que es una herramienta que en verdad me ha gustado y hay mucho temas por hablar sobre este framework asi que espero poder dividir en temas mas pequeños para proximas entregas.