question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature request: allow specification of types of DependencyContainer() providers

See original GitHub issue

Containers have two types of parameters - dependencies, and dependency containers, which are collections of dependencies, satisfied with a container. They both can be instantiated in two ways: either on container creation (or afterwards with override_providers), or when a derived Container class overrides a dependency or dependency container.

In the latter case, currently at least, a DependenciesContainer has to be provided with explicit dependencies so that other providers binding to them have placeholders for their eventual targets:

class C(DeclarativeContainer):

    f = providers.DependenciesContainer(bar = providers.Dependency(instance_of=str))
    g = f.bar.provided 

For complex DependenciesContainers, this can be onerous, especially if the dependencies themselves container dependencies containers.

I suggest allowing a DependenciesContainer to take as an argument a Container, which specifies the base type of Container that will instantiate the DependenciesContainer:

Below is a snippet that constructs placeholders appropriate for a given incoming container (or even another dependencies container):


"""
Utilities for containers.
"""
from typing import (
    Any,
    Dict,
    Mapping,
    MutableMapping,
    Optional,
    Protocol,
    Type,
    Union,
)

from dependency_injector import containers, providers

ContainerSpec = Dict[str, providers.Provider]
ForwardContainerType = Union[
    Type[containers.DeclarativeContainer], ContainerSpec
]

#: type for DependenciesContainer possibly overridden by Container
FormalContainer = Union["DependenciesContainer", providers.Container]


class DependenciesContainer(providers.DependenciesContainer):
    """
    Dependencies container allowing spec of container to bind.
    """

    def __init__(
        self,
        subclass_of: Optional[ForwardContainerType] = None,
        **kw: Any
    ):
        if subclass_of is not None:
            if issubclass(subclass_of, containers.DeclarativeContainer):  # type: ignore
                spec: ContainerSpec = container_spec(subclass_of)  # type: ignore
            else:
                spec = subclass_of  # type: ignore
        else:
            spec = {}
        spec.update(kw)
        super().__init__(**spec)


class ContainerProtocol(Protocol):
    providers: Mapping[
        str,
        Union[providers.DependenciesContainer, providers.Dependency],
    ]


def container_spec(
    container: ContainerProtocol,
) -> ContainerSpec:
    """
    Create spec for container.

    Recurse over container and
    """

    spec: ContainerSpec = {}
    for name, provider in container.providers.items():
        if isinstance(
            provider,
            (providers.DependenciesContainer, providers.Dependency),
        ):
            spec[name] = provider
        else:
            spec[name] = providers.Dependency(instance_of=object)
    return spec


def resolve_part(c: Any, part: str) -> Any:
    """
    Resolve a part of a container.

    Helper to work around: https://github.com/ets-labs/python-dependency-injector/issues/354

    As `DependenciesContainer` can't be passed, have converted
    into a `Dependency`. However, that makes things "too lazy",
    as we have to use <container>.provided.<part>.provided, and
    consumer must do <container-instance>()().

    Instead, we take first provided "lazy" and resolve last greedily
    """
    return providers.Callable(
        lambda c: getattr(c, part)(), c.provided
    ).provided

It doesn’t check the type of a passed in container on instantiation – that is nice to have, but not as essential for me.

It can be used so:


from .container_util import DependenciesContainer, resolve_part

class C1(containers.DeclarativeContainer):
    foo1 = Dependency(instance_of=str)
    foo2 = Dependency(instance_of=str)

class C2(containers.DeclarativeContainer):
    c1 = DependenciesContainer(C1)

class C3(containers.DeclarativeContainer):
    c2 = DependenciesContainer(C2)
    foo1 = resolve_part(resolve_part(c2, "c1"), "foo1")
    foo2 = resolve_part(resolve_part(c2, "c1"), "foo2")

c3 = C3(c2 = C2(c1 = C1(foo1 = "hello", foo2="world")))

print(c3.foo1(), c3.foo2())  # prints "hello world"

Also, this should work (or something similar):

...
@containers.copy(C3)
class C4(C3):
    selected_c1 = providers.Container(C1, foo1 = "hello", foo2="world")
    c2 = DependenciesContainer(C2)
    c2.override(providers.Callable(C2, c1=selected_c1))

c4 = C4()
print(c4.foo1(), c4.foo2())  # prints "hello world"

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:11 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
rmk135commented, Jan 15, 2021

This example now works with 4.9.0:

from dependency_injector import containers, providers


class C1(containers.DeclarativeContainer):
    foo1 = providers.Dependency(instance_of=str)
    foo2 = providers.Dependency(instance_of=str)


class C2(containers.DeclarativeContainer):
    c1 = providers.DependenciesContainer(**C1.dependencies)


class C3(containers.DeclarativeContainer):
    c2 = providers.DependenciesContainer(**C2.dependencies)
    foo1 = c2.c1.foo1
    foo2 = c2.c1.foo2


if __name__ == '__main__':
    c3 = C3(c2=C2(c1=C1(foo1="hello", foo2="world")))

    print(c3.foo1(), c3.foo2())  # prints "hello world"
0reactions
rmk135commented, Jan 18, 2021

Good. Thanks for the update.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Design Patterns Explained – Dependency Injection with Code ...
Dependency injection is a programming technique that makes a class independent of its dependencies. It achieves that by decoupling the usage ...
Read more >
Introduction to Contexts and Dependency Injection - Quarkus
In this guide we're going to describe the basic principles of the Quarkus programming model that is based on the Contexts and Dependency...
Read more >
Chapter 3. Dependency Injection - JBoss.org
Dependency injection (DI) allows for cleaner and more modular code, by permitting the implementation of decoupled and type-safe components.
Read more >
CDI Part 1: Contexts and Dependency Injection for enterprise ...
Other features of the specification include cross-cutting functionality provided by ... Type-safe dependency injection; Scopes and contexts ...
Read more >
Configuring dependency providers - Angular
The Creating and injecting services topic describes how to use classes as dependencies. Besides classes, you can also use other values such as...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found