Saltar al contenido

Dependencias

FastAPI tiene un sistema de Dependency Injection muy potente pero intuitivo.

Está diseñado para ser muy simple de usar, y para hacer muy fácil que cualquier developer integre otros componentes con FastAPI.

Qué es "Dependency Injection"

"Dependency Injection" significa, en programación, que hay una forma para que tu código (en este caso, tus path operation functions) declare cosas que requiere para funcionar y usar: "dependencias".

Y luego, 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.
  • Aplicar seguridad, autenticación, requisitos de roles, etc.
  • Y muchas otras cosas...

Todo esto, minimizando la repetición de código.

Primeros Pasos

Veamos un ejemplo muy simple. Será tan simple que no será muy útil, por ahora.

Pero de esta manera podemos enfocarnos en cómo funciona el sistema de Dependency Injection.

Crear una dependencia, o "dependable"

Primero enfoquémonos en la dependencia.

Es simplemente una función que puede tomar todos los mismos parámetros que una path operation function puede tomar:

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
    return commons


@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return commons
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons


@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

Eso es todo.

2 lines.

Y tiene la misma forma y estructura que todas tus path operation functions.

Puedes pensar en ella como una path operation function sin el "decorador" (sin el @app.get("/some-path")).

Y puede retornar cualquier cosa que quieras.

En este caso, esta dependencia espera:

  • Un parámetro de query opcional q que es un str.
  • Un parámetro de query opcional skip que es un int, y por defecto es 0.
  • Un parámetro de query opcional limit que es un int, y por defecto es 100.

Y luego simplemente retorna un dict conteniendo esos valores.

Nota

FastAPI añadió soporte para Annotated (y empezó a recomendarlo) en la versión 0.95.0.

Si tienes una versión anterior, obtendrías errores al intentar usar Annotated.

Asegúrate de Actualizar la versión de FastAPI a al menos 0.95.1 antes de usar Annotated.

Importar Depends

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
    return commons


@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return commons
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons


@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

Declarar la dependencia, en el "dependant"

De la misma manera que usas Body, Query, etc. con los parámetros de tu path operation function, usa Depends con un nuevo parámetro:

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
    return commons


@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return commons
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons


@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return commons

Aunque usas Depends en los parámetros de tu función de la misma manera que usas Body, Query, etc, Depends funciona un poco diferente.

Solo le das a Depends un único parámetro.

Este parámetro debe ser algo parecido a una función.

no la llamas directamente (no añades los paréntesis al final), solo la pasas como parámetro a Depends().

Y esa función toma parámetros de la misma manera que las path operation functions.

Consejo

Verás qué otras "cosas", aparte de funciones, pueden usarse como dependencias en el próximo capítulo.

Siempre que llegue una nueva petición, FastAPI se encargará de:

  • Llamar a tu función de dependencia ("dependable") con los parámetros correctos.
  • Obtener el resultado de tu función.
  • Asignar ese resultado al parámetro en tu path operation function.

De esta manera escribes código compartido una vez y FastAPI se encarga de llamarlo para tus path operations.

Consejo

Ten en cuenta que no tienes que crear una clase especial y pasarla a algún lado a FastAPI para "registrarla" o nada similar.

Solo la pasas a Depends y FastAPI sabe cómo hacer el resto.

Compartir dependencias Annotated

En los ejemplos anteriores, ves que hay una pequeña duplicación de código.

Cuando necesitas usar la dependencia common_parameters(), tienes que escribir todo el parámetro con la anotación de tipo y Depends():

commons: Annotated[dict, Depends(common_parameters)]

Pero como estamos usando Annotated, podemos almacenar ese valor Annotated en una variable y usarlo en múltiples lugares:

from typing import Annotated

from fastapi import Depends, FastAPI

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


CommonsDep = Annotated[dict, Depends(common_parameters)]


@app.get("/items/")
async def read_items(commons: CommonsDep):
    return commons


@app.get("/users/")
async def read_users(commons: CommonsDep):
    return commons

Consejo

Esto es simplemente Python estándar, se llama "type alias", en realidad no es específico de FastAPI.

Pero como FastAPI está basado en los estándares de Python, incluyendo Annotated, puedes usar este truco en tu código. 😎

Las dependencias seguirán funcionando como se espera, y la mejor parte es que la información de tipos se preservará, lo que significa que tu editor podrá seguir proporcionándote autocompletado, errores en línea, etc. Lo mismo para otras herramientas como mypy.

Esto será especialmente útil cuando lo uses en una base de código grande donde usas las mismas dependencias una y otra vez en muchas path operations.

async o no async

Como las dependencias también serán llamadas por FastAPI (igual que tus path operation functions), las mismas reglas aplican al definir tus funciones.

Puedes usar async def o def normal.

Y puedes declarar dependencias con async def dentro de def normales path operation functions, o dependencias con def dentro de async def path operation functions, etc.

No importa. FastAPI sabrá qué hacer.

Nota

Si no lo sabes, revisa la sección Async: "¿Con prisa?" sobre async y await en la documentación.

Integrado con OpenAPI

Todas las declaraciones de petición, validaciones y requisitos de tus dependencias (y sub-dependencias) serán integrados en el mismo esquema OpenAPI.

Así que, la documentación interactiva también tendrá toda la información de estas dependencias:

Uso simple

Si lo piensas, las path operation functions se declaran para ser usadas cuando un path y una operation coinciden, y luego FastAPI se encarga de llamar a la función con los parámetros correctos, extrayendo los datos de la petición.

En realidad, todos (o la mayoría) de los frameworks web funcionan de esta misma manera.

Nunca llamas a esas funciones directamente. Son llamadas por tu framework (en este caso, FastAPI).

Con el sistema de Dependency Injection, también puedes decirle a FastAPI que tu path operation function también "depende" de algo más que debería ejecutarse antes de tu path operation function, y FastAPI se encargará de ejecutarlo e "inyectar" los resultados.

Otros términos comunes para esta misma idea de "dependency injection" son:

  • resources
  • providers
  • services
  • injectables
  • components

Plug-ins de FastAPI

Se pueden construir integraciones y "plug-ins" usando el sistema de Dependency Injection. Pero de hecho, en realidad no hay necesidad de crear "plug-ins", ya que usando dependencias es posible declarar un número infinito de integraciones e interacciones que quedan disponibles para tus path operation functions.

Y las dependencias pueden crearse de una manera muy simple e intuitiva que te permite simplemente importar los paquetes de Python que necesitas, e integrarlos con tus funciones de API en un par de líneas de código, literalmente.

Verás ejemplos de esto en los próximos capítulos, sobre bases de datos relacionales y NoSQL, seguridad, etc.

Compatibilidad de FastAPI

La simplicidad del sistema de dependency injection hace que FastAPI sea compatible con:

  • todas las bases de datos relacionales
  • bases de datos NoSQL
  • paquetes externos
  • APIs externas
  • sistemas de autenticación y autorización
  • sistemas de monitoreo de uso de API
  • sistemas de inyección de datos de respuesta
  • etc.

Simple y Potente

Aunque el sistema jerárquico de dependency injection es muy simple de definir y usar, sigue siendo muy potente.

Puedes definir dependencias que a su vez pueden definir dependencias por sí mismas.

Al final, se construye un árbol jerárquico de dependencias, y el sistema de Dependency Injection se encarga de resolver todas estas dependencias por ti (y sus sub-dependencias) y proporcionar (inyectar) los resultados en cada paso.

Por ejemplo, digamos que tienes 4 endpoints de API (path operations):

  • /items/public/
  • /items/private/
  • /users/{user_id}/activate
  • /items/pro/

entonces podrías añadir diferentes requisitos de permisos para cada uno de ellos solo con dependencias y sub-dependencias:

Integrado con OpenAPI

Todas estas dependencias, al declarar sus requisitos, también añaden parámetros, validaciones, etc. a tus path operations.

FastAPI se encargará de añadirlo todo al esquema OpenAPI, para que se muestre en los sistemas de documentación interactiva.