Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Injecting a list of dependencies #243

Closed
JarnoRFB opened this issue Mar 15, 2020 · 4 comments
Closed

Injecting a list of dependencies #243

JarnoRFB opened this issue Mar 15, 2020 · 4 comments
Assignees

Comments

@JarnoRFB
Copy link
Contributor

@JarnoRFB JarnoRFB commented Mar 15, 2020

Hi, thanks for the nice and well documented framework! There is one point I am currently missing. Let's say I have a class that depends on a list of other classes, I want to achieve something like the following example (written in Python 3.7)

from typing import *
from dependency_injector import containers, providers


class B:
    def __init__(self, val: int):
        self.val = val

    def __repr__(self):
        return f"B({self.val})"


class A:
    def __init__(self, bs: List[B]):
        self.bs = bs

    def __repr__(self):
        return f"A({self.bs})"


def main() -> None:
    class IocContainer(containers.DeclarativeContainer):
        bs = providers.Provider(
            list,
            providers.Factory(B, 1),
            providers.Factory(B, 2),

        )

        a = providers.Factory(
            A,
            bs,
        )

    container = IocContainer()

    print(container.a())


if __name__ == "__main__":
    main()

However, using a list of providers seems not to be possible, as the list constructor only takes a single argument, but directly instantiating a list of providers will not instantiate the providers.
What would be the way to go to inject a list of dependencies.

@mithrandir4859
Copy link

@mithrandir4859 mithrandir4859 commented Mar 15, 2020

@JarnoRFB
Funnily enough I was exploring this very issue at this very moment. So, I don't know how authors intended to handle such case but it is very easy to circumvent the apparent framework limitation - just use to_list function instead of list as the first argument to your factory.

def to_list(*vargs):
    return vargs

@JarnoRFB
Copy link
Contributor Author

@JarnoRFB JarnoRFB commented Mar 17, 2020

@yandrieiev thanks for the hint. One could also generalize this for other types that take iterables in their contructor.

from typing import *

from dependency_injector import containers, providers


class B:
    def __init__(self, val: int):
        self.val = val

    def __repr__(self):
        return f"B({self.val})"


class A:
    def __init__(self, bs: List[B]):
        self.bs = bs

    def __repr__(self):
        return f"A({self.bs})"


def collection_of(collection_type, *args):
    return collection_type(args)


def main() -> None:
    class IocContainer(containers.DeclarativeContainer):
        bs = providers.Factory(collection_of, list, providers.Factory(B, 1), providers.Factory(B, 2))

        a = providers.Factory(A, bs)

    container = IocContainer()

    print(container.a())


if __name__ == "__main__":
    main()

Probably would be a good idea to document this.

@rmk135
Copy link
Member

@rmk135 rmk135 commented Apr 27, 2020

Hello gentlemen,

I would adapt particular case to:

from typing import *

from dependency_injector import containers, providers


class B:
    def __init__(self, val: int):
        self.val = val

    def __repr__(self):
        return f"B({self.val})"


class A:
    def __init__(self, *bs: List[B]):
        self.bs = bs

    def __repr__(self):
        return f"A({self.bs})"


def main() -> None:
    class IocContainer(containers.DeclarativeContainer):

        a = providers.Factory(
            A,
            providers.Factory(B, 1),
            providers.Factory(B, 2),
        )

    container = IocContainer()

    print(container.a())


if __name__ == "__main__":
    main()

The solution is not identical to provided ones. There are two differences:

  1. It limits A from having other positional arguments.
  2. Context positional arguments will be appended (details at bullet #4 at http://python-dependency-injector.ets-labs.org/providers/factory.html#factory-providers-and-init-injections).

I like the workaround proposed by @yandrieiev and collection_of(...) solution looks accurate @JarnoRFB . This feature is a good release candidate. I will consider building it in.

Roman

@rmk135
Copy link
Member

@rmk135 rmk135 commented Jun 14, 2020

@JarnoRFB @yandrieiev

I have created a new provider - List. It can be used for injecting a list of values. It's as simple as this:

dispatcher_factory = providers.Factory(
    Dispatcher,
    modules=providers.List(
        providers.Factory(Module, name='m1'),
        providers.Factory(Module, name='m2'),
    ),
)

The original example from this issue now can look like this:

class IocContainer(containers.DeclarativeContainer):
    bs = providers.List(
        providers.Factory(B, 1),
        providers.Factory(B, 2),

    )

    a = providers.Factory(
        A,
        bs,
    )

List provider is available in version 3.16.0. Version 3.16.0 is published on PyPI. You're welcome to upgrade.

Roman

@rmk135 rmk135 closed this as completed Jun 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants