ets-labs / python-dependency-injector

Dependency injection framework for Python
https://python-dependency-injector.ets-labs.org/
BSD 3-Clause "New" or "Revised" License
3.91k stars 305 forks source link

Eager provider/container initialization #678

Open cpvandehey opened 1 year ago

cpvandehey commented 1 year ago

Im curious if there is a reason that "lazily" loading dependencies is the only way to load any kind of provider (with the exception of Resources)

I think it makes sense to fail fast with regards to dependencies. For example: If there is a connection issue in a dependency that makes a web request on creation, i'd rather my app fail immediately than N amount of time later when that dependency is used.

Im writing this issue to see if other folks have written workarounds to eagerly initialize all of a projects dependency at Container creation time. Or maybe there is a good reason to avoid this. Its certainly standard to eagerly load dependencies in Java Spring.

There is a traversal method that is available that could make the manual way doing this a little easier.

kamilglod commented 2 months ago

Other use case might be to e.g. subscribe to the domain events when starting container, something similar to https://www.baeldung.com/guice#2-eager-singleton. I guess it might be achieved by using that traversal method within some inherited Container implementation that creates all resources of custom type like EagerSingleton():

class EagerSingleton(Singleton):
  pass

class MyContainer(Container):
  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    for resource in self.traverse(types=[EagerSingleton]):
      resource()

didn't test it so it's more like a pseudo-code but might be useful for someone. In my projects I always have single function to create container instance so I can easily do something similar iniside of that function but this solution ^^ is more general.

EDIT: I tried to implement this but failed since DeclarativeContainer have custom metaclass that creates Container cls and even if I was able to override __init__() I didn't have an access to self.providers and self.traverse didn't work. And even if I will be able to make it work I still have a problem (this issue) that my EagerSingletons are async and I need to await on them to setup them. I ended up with code like:

from inspect import isawaitable

from dependency_injector.containers import Container
from dependency_injector.providers import Singleton

class EagerSingleton(Singleton):
    pass

async def load_eager_singletons(container: Container):
    for resource in container.traverse(types=[EagerSingleton]):
        singleton = resource()
        if isawaitable(singleton):
            await singleton

And then in the container:

class Container(containers.DeclarativeContainer):
    foo_service = EagerSingleton(
        FooService,
        dep1=dep1,
        dep2=dep2,
        ...
    )

@asynccontextmanager
async def init_container(container: Container):
    logger.info("Initiating container")
    await load_eager_singletons(container)

    try:
        yield container
    finally:
        shutdown_fut = container.shutdown_resources()
        if isawaitable(shutdown_fut):
            await shutdown_fut
        logger.info("Container shutdown")