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

How can a list element in a Configuration provider be injected? #274

Closed
clifflau1120 opened this issue Jul 31, 2020 · 6 comments
Closed
Assignees

Comments

@clifflau1120
Copy link

clifflau1120 commented Jul 31, 2020

Hello,

I have quite a nested configuration file that contains a lot of lists.
I would like to inject a specific element into a provider, which would look like:

config = providers.Configuration(defaults={'index': 0, 'items': list('a', 'b', 'c')})

foo= providers.Factory(Foo, item=config.items[config.index])

However, I was prompted:

TypeError: 'dependency_injector.providers.Configuration' object is not subscriptable

Anyone has idea about this? Thank you.

@rmk135
Copy link
Member

rmk135 commented Jul 31, 2020

Hi @clifflau1120 ,

Try to look at the ‘Selector’ provider. If I understood right what you’re going to build, it should help.

PS: I’m traveling and have limited access to the computer. I’ll work with that code in a day or two if Selector provider will not help.

@clifflau1120
Copy link
Author

clifflau1120 commented Jul 31, 2020

Hi @rmk135 ,

I think providers.Selector won't help, if you mean to use config.index as the actual switch:

config = providers.Configuration(defaults={'index': 0, 'items': list('a', 'b', 'c')})

foo= providers.Factory(Foo, item=providers.Selector(
    # 0 is not valid as an identifier of a keyword argument
    # Subscripting config.items would still prompt the error
    0=config.items[0], 
    # ...
))

Moreover, I actually have a dict instead of a list as config.items in my project, which makes the scenario even more complicated:

config = providers.Configuration(defaults={
    'target': 'A', 
    'items': {
        'A': {
            'option1': 60,
            'option2': 80,
        },
        'B': {
            'option1': 10,
            'option2': 20,
        },
    },
})

foo = providers.Factory(Foo, option1=config.items[config.target].option1)
bar = providers.Factory(Bar, option2=config.items[config.target].option2)

Btw, thanks for your quick response.

@rmk135
Copy link
Member

rmk135 commented Aug 1, 2020

There is no way to implement the config.items[config.target].option1 without major changes.

Configuration provider node does not know its parent. The current design is based on cascade updates that go from the top to the bottom. This situation creates an invariant that could be resolved only in the runtime.

I have created two spikes that you can use as a temporary solution.

Spike # 1. Inject the whole dictionary

import dataclasses
import operator

from dependency_injector import providers


@dataclasses.dataclass
class Foo:
    options: dict


config = providers.Configuration(default={
    'target': 'A',
    'items': {
        'A': {
            'option1': 60,
            'option2': 80,
        },
        'B': {
            'option1': 10,
            'option2': 20,
        },
    },
})


class ConfigSelector(providers.Callable):
    def __init__(self, selector, items):
        super().__init__(operator.getitem, items, selector)


foo = providers.Factory(
    Foo,
    options=ConfigSelector(
        config.target,
        config.items,
    ),
)


if __name__ == '__main__':
    config.target.from_env('TARGET')
    f = foo()
    print(f.options)

# $ TARGET=A python examples/config_itemselector.py
# {'option1': 60, 'option2': 80}
# $ TARGET=B python examples/config_itemselector.py
# {'option1': 10, 'option2': 20}

Spike # 2. Inject individual options.

import dataclasses

from dependency_injector import providers


@dataclasses.dataclass
class Foo:
    option1: object
    option2: object


config = providers.Configuration(default={
    'target': 'A',
    'items': {
        'A': {
            'option1': 60,
            'option2': 80,
        },
        'B': {
            'option1': 10,
            'option2': 20,
        },
    },
})


class ConfigOptionSelector(providers.Callable):

    def __init__(self, selector, items, option):
        super().__init__(
            lambda target, items, option: items[target][option],
            selector,
            items,
            option,
        )


foo = providers.Factory(
    Foo,
    option1=ConfigOptionSelector(
        config.target,
        config.items,
        'option1',
    ),
    option2=ConfigOptionSelector(
        config.target,
        config.items,
        'option2',
    ),
)


if __name__ == '__main__':
    config.target.from_env('TARGET')
    f = foo()
    print(f.option1, f.option2)


# $ TARGET=B python examples/config_itemselector_2.py
# 10 20
# $ TARGET=A python examples/config_itemselector_2.py
# 60 80

In both cases you will need to create a custom provider.

Does it work for you?

@rmk135
Copy link
Member

rmk135 commented Aug 3, 2020

@clifflau1120 ,

I didn't sleep well after was unable to implement providers.Factory(Foo, option1=config.items[config.target].option1). I would like Dependency Injector to handle this case. I also didn't like the internals of Configuration provider, so:

Feature is available in Dependency Injector 3.26.0.

@rmk135 rmk135 closed this as completed Aug 3, 2020
@clifflau1120
Copy link
Author

clifflau1120 commented Aug 4, 2020

Sorry I was off for few days.
Both the custom provider and new implementation of Configuration provider work very well on my use case.

@rmk135
Copy link
Member

rmk135 commented Aug 4, 2020

Ok, sounds good. Thank you.

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

2 participants