#Add Controller dependencies on startup

1 messages · Page 1 of 1 (latest)

polar hill
#

I'd like to set up DI dynamically, based on information available at startup time (ie. not hardcoded) but it doesn't seem to work. Simplified example:

from litestar import Controller, Litestar, get
from litestar.di import Provide


class MyController(Controller):
    dependencies = {
        "my_dep": Provide(lambda: "default", sync_to_thread=False)
    }

    @classmethod
    def set_dependency(cls, dependency: str) -> None:
        cls.dependencies["my_dep"] = Provide(
            lambda: dependency, sync_to_thread=False
        )

    @get("/my_route")
    async def my_route(self, my_dep: str) -> str:
        return my_dep


def create_app() -> Litestar:
    return Litestar(
        route_handlers=[MyController],
        on_startup=[lambda: MyController.set_dependency("foo")],
    )

Calling /my_route returns "default" instead of "foo". Any ideas?

umbral relicBOT
#

lapis sky
#

I don't think that will work since we extract the dependencies from the controller when we initialize the application. You'd have to do this before you create the Litestar instance. I think a init plugin should work as well, but it may be tricky to set the dependency at the correct ownership layer (not too sure about this).

stable ferry
#

wouldn't that be possible with a plugin ?

#

oups I didnt read entirely your answer guacs 🙂

polar hill
#

I've never used plugins, do you have an example? For now my workaround is to extend the controller

    class MyNewController(MyController):
        dependencies = {"my_dep": Provide(lambda: "foo", sync_to_thread=False)}

    return Litestar(
        route_handlers=[MyNewController],
    )
stable ferry
polar hill
#

@lapis sky

You'd have to do this before you create the Litestar instance.
That's what i do, create_app is the top level function that creates the Litestar app. Still doesn't work.

stable ferry
#

out of curiosity, why this "dynamic" need, are you building some sort of meta-api ?

polar hill
#

No, it's (almost) like env (pydantic-)settings; i prefer to pass them as a dependency instead of globals or inject them in app.state

#

for example it could be an ML model that i want to (a) load on_startup and (b) make it available to the Controller

lapis sky
lapis sky
polar hill
polar hill
stable ferry
#

quick and dirty, would that be ok ?

#
from dataclasses import dataclass
from typing import Any, Callable

import uvicorn
from docs.examples.plugins.init_plugin_protocol import MyPlugin

from litestar import Controller, Litestar, get
from litestar.config.app import AppConfig
from litestar.di import Provide
from litestar.plugins import InitPluginProtocol

__all__ = ("MyController", "MyPlugin", "MyPluginConfig", "create_app", "dep_callable1", "dep_callable2", )


@dataclass
class MyPluginConfig:
    dependencies: list[dict[str, Callable[[], Any]]]


class MyPlugin(InitPluginProtocol):
    def __init__(self, config: MyPluginConfig):
        self.config = config

    def on_app_init(self, app_config: AppConfig) -> AppConfig:
        for d in self.config.dependencies:
            app_config.dependencies.update(d)
        app_config.route_handlers.append(MyController)
        return app_config


class MyController(Controller):
    @get("/my_route")
    async def my_route(self, dep1_key: str) -> str:
        return dep1_key


def dep_callable1() -> str:
    return "my_dep1"


async def dep_callable2() -> str:
    return "my_dep2"


def create_app() -> Litestar:
    plugin_config = MyPluginConfig(
        dependencies=[{"dep1_key": Provide(dep_callable1)}, {"dep2_key": Provide(dep_callable2)}]
    )

    return Litestar(plugins=[MyPlugin(plugin_config)])


if __name__ == "__main__":
    app = create_app()
    uvicorn.run(app)
lapis sky
lapis sky
polar hill
stable ferry
#

dirty in the sense I'm not even sure it does what you want in terms of dymanic