Saltar a contenido

Inyección de Dependencias

FastAPI dispone de un sistema de Inyección de Dependencias, también conocidos como componentes, recursos, proveedores, servicios, inyectables, (y un largo etc... 😄) muy potente pero intuitivo. Está diseñado para ser muy sencillo de utilizar y para que cualquier desarrollador pueda integrar fácilmente otros componentes con FastAPI.

«Inyección de Dependencias» significa, que hay una forma de que tu código (en este caso, tus routes) declaren cosas que requiere para funcionar y usar: «dependencias».

Y entonces, ese sistema (en este caso FastAPI) se encargará de hacer lo que sea necesario para proporcionar a tu código esas dependencias necesarias («inyectar» las dependencias).

Esto es muy útil cuando necesitas:

  • Tener lógica compartida (la misma lógica de código una y otra vez).
  • Compartir conexiones a bases de datos.
  • Imponer seguridad, autenticación, requisitos de roles, etc.

Y muchas otras cosas...Todo esto, minimizando la repetición de código.

veamos un ejemplo:

Para tener en cuenta

  • Operación de ruta (Path Operation): Se refiere a una operación asociada a un "path" ó ruta en particular, que responde a una solicitud HTTP. Por ejemplo, una operación que maneja solicitudes GET, POST, PUT, etc., en una ruta determinada.
1
2
3
    @app.get("/items/")
    async def read_items():
        return {"items": ["item1", "item2"]}
  • Route: Una ruta es la dirección URL que se utiliza para acceder a una operación de ruta. (ej: /users)
  • Endpoint: es la combinación de la ruta y el método HTTP. Es el lugar completo al que se hace una solicitud HTTP. (ej: GET /users/)

Crear una dependencia, o "dependable"

Centrémonos primero en la dependencia.

Definimos una función que va a representar nuestra dependencia, puede tomar los mismos parámetros que puede tomar una operación de ruta, y devolver lo que quieras.

Este ejemplo sencillo leerá la cabecera "X-Token":

1
2
3
4
5
6
7
...

async def verify_token(x_token: Annotated[str, Header()]):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")

    return x_token

Usar la dependencia en un Operación de ruta

Ahora que hemos creado la dependencia, podemos utilizarla en un endpoint de FastAPI. Para esto, simplemente la incluimos como un argumento en la función, precedida por el tipo Depends().

from typing import Annotated
from fastapi import Depends, FastAPI

app = FastAPI()

...

class Item(BaseModel):
    name: str
    last_name: str

@app.get("/users/")
async def read_users(token:str, Depends(verify_token)]):
     return {"message": f"Token recibido: {token}"}

@app.post("/users/")
async def create_item(item: Item, token: str = Depends(verify_token)):
    return {"message": f"Item '{item.name}' creado con el token: {token}"}

Nota

En cada operación de ruta del ejemplo, estamos usando la misma lógica de código una y otra vez, a través de la Inyección de Dependencias. 🎉

En este ejemplo, el endpoint GET /users/ y POST /users/ "dependen" de la función verify_token(). Cuando un cliente hace una solicitud a alguna de estas rutas, FastAPI automágicamente ejecuta la función verify_token(), en este caso devolviendo el token, si la validacion no falla, e "inyectando" este valor (en el parámetro token) en el endpoint que se esté ejecutando.

El ejemplo completo:

from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi import HTTPException, Header

app = FastAPI()
...

async def verify_token(x_token: Annotated[str, Header()]):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")

    return x_token

class Item(BaseModel):
    name: str
    last_name: str

@app.get("/users/")
async def read_users(token:str, Depends(verify_token)]):
     return {"message": f"Token recibido: {token}"}

@app.post("/users/")
async def create_item(item: Item, token: str = Depends(verify_token)):
    return {"message": f"Item '{item.name}' creado con el token: {token}"}

Dependencias y APIRouter

Digamos que también tienes los endpoints dedicados a manejar "customers", "artists", "playlist", en tu BackEnd de servicios, claro, siempre puedes agregar Dependencias (Depends) a cada uno de ellos, pero si, conoces de antemano que cada uno de esos endpoints necesita una dependencia, para validar algun aspecto, en vez de añadir todo eso a cada operación de ruta, podemos añadirlo al APIRouter.

Veamos:

from fastapi import APIRouter, Depends
from path.to.dependencies.files import verify_token # fake import
from path.to.models import Item # fake import
...

user_router = APIRouter(
    prefix="/users",
    tags=["users"],
    dependencies=[Depends(verify_token)],
)

@user_router.get("/")
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]

@user_router.get("/{item_id}")
async def read_one_user(item_id: str):
    return [{"username": "Rick", "item_id": item_id}, {"username": "Morty", "item_id": item_id}]


@user_router.post("/")
async def create_user(item: Item):
    return {"username": item.name}

...y de esta forma, la dependencia verify_token se añadirá a todas las operaciones de ruta y se ejecutará para cada petición que se les haga.

Nota

Te recomendamos leer el apartado de Módulos y Recursos, para más información.

Lectura recomendada