Container#

Container is an object you use to get your dependency.

Basic usage#

Container can be synchronous or asynchronous.

  • Async container can use any type of dependency sources: both sync and async are supported. Sync methods are called directly and no executors are used, so avoid network I/O in synchronous functions

  • Sync container can use only synchronous dependency sources.

To create a top level container you should call make_container (or make_async_container). Pass there one or more providers.

from dishka import make_container
container = make_container(provider)

And async version correspondingly:

from dishka import make_async_container

container = make_async_container(provider)

If you have not provided your own scopes enum, then default one will be used. Root container is attached to the first scope: Scope.APP by default.

To enter the next scope you should call container as a function and enter context manager:

with container() as nested_container:
    pass

or if you created async container:

async with container() as nested_container:
    pass

Container is needed for retrieving objects. To do it you need to call get(DependencyType) (and await it for async container). All retrieved dependencies are stored inside container of corresponding scope until you exit that scope. So, you if you call get multiple times you will receive the same instance. The rule is followed for indirect dependencies as well. Multiple dependencies of the same scope have their own cache.

container = make_container(provider)
a = container.get(A)
a = container.get(A)  # same instance

And async:

container = make_async_container(provider)
a = await container.get(A)
a = await container.get(A)  # same instance

When you exit the scope, dependency cache is cleared. Finalization of dependencies is done if you used generator factories.

APP-level container is not a context manager, so call .close() on your app termination

container.close()

And async:

await container.close()

Thread/task safety#

You can have multiple containers of the same scope simultaneously (except the top level one) - it is safe while you do not have dependencies of previous scope.

For example, if you have declared SessionPool as an APP-scoped dependency and then you concurrently enter REQUEST scope. Once you request SessionPool for the first time (directly or for another dependency) you cannot guarantee that only one instance of that object is created.

To prevent such a condition you need to protect any session whose children can be used concurrently: to pass lock_factory when creating a container. Do not mix up threading and asyncio locks: they are not interchangeable, use the proper one.

import threading

container = make_container(provider, lock_factory=threading.Lock):
with container(lock_factory=threading.Lock) as nested_container:
    ...
import asyncio

container = make_async_container(provider, lock_factory=asyncio.Lock)
async with container(lock_factory=asyncio.Lock) as nested_container:
    ...

Note

Do not worry, lock is set by default for top level (Scope.APP) container. So, if you are not using other scopes concurrently you do not need any changes. (E.g. if you are not using multiple Scope.ACTION containers at a same time within one Scope.REQUEST container)