pythondependency-injectionasgilitestar

How to Properly Use dependency-injector with Litestar Controllers?


Problem Description:

I'm working on a sketch of a project using Litestar and I want to leverage the dependency-injector framework by Roman Mogylatov to handle dependency injection within my controllers. However, I've encountered some issues when trying to integrate dependency-injector with Litestar’s controller classes. I could do this with FastAPI because of the nature of their Depends function, but with Litestar nothing worked. I'm actually searching for someone with experience on this, because there's no documentation on integrating this kind of annotated dependency injection with Litestar framework. With the few months of experience with this kind of frameworks, I find dependency-injector an ideal implementation because it provides lazy initialization and more reliability through design patterns. I also don't like the function-based dependency injection most python web frameworks suggest since I find them not organized. Despite reading the documentation for both Litestar and dependency-injector, I haven't found a solution that works.

Project Structure:

Here's an overview of my project structure:

lite/
├── __init__.py
├── __main__.py
├── controllers.py
├── di.py
├── logging.py
├── main.py
├── services.py

di.py

from dependency_injector import containers, providers

from .services import Service


class Container(containers.DeclarativeContainer):

    service = providers.Factory(Service)

__main__.py:

import uvicorn

from .di import Container
from .logging import logger

if __name__ == "__main__":
    try:
        container = Container()
        container.wire(modules=[__name__,
                                "lite",
                                "lite.controllers",])

        config = uvicorn.Config(app="lite.main:app", port=3000)
        server = uvicorn.Server(config)

        server.run()
    except Exception as e:
        logger.exception(e)

main.py:

from litestar import Litestar
from .controllers import ExampleController
from litestar.logging import LoggingConfig

app = Litestar(
    route_handlers=[ExampleController],
    logging_config=LoggingConfig(
        root={"level": "INFO", "handlers": ["queue_listener"]},
        formatters={
            "standard": {
                "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
            }
        },
        log_exceptions="always",
    ),
)

Here is how I've tried to use dependency injection in my example controller:

controllers.py:

from typing import Annotated

from litestar import Controller, get
from litestar.di import Provide as Depends
from litestar.params import Dependency

from .di import Container
from dependency_injector.wiring import Provide, inject

from .services import Service


class ExampleController(Controller):
    path = "/test"

    @get("/")
    @inject
    async def bar(self, service: Service = Depends(Provide[Container.service])) -> None:
        service.foo() # just prints "foo"

Expected Behavior:

Actual Behavior:

When I run the application, I encounter the following exception:

Traceback (most recent call last):
  File "/workspaces/LiteTest/.venv/lib/python3.10/site-packages/litestar/_signature/model.py", line 101, in _deserializer
    return default_deserializer(target_type, value)
  File "/workspaces/LiteTest/.venv/lib/python3.10/site-packages/litestar/serialization/msgspec_hooks.py", line 139, in default_deserializer
    raise TypeError(f"Unsupported type: {type(value)!r}")
TypeError: Unsupported type: <class 'dependency_injector.wiring.Provide'>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/workspaces/LiteTest/.venv/lib/python3.10/site-packages/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
msgspec.ValidationError: Unsupported type: <class 'dependency_injector.wiring.Provide'> - at `$.service`
...

What I’ve Tried:

Question:

How can I properly use dependency-injector to inject dependencies into methods of a Litestar Controller subclass? Is there a way to make the dependency-injector compatible with Litestar’s dependency injection system, or do I need to implement a workaround?

Any examples or suggestions would be greatly appreciated!

Edit:

I discovered a very dirty way to integrate these two:


from litestar import Controller, get
from litestar.di import Provide as Depends

from .di import Container, Provide, inject

from .services import Service


@inject
def _get_service(service: Service = Provide[Container.service]) -> Service:
    return service

def get_service() -> Service:
    return _get_service()

class ExampleController(Controller):
    path = "/test"

    @get("/", dependencies={"service": Depends(get_service)})
    async def bar(self, service: Service) -> None:
        service.foo()

Solution

  • I can suggest an example with dishka instead of dependency-injector. From my point of view it gives you more flexibility

    https://github.com/Sehat1137/litestar-dishka-faststream