Custom dependency injection mechanism #584
-
Ask your question... Starlite validates all controller's parameters, so I can't simply put my "dependency" there. Here is an example with DI container (requires dependency-injector): from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
from starlite import Starlite, get, Dependency
class Cache:
def get(self): print("Value")
class Container(containers.DeclarativeContainer):
cache = providers.Factory(Cache)
@get("/1")
def first(dependency: int = Dependency(default=1, skip_validation=True)) -> None:
# how does that work?
print(type(dependency)) # >>> <class 'ellipsis'>
@get("/2")
@inject
def second(cache: Cache = Provide[Container.cache]) -> None: # how to wrap `Provide` object properly?
cache.get()
@inject # wiring example
def on_startup(*args, cache: Cache = Provide[Container.cache], **kwargs) -> None:
cache.get()
container = Container()
container.wire(modules=[__name__])
app = Starlite(
route_handlers=[first, second],
on_startup=[on_startup], # >>> "Value"
) So the question is: are there any workarounds for this (for skipping validation and passing a callable as an argument)? Currently Starlite's |
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 22 replies
-
There is the |
Beta Was this translation helpful? Give feedback.
-
As a possible workaround, one can use class-based controllers and inject dependencies as class attributes. Example: from dependency_injector import containers, providers
from dependency_injector.wiring import Provide
from starlite import Controller, Starlite, get
class Cache:
def get(self):
print("Value")
class Container(containers.DeclarativeContainer):
cache = providers.Factory(Cache)
class Resource(Controller):
cache = Provide[Container.cache]
@get("/1")
def controller(self) -> None:
self.cache.get()
container = Container()
container.wire(modules=[__name__])
app = Starlite(route_handlers=[Resource])
@Goldziher But I'm still curious if you can do something like that in Starlite |
Beta Was this translation helpful? Give feedback.
-
Another solution is to use Starlite DI mechanism instead of Example: from functools import partial
from typing import AsyncIterator, TypeAlias
from dependency_injector import containers, providers
from dependency_injector.containers import DynamicContainer
from starlite import Provide, Starlite, get
# cache.py
class Cache:
@classmethod
async def from_url(cls) -> "Cache":
return cls()
async def get(self) -> None:
print("Value")
async def init_cache() -> AsyncIterator[Cache]:
cache = await Cache.from_url()
yield cache
# containers.py
class Container(containers.DeclarativeContainer):
cache = providers.Resource(init_cache)
# controllers.py
@get("/1")
async def controller(cache: Cache) -> None:
await cache.get() # >>> Value
# dependencies.py
# XXX: Dependency Injector can cast container to `DynamicContainer` and Starlite's type check would fail.
ContainerT: TypeAlias = Container | DynamicContainer
async def get_cache(container: ContainerT) -> Cache:
return await container.cache()
def create_dependencies() -> dict[str, Provide]:
return {"cache": Provide(get_cache)}
# main.py
async def on_startup(*_, container: Container, **__) -> None:
# XXX: can't pass `container.init_resources()` directly into `Starlite.on_startup` because of AnyIO worker thread
await container.init_resources()
def create_app() -> Starlite:
container = Container()
container.wire(modules=[__name__])
def get_container() -> Container:
return container
dependencies = {"container": Provide(get_container)}
dependencies.update(create_dependencies())
app = Starlite(
route_handlers=[controller],
dependencies=dependencies,
on_startup=[partial(on_startup, container=container)],
)
return app |
Beta Was this translation helpful? Give feedback.
-
How about something like this? from typing import AsyncIterator
from dependency_injector import containers, providers
from starlite import Provide, Starlite, State, get
# cache.py
class Cache:
@classmethod
async def from_url(cls) -> "Cache":
return cls()
async def get(self) -> None:
print("Value")
async def init_cache() -> AsyncIterator[Cache]:
cache = await Cache.from_url()
yield cache
# containers.py
class Container(containers.DeclarativeContainer):
cache = providers.Resource(init_cache)
# controllers.py
@get("/1")
async def controller(cache: Cache) -> None:
await cache.get() # >>> Value
# main.py
async def get_cache(state: State) -> Cache:
return await state.container.cache()
async def on_startup(state: State) -> None:
# XXX: can't pass `container.init_resources()` directly into `Starlite.on_startup` because of AnyIO worker thread
container = Container()
container.wire(modules=[__name__])
await container.init_resources()
state.container = container
def create_app() -> Starlite:
app = Starlite(
route_handlers=[controller],
dependencies={"cache": Provide(get_cache)},
on_startup=[on_startup],
)
return app |
Beta Was this translation helpful? Give feedback.
-
@ReznikovRoman Seems like you could create your own |
Beta Was this translation helpful? Give feedback.
-
Adding some detail from a discussion with @ThirVondukr in discord. @ThirVondukr expressed that a weakness with having a built-in DI is the inability to share that DI mechanism with interfaces unrelated to the framework and gave the following example code: async def get_user(
token: str = Header(...), # Handled by framework
user_service: Annotated[UserService, Inject], # Handled by your DI library
) -> User:
...
async def endpoint(
service: Annotated[SomeOtherService, Inject],
user: User = Depends(get_user),
) -> ... Example DI frameworks:
Examples of using
Would certainly be interesting to investigate whether we could facilitate our DI to interop with framework DI in some manner. |
Beta Was this translation helpful? Give feedback.
-
@peterschutt Essentially modifying wrapped function is up to DI framework or people who use it, I don't think that starlite could provide something for that case, all DI libraries use something different. For example:
|
Beta Was this translation helpful? Give feedback.
How about something like this?