Skip to content

Scopes

Scopes are one of the fundamental concepts in dependency injection. Some dependency injection frameworks provide fixes scopes, for example:

  • Singleton: only one instance is created
  • Request: in web frameworks, this could be the lifetime of a request
  • Prototype: re-initialized every time it is needed

di generalizes this concept by putting control of scopes into the hands of the users / implementers: a scope in di is identified by any hashable value (a string, enum, int, etc.) and entering / exiting scopes is handled via context managers:

async with container.enter_scope("app"):
    async with container.enter_scope("request"):
        async with container.enter_scope("foo, bar, baz!"):

Scopes provide a framework for several other important features:

  • Dependency lifespans
  • Dependency value sharing

Every dependency is linked to a scope. When a scope exits, all dependencies linked to it are destroyed (if they have cleanup, the cleanup is run) and their value is no longer available as a share value. This means that dependencies scoped to an outer scope cannot depend on dependencies scoped to an inner scope:

from di import Container, Dependant, Depends


class Request:
    ...


class DBConnection:
    def __init__(self, request: Request) -> None:
        ...


def controller(conn: DBConnection = Depends(scope="app")) -> None:
    ...


def framework() -> None:
    container = Container()
    with container.enter_scope("app"):
        with container.enter_scope("request"):
            request = Request()
            with container.bind(Dependant(lambda: request, scope="request"), Request):
                container.execute_sync(container.solve(Dependant(controller)))

This example will fail with di.exceptions.ScopeViolationError because an "app" scoped dependency (conn, as requested by controller via Depends(scope="app")) depends on a request scope dependency (in framework, we specify Dependant(..., scope="request"). This is because dependencies and scopes behave much a stack and references in general purpose langauges: you can't reference a function local once you exit that function. Even if we could hold onto the value once we exit the scope, that value could be a reference to an object that already had it's destructor run, for example a database connection that was closed.