Clases como Dependencias¶
Antes de profundizar en el sistema de Dependency Injection, mejoremos el ejemplo anterior.
Un dict del ejemplo anterior¶
En el ejemplo anterior, estábamos retornando un dict desde nuestra dependencia ("dependable"):
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
Pero entonces obtenemos un dict en el parámetro commons de la path operation function.
Y sabemos que los editores no pueden proporcionar mucho soporte (como autocompletado) para los dicts, porque no pueden conocer sus claves y tipos de valores.
Podemos hacerlo mejor...
Qué conforma una dependencia¶
Hasta ahora has visto dependencias declaradas como funciones.
Pero esa no es la única forma de declarar dependencias (aunque probablemente sea la más común).
El factor clave es que una dependencia debe ser un "callable".
Un "callable" en Python es cualquier cosa que Python pueda "llamar" como una función.
Entonces, si tienes un objeto something (que podría no ser una función) y puedes "llamarlo" (ejecutarlo) así:
something()
o
something(some_argument, some_keyword_argument="foo")
entonces es un "callable".
Clases como dependencias¶
Puede que notes que para crear una instancia de una clase de Python, usas esa misma sintaxis.
Por ejemplo:
class Cat:
def __init__(self, name: str):
self.name = name
fluffy = Cat(name="Mr Fluffy")
En este caso, fluffy es una instancia de la clase Cat.
Y para crear fluffy, estás "llamando" a Cat.
Entonces, una clase de Python es también un callable.
Entonces, en FastAPI, podrías usar una clase de Python como dependencia.
Lo que FastAPI realmente verifica es que sea un "callable" (función, clase o cualquier otra cosa) y los parámetros definidos.
Si pasas un "callable" como dependencia en FastAPI, analizará los parámetros de ese "callable", y los procesará de la misma manera que los parámetros de una path operation function. Incluyendo sub-dependencias.
Eso también aplica a callables sin ningún parámetro. Igual que sería para path operation functions sin parámetros.
Entonces, podemos cambiar la dependencia "dependable" common_parameters de arriba por la clase CommonQueryParams:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
Presta atención al método __init__ usado para crear la instancia de la clase:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
...tiene los mismos parámetros que nuestro common_parameters anterior:
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
Esos parámetros son los que FastAPI usará para "resolver" la dependencia.
En ambos casos, tendrá:
- Un parámetro de query opcional
qque es unstr. - Un parámetro de query
skipque es unint, con un valor por defecto de0. - Un parámetro de query
limitque es unint, con un valor por defecto de100.
En ambos casos los datos serán convertidos, validados, documentados en el esquema OpenAPI, etc.
Usarlo¶
Ahora puedes declarar tu dependencia usando esta clase.
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
FastAPI llama a la clase CommonQueryParams. Esto crea una "instancia" de esa clase y la instancia será pasada como el parámetro commons a tu función.
Anotación de tipo vs Depends¶
Nota cómo escribimos CommonQueryParams dos veces en el código anterior:
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
Consejo
Preferible usar la versión con Annotated si es posible.
commons: CommonQueryParams = Depends(CommonQueryParams)
El último CommonQueryParams, en:
... Depends(CommonQueryParams)
...es lo que FastAPI realmente usará para saber cuál es la dependencia.
Es de esta de la que FastAPI extraerá los parámetros declarados y es lo que FastAPI realmente llamará.
En este caso, el primer CommonQueryParams, en:
commons: Annotated[CommonQueryParams, ...
Consejo
Preferible usar la versión con Annotated si es posible.
commons: CommonQueryParams ...
...no tiene ningún significado especial para FastAPI. FastAPI no lo usará para conversión de datos, validación, etc. (ya que está usando Depends(CommonQueryParams) para eso).
En realidad podrías escribir simplemente:
commons: Annotated[Any, Depends(CommonQueryParams)]
Consejo
Preferible usar la versión con Annotated si es posible.
commons = Depends(CommonQueryParams)
...como en:
from typing import Annotated, Any
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons=Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
Pero se recomienda declarar el tipo ya que de esa manera tu editor sabrá qué se pasará como el parámetro commons, y entonces podrá ayudarte con autocompletado de código, verificación de tipos, etc:

Atajo¶
Pero ves que tenemos algo de repetición de código aquí, escribiendo CommonQueryParams dos veces:
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
Consejo
Preferible usar la versión con Annotated si es posible.
commons: CommonQueryParams = Depends(CommonQueryParams)
FastAPI proporciona un atajo para estos casos, en donde la dependencia es específicamente una clase que FastAPI "llamará" para crear una instancia de la clase misma.
Para esos casos específicos, puedes hacer lo siguiente:
En lugar de escribir:
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
Consejo
Preferible usar la versión con Annotated si es posible.
commons: CommonQueryParams = Depends(CommonQueryParams)
...escribes:
commons: Annotated[CommonQueryParams, Depends()]
Consejo
Preferible usar la versión con Annotated si es posible.
commons: CommonQueryParams = Depends()
Declaras la dependencia como el tipo del parámetro, y usas Depends() sin ningún parámetro, en lugar de tener que escribir la clase completa otra vez dentro de Depends(CommonQueryParams).
El mismo ejemplo entonces se vería así:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
...y FastAPI sabrá qué hacer.
Consejo
Si te parece más confuso que útil, ignóralo, no lo necesitas.
Es solo un atajo. Porque a FastAPI le importa ayudarte a minimizar la repetición de código.