Skip to content

FastAPI ​

APIs agiles con Python y FastAPI

FastAPI fue construido por el desarrollador colombiano Sebastián Ramirez, se trata de un marco de trabajo web moderno y de alto rendimiento para construir APIs con Python 3.7+ basado en type hints estándar. Se destaca por su velocidad, facilidad de uso y generación automática de documentación.

Lo que hace atractivo este framework para el desrrallo de una API bajo Python son caractarísticas tales como:

  • Validación automática: Usa type hints de Python para validar datos de entrada y salida
  • Documentación automática: Genera OpenAPI (Swagger) y ReDoc sin configuración adicional
  • Alto rendimiento: Basado en Starlette y Pydantic, comparable a NodeJS y Go
  • Soporte async/await: Programación asíncrona nativa para operaciones I/O
  • Inyección de dependencias: Sistema integrado para gestionar dependencias

Instalación de FastAPI ​

bash
pip install fastapi uvicorn

Uvicorn es el servidor ASGI recomendado para ejecutar FastAPI en producción.
Es conveniente usar entornos virtuales para instalación, por ejemplo, si usas sistema macOS.

Usando entorno virtual ​

Los entornos virtuales permiten conservar el conjunto de depedencias de un proyecto específico, evitando conflictos por uso de versiones distintas entre diversos proyectos. Además ofrecen la versiónn de Python que requiere tu proyecto. Es decir, proporcionan un entorno específico para tu proyecto (de modo virtual).

Es probable que con la instalación de Python encuentres venv. De esta manera, el comando pip install fastapi uvicorn (de arriba) se debe ejecutar después de establecer lo siguiente...

bash
python3 -m venv ./venv
source venv/bin/activate

./venv se refiere a la ruta en la que se gestiona el entorno virtual.
deactivate es el comando usado para salir del entorno virtual.

Primer contacto con FastAPI ​

Crea un archivo main.py, agrega y observa el siguiente código:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hi": "there!"}

Ejecuta el servidor:

bash
uvicorn main:app --reload

La API estará disponible en http://127.0.0.1:8000 La documentación automática se genera en http://127.0.0.1:8000/docs (Swagger UI) o /redoc (ReDoc)

Siguiente API con FastAPI ​

A continuación tendremos otro ejemplo sencillo en el que involucramos un router para organizar la gestión del endpoint. De esa manera, creaermeos tres (3) archivos Python: main.py, router.py, endpoint.py. Veamos cada uno...

  • main.py
python
from fastapi import FastAPI
from router import router

app = FastAPI()
app.include_router(router)

@app.get("/")
def read_root():
    return {"Hi": "there!"}
  • router.py
python
from fastapi import APIRouter
import endpoint

router = APIRouter()
router.include_router(endpoint.router, prefix="/actions", tags=["actions"])
  • endpoint.py
python
import json
from http import HTTPStatus

from fastapi import APIRouter
from starlette.responses import Response

router = APIRouter()

@router.post("/", dependencies=[])
def handle_event(data: dict) -> Response:
    print(data)
    return Response(
        content=json.dumps({"message": "Data processed!"}),
        status_code=HTTPStatus.ACCEPTED,
        media_type="application/json",
    )

Nótese que se define el endpoint y se inporta en router bajo la ruta /actions, y en main se incluye el router.
Recuerda lanzar el servicio con comando: uvicorn main:app --reload

Estructura de proyecto recomendada ​

./myapi/
├── app/
│   ├── __init__.py
│   ├── main.py          # Punto de entrada de la aplicación
│   ├── models.py        # Modelos Pydantic (esquemas de datos)
│   ├── routers/         # Rutas organizadas por recurso
│   │   ├── __init__.py
│   │   ├── items.py
│   │   └── users.py
│   ├── dependencies.py  # Dependencias inyectables
│   └── database.py      # Configuración de base de datos
├── requirements.txt
└── .env

Conceptos clave ​

Path Operations (Operaciones de ruta) ​

python
@app.get("/items")      # Leer recursos
@app.post("/items")     # Crear recursos
@app.put("/items/{id}") # Actualizar completo
@app.patch("/items/{id}") # Actualizar parcial
@app.delete("/items/{id}") # Eliminar recursos

Modelos con Pydantic ​

python
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.post("/items/")
def create_item(item: Item):
    return item

Parámetros de consulta y cuerpo ​

python
from fastapi import Query, Path, Body

@app.get("/items/")
def read_items(
    q: str = Query(None, max_length=50),
    skip: int = 0,
    limit: int = 100
):
    return {"q": q, "skip": skip, "limit": limit}

@app.put("/items/{item_id}")
def update_item(
    item_id: int = Path(..., title="ID del item"),
    item: Item = Body(...)
):
    return {"item_id": item_id, **item.dict()}

Inyección de dependencias ​

python
from fastapi import Depends

def get_query_token(token: str):
    if token != "fake-super-secret-token":
        raise HTTPException(status_code=403, detail="Token inválido")
    return token

@app.get("/items/")
def read_items(token: str = Depends(get_query_token)):
    return [{"item": "Foo", "token": token}]

Respuestas y status codes ​

python
from fastapi import Response, status

@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(item: Item, response: Response):
    response.headers["X-Custom-Header"] = "custom-value"
    return item

Middleware y CORS ​

python
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Base de datos con SQLAlchemy ​

python
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# Dependencia para obtener sesión de DB
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Testing ​

python
from fastapi.testclient import TestClient

client = TestClient(app)

def test_read_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"Hello": "World"}

def test_read_item():
    response = client.get("/items/42?q=test")
    assert response.status_code == 200
    assert response.json() == {"item_id": 42, "q": "test"}

Despliegue ​

bash
# Desarrollo
uvicorn main:app --reload --host 0.0.0.0 --port 8000

# Producción
uvicorn main:app --host 0.0.0.0 --port 80 --workers 4

# Con Gunicorn (para múltiples workers)
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker

Chat con FastAPI & OpenIA API ​

Dada las facilidades de FastAPI para hacer una API en Python, y del lenguaje para interactuar con modelos de IA, resulta propicio para un ejercicio que he publicado en GitHub sobre Chat con FastAPI & OpenIA API