Dependencias¶
FastAPI tiene un sistema de Inyección de Dependencias muy poderoso pero intuitivo.
Está diseñado para ser muy simple de usar, y para hacer que cualquier desarrollador integre otros componentes con FastAPI de forma muy sencilla.
Qué es la "Inyección de Dependencias"¶
"Inyección de Dependencias" significa, en programación, que hay una manera para que tu código (en este caso, tus path operation functions) declare las cosas que necesita para funcionar y utilizar: "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.
- Imponer seguridad, autenticación, requisitos de roles, etc.
- Y muchas otras cosas...
Todo esto, mientras minimizas la repetición de código.
Primeros Pasos¶
Veamos un ejemplo muy simple. Será tan simple que no es muy útil, por ahora.
Pero de esta manera podemos enfocarnos en cómo funciona el sistema de Inyección de Dependencias.
Crear una dependencia, o "dependable"¶
Primero enfoquémonos en la dependencia.
Es solo 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
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[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
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[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
Tip
Prefer to use the Annotated
version if possible.
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
Tip
Prefer to use the Annotated
version if possible.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[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 líneas.
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 devolver lo que quieras.
En este caso, esta dependencia espera:
- Un parámetro de query opcional
q
que es unstr
. - Un parámetro de query opcional
skip
que es unint
, y por defecto es0
. - Un parámetro de query opcional
limit
que es unint
, y por defecto es100
.
Y luego solo devuelve un dict
que contiene esos valores.
Información
FastAPI agregó soporte para Annotated
(y comenzó a recomendarlo) en la versión 0.95.0.
Si tienes una versión anterior, obtendrás errores al intentar usar Annotated
.
Asegúrate de Actualizar la versión de FastAPI al menos a la 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
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[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
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[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
Tip
Prefer to use the Annotated
version if possible.
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
Tip
Prefer to use the Annotated
version if possible.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[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 forma en 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
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[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
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[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
Tip
Prefer to use the Annotated
version if possible.
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
Tip
Prefer to use the Annotated
version if possible.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[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.
Le das a Depends
un solo parámetro.
Este parámetro debe ser algo como una función.
No la llames directamente (no agregues los paréntesis al final), solo pásala como un 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", además de funciones, pueden usarse como dependencias en el próximo capítulo.
Cada vez que llega un nuevo request, 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.
graph TB
common_parameters(["common_parameters"])
read_items["/items/"]
read_users["/users/"]
common_parameters --> read_items
common_parameters --> read_users
De esta manera escribes código compartido una vez y FastAPI se encarga de llamarlo para tus path operations.
Revisa
Nota que no tienes que crear una clase especial y pasarla en algún lugar a FastAPI para "registrarla" o algo similar.
Solo la pasas a Depends
y FastAPI sabe cómo hacer el resto.
Compartir dependencias Annotated
¶
En los ejemplos anteriores, ves que hay un poquito de 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
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[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
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[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 solo Python estándar, se llama un "alias de tipo", en realidad no es específico de FastAPI.
Pero porque FastAPI está basado en los estándares de Python, incluido Annotated
, puedes usar este truco en tu código. 😎
Las dependencias seguirán funcionando como se esperaba, y la mejor parte es que la información de tipo 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 gran base de código donde uses las mismas dependencias una y otra vez en muchas path operations.
Usar async
o no usar async
¶
Como las dependencias también serán llamadas por FastAPI (lo mismo 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 path operation functions normales def
, o dependencias def
dentro de path operation functions async def
, 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.
Integración con OpenAPI¶
Todas las declaraciones de request, validaciones y requisitos de tus dependencias (y sub-dependencias) se integrarán en el mismo esquema de OpenAPI.
Así, la documentación interactiva tendrá toda la información de estas dependencias también:
Uso simple¶
Si lo ves, las path operation functions se declaran para ser usadas siempre que un path y una operación coincidan, y luego FastAPI se encarga de llamar la función con los parámetros correctos, extrayendo los datos del request.
En realidad, todos (o la mayoría) de los frameworks web funcionan de esta misma manera.
Nunca llamas directamente a esas funciones. Son llamadas por tu framework (en este caso, FastAPI).
Con el sistema de Inyección de Dependencias, también puedes decirle a FastAPI que tu path operation function también "depende" de algo más que debe ejecutarse antes que tu path operation function, y FastAPI se encargará de ejecutarlo e "inyectar" los resultados.
Otros términos comunes para esta misma idea de "inyección de dependencias" son:
- recursos
- proveedores
- servicios
- inyectables
- componentes
Plug-ins de FastAPI¶
Las integraciones y "plug-ins" pueden construirse usando el sistema de Inyección de Dependencias. Pero, de hecho, en realidad no hay necesidad de crear "plug-ins", ya que al usar dependencias es posible declarar una cantidad infinita de integraciones e interacciones que se vuelven disponibles para tus path operation functions.
Y las dependencias se pueden crear 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 inyección de dependencias 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 la API
- sistemas de inyección de datos de response
- etc.
Simple y Poderoso¶
Aunque el sistema de inyección de dependencias jerárquico es muy simple de definir y usar, sigue siendo muy poderoso.
Puedes definir dependencias que a su vez pueden definir dependencias ellas mismas.
Al final, se construye un árbol jerárquico de dependencias, y el sistema de Inyección de Dependencias 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 agregar diferentes requisitos de permiso para cada uno de ellos solo con dependencias y sub-dependencias:
graph TB
current_user(["current_user"])
active_user(["active_user"])
admin_user(["admin_user"])
paying_user(["paying_user"])
public["/items/public/"]
private["/items/private/"]
activate_user["/users/{user_id}/activate"]
pro_items["/items/pro/"]
current_user --> active_user
active_user --> admin_user
active_user --> paying_user
current_user --> public
active_user --> private
admin_user --> activate_user
paying_user --> pro_items
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 agregar todo al esquema de OpenAPI, para que se muestre en los sistemas de documentación interactiva.