Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c26b260

Browse files
committedJan 31, 2022
Merge branch 'release/4.38.0' into master
2 parents 541131e + ad0d430 commit c26b260

40 files changed

+18105
-14970
lines changed
 

‎README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ Key features of the ``Dependency Injector``:
9090
api_client = providers.Singleton(
9191
ApiClient,
9292
api_key=config.api_key,
93-
timeout=config.timeout.as_int(),
93+
timeout=config.timeout,
9494
)
9595
9696
service = providers.Factory(
@@ -106,8 +106,8 @@ Key features of the ``Dependency Injector``:
106106
107107
if __name__ == "__main__":
108108
container = Container()
109-
container.config.api_key.from_env("API_KEY")
110-
container.config.timeout.from_env("TIMEOUT")
109+
container.config.api_key.from_env("API_KEY", required=True)
110+
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
111111
container.wire(modules=[__name__])
112112
113113
main() # <-- dependency is injected automatically

‎docs/_static/logo.svg

Lines changed: 1 addition & 1 deletion
Loading

‎docs/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Key features of the ``Dependency Injector``:
9696
api_client = providers.Singleton(
9797
ApiClient,
9898
api_key=config.api_key,
99-
timeout=config.timeout.as_int(),
99+
timeout=config.timeout,
100100
)
101101
102102
service = providers.Factory(
@@ -112,8 +112,8 @@ Key features of the ``Dependency Injector``:
112112
113113
if __name__ == "__main__":
114114
container = Container()
115-
container.config.api_key.from_env("API_KEY")
116-
container.config.timeout.from_env("TIMEOUT")
115+
container.config.api_key.from_env("API_KEY", required=True)
116+
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
117117
container.wire(modules=[__name__])
118118
119119
main() # <-- dependency is injected automatically

‎docs/introduction/di_in_python.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ What does the Dependency Injector do?
150150
-------------------------------------
151151

152152
With the dependency injection pattern objects loose the responsibility of assembling
153-
the dependencies. The ``Dependency Injector`` absorbs that responsibilities.
153+
the dependencies. The ``Dependency Injector`` absorbs that responsibility.
154154

155155
``Dependency Injector`` helps to assemble and inject the dependencies.
156156

@@ -172,7 +172,7 @@ the dependency.
172172
api_client = providers.Singleton(
173173
ApiClient,
174174
api_key=config.api_key,
175-
timeout=config.timeout.as_int(),
175+
timeout=config.timeout,
176176
)
177177
178178
service = providers.Factory(
@@ -188,8 +188,8 @@ the dependency.
188188
189189
if __name__ == "__main__":
190190
container = Container()
191-
container.config.api_key.from_env("API_KEY")
192-
container.config.timeout.from_env("TIMEOUT")
191+
container.config.api_key.from_env("API_KEY", required=True)
192+
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
193193
container.wire(modules=[__name__])
194194
195195
main() # <-- dependency is injected automatically

‎docs/main/changelog.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@ that were made in every particular version.
77
From version 0.7.6 *Dependency Injector* framework strictly
88
follows `Semantic versioning`_
99

10+
4.38.0
11+
------
12+
- Add new provider ``Aggregate``. It is a generalized version of ``FactoryAggregate`` that
13+
can contain providers of any type, not only ``Factory``. See issue
14+
`#530 <https://github.com/ets-labs/python-dependency-injector/issues/530>`_. Thanks to
15+
`@zerlok (Danil Troshnev) <https://github.com/zerlok>`_ for suggesting the feature.
16+
- Add argument ``as_`` to the ``config.from_env()`` method for the explicit type casting
17+
of an environment variable value, e.g.: ``config.timeout.from_env("TIMEOUT", as_=int)``.
18+
See issue `#533 <https://github.com/ets-labs/python-dependency-injector/issues/533>`_. Thanks to
19+
`@gtors (Andrey Torsunov) <https://github.com/gtors>`_ for suggesting the feature.
20+
- Add ``.providers`` attribute to the ``FactoryAggregate`` provider. It is an alias for
21+
``FactoryAggregate.factories`` attribute.
22+
- Add ``.set_providers()`` method to the ``FactoryAggregate`` provider. It is an alias for
23+
``FactoryAggregate.set_factories()`` method.
24+
- Add string imports for ``Factory``, ``Singleton``, ``Callable``, ``Resource``, and ``Coroutine``
25+
providers, e.g. ``Factory("module.Class")``.
26+
See issue `#531 <https://github.com/ets-labs/python-dependency-injector/issues/531>`_.
27+
Thanks to `@al-stefanitsky-mozdor <https://github.com/al-stefanitsky-mozdor>`_ for suggesting the feature.
28+
- Fix ``Dependency`` provider to don't raise "Dependency is not defined" error when the ``default``
29+
is a falsy value of proper type.
30+
See issue `#550 <https://github.com/ets-labs/python-dependency-injector/issues/550>`_. Thanks to
31+
`@approxit <https://github.com/approxit>`_ for reporting the issue.
32+
- Refactor ``FactoryAggregate`` provider internals.
33+
- Update logo on Github and in docs to support dark themes and remove some imperfections.
34+
1035
4.37.0
1136
------
1237
- Add support of Python 3.10.

‎docs/providers/aggregate.rst

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
.. _aggregate-provider:
2+
3+
Aggregate provider
4+
==================
5+
6+
.. meta::
7+
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
8+
Aggregate,Polymorphism,Environment Variable,Flexibility
9+
:description: Aggregate provider aggregates other providers.
10+
This page demonstrates how to implement the polymorphism and increase the
11+
flexibility of your application using the Aggregate provider.
12+
13+
:py:class:`Aggregate` provider aggregates a group of other providers.
14+
15+
.. currentmodule:: dependency_injector.providers
16+
17+
.. literalinclude:: ../../examples/providers/aggregate.py
18+
:language: python
19+
:lines: 3-
20+
:emphasize-lines: 24-27
21+
22+
Each provider in the ``Aggregate`` is associated with a key. You can call aggregated providers by providing
23+
their key as a first argument. All positional and keyword arguments following the key will be forwarded to
24+
the called provider:
25+
26+
.. code-block:: python
27+
28+
yaml_reader = container.config_readers("yaml", "./config.yml", foo=...)
29+
30+
You can also retrieve an aggregated provider by providing its key as an attribute name:
31+
32+
.. code-block:: python
33+
34+
yaml_reader = container.config_readers.yaml("./config.yml", foo=...)
35+
36+
To retrieve a dictionary of aggregated providers, use ``.providers`` attribute:
37+
38+
.. code-block:: python
39+
40+
container.config_readers.providers == {
41+
"yaml": <YAML provider>,
42+
"json": <JSON provider>,
43+
}
44+
45+
.. note::
46+
You can not override the ``Aggregate`` provider.
47+
48+
.. note::
49+
When you inject the ``Aggregate`` provider, it is passed "as is".
50+
51+
To use non-string keys or string keys with ``.`` and ``-``, provide a dictionary as a positional argument:
52+
53+
.. code-block:: python
54+
55+
aggregate = providers.Aggregate({
56+
SomeClass: providers.Factory(...),
57+
"key.with.periods": providers.Factory(...),
58+
"key-with-dashes": providers.Factory(...),
59+
})
60+
61+
.. seealso::
62+
:ref:`selector-provider` to make injections based on a configuration value, environment variable, or a result of a callable.
63+
64+
``Aggregate`` provider is different from the :ref:`selector-provider`. ``Aggregate`` provider doesn't select which provider
65+
to inject and doesn't have a selector. It is a group of providers and is always injected "as is". The rest of the interface
66+
of both providers is similar.
67+
68+
.. note::
69+
``Aggregate`` provider is a successor of :ref:`factory-aggregate-provider` provider. ``Aggregate`` provider doesn't have
70+
a restriction on the provider type, while ``FactoryAggregate`` aggregates only ``Factory`` providers.
71+
72+
.. disqus::

‎docs/providers/configuration.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,24 @@ Loading from an environment variable
205205
:lines: 3-
206206
:emphasize-lines: 18-20
207207

208+
You can use ``as_`` argument for the type casting of an environment variable value:
209+
210+
.. code-block:: python
211+
:emphasize-lines: 2,6,10
212+
213+
# API_KEY=secret
214+
container.config.api_key.from_env("API_KEY", as_=str, required=True)
215+
assert container.config.api_key() == "secret"
216+
217+
# SAMPLING_RATIO=0.5
218+
container.config.sampling.from_env("SAMPLING_RATIO", as_=float, required=True)
219+
assert container.config.sampling() == 0.5
220+
221+
# TIMEOUT undefined, default is used
222+
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
223+
assert container.config.timeout() == 5
224+
225+
208226
Loading a value
209227
---------------
210228

‎docs/providers/factory.rst

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,45 @@ attribute of the provider that you're going to inject.
110110

111111
.. note:: Any provider has a ``.provider`` attribute.
112112

113+
.. _factory-string-imports:
114+
115+
String imports
116+
--------------
117+
118+
``Factory`` provider can handle string imports:
119+
120+
.. code-block:: python
121+
122+
class Container(containers.DeclarativeContainer):
123+
124+
service = providers.Factory("myapp.mypackage.mymodule.Service")
125+
126+
You can also make a relative import:
127+
128+
.. code-block:: python
129+
130+
# in myapp/container.py
131+
132+
class Container(containers.DeclarativeContainer):
133+
134+
service = providers.Factory(".mypackage.mymodule.Service")
135+
136+
or import a member of the current module just specifying its name:
137+
138+
.. code-block:: python
139+
140+
class Service:
141+
...
142+
143+
144+
class Container(containers.DeclarativeContainer):
145+
146+
service = providers.Factory("Service")
147+
148+
.. note::
149+
``Singleton``, ``Callable``, ``Resource``, and ``Coroutine`` providers handle string imports
150+
the same way as a ``Factory`` provider.
151+
113152
.. _factory-specialize-provided-type:
114153

115154
Specializing the provided type
@@ -145,11 +184,17 @@ provider with two peculiarities:
145184
:lines: 3-
146185
:emphasize-lines: 34
147186

187+
.. _factory-aggregate-provider:
188+
148189
Factory aggregate
149190
-----------------
150191

151192
:py:class:`FactoryAggregate` provider aggregates multiple factories.
152193

194+
.. seealso::
195+
:ref:`aggregate-provider` – it's a successor of ``FactoryAggregate`` provider that can aggregate
196+
any type of provider, not only ``Factory``.
197+
153198
The aggregated factories are associated with the string keys. When you call the
154199
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
155200
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
@@ -163,9 +208,9 @@ The aggregated factories are associated with the string keys. When you call the
163208
:lines: 3-
164209
:emphasize-lines: 33-37,47
165210

166-
You can get a dictionary of the aggregated factories using the ``.factories`` attribute.
167-
To get a game factories dictionary from the previous example you can use
168-
``game_factory.factories`` attribute.
211+
You can get a dictionary of the aggregated providers using ``.providers`` attribute.
212+
To get a game provider dictionary from the previous example you can use
213+
``game_factory.providers`` attribute.
169214

170215
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the
171216
previous example you can do ``chess = game_factory.chess("John", "Jane")``.
@@ -176,7 +221,7 @@ previous example you can do ``chess = game_factory.chess("John", "Jane")``.
176221
.. note::
177222
When you inject the ``FactoryAggregate`` provider it is passed "as is".
178223

179-
To use non-string keys or keys with ``.`` and ``-`` you can provide a dictionary as a positional argument:
224+
To use non-string keys or string keys with ``.`` and ``-``, you can provide a dictionary as a positional argument:
180225

181226
.. code-block:: python
182227

‎docs/providers/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
4646
dict
4747
configuration
4848
resource
49+
aggregate
4950
selector
5051
dependency
5152
overriding

‎docs/providers/selector.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,7 @@ When a ``Selector`` provider is called, it gets a ``selector`` value and delegat
3030
the provider with a matching name. The ``selector`` callable works as a switch: when the returned
3131
value is changed the ``Selector`` provider will delegate the work to another provider.
3232

33+
.. seealso::
34+
:ref:`aggregate-provider` to inject a group of providers.
35+
3336
.. disqus::

‎examples/demo/with_di.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Container(containers.DeclarativeContainer):
1313
api_client = providers.Singleton(
1414
ApiClient,
1515
api_key=config.api_key,
16-
timeout=config.timeout.as_int(),
16+
timeout=config.timeout,
1717
)
1818

1919
service = providers.Factory(
@@ -29,8 +29,8 @@ def main(service: Service = Provide[Container.service]):
2929

3030
if __name__ == "__main__":
3131
container = Container()
32-
container.config.api_key.from_env("API_KEY")
33-
container.config.timeout.from_env("TIMEOUT")
32+
container.config.api_key.from_env("API_KEY", required=True)
33+
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
3434
container.wire(modules=[__name__])
3535

3636
main() # <-- dependency is injected automatically

‎examples/providers/aggregate.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""`Aggregate` provider example."""
2+
3+
from dependency_injector import containers, providers
4+
5+
6+
class ConfigReader:
7+
8+
def __init__(self, path):
9+
self._path = path
10+
11+
def read(self):
12+
print(f"Parsing {self._path} with {self.__class__.__name__}")
13+
...
14+
15+
16+
class YamlReader(ConfigReader):
17+
...
18+
19+
20+
class JsonReader(ConfigReader):
21+
...
22+
23+
24+
class Container(containers.DeclarativeContainer):
25+
26+
config_readers = providers.Aggregate(
27+
yaml=providers.Factory(YamlReader),
28+
json=providers.Factory(JsonReader),
29+
)
30+
31+
32+
if __name__ == "__main__":
33+
container = Container()
34+
35+
yaml_reader = container.config_readers("yaml", "./config.yml")
36+
yaml_reader.read() # Parsing ./config.yml with YamlReader
37+
38+
json_reader = container.config_readers("json", "./config.json")
39+
json_reader.read() # Parsing ./config.json with JsonReader

‎src/dependency_injector/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Top-level package."""
22

3-
__version__ = "4.37.0"
3+
__version__ = "4.38.0"
44
"""Version number.
55
66
:type: str

‎src/dependency_injector/containers.c

Lines changed: 898 additions & 867 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/dependency_injector/containers.pyi

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ from typing import (
2020
from .providers import Provider, Self, ProviderParent
2121

2222

23-
C_Base = TypeVar('C_Base', bound='Container')
24-
C = TypeVar('C', bound='DeclarativeContainer')
25-
C_Overriding = TypeVar('C_Overriding', bound='DeclarativeContainer')
26-
T = TypeVar('T')
27-
TT = TypeVar('TT')
23+
C_Base = TypeVar("C_Base", bound="Container")
24+
C = TypeVar("C", bound="DeclarativeContainer")
25+
C_Overriding = TypeVar("C_Overriding", bound="DeclarativeContainer")
26+
T = TypeVar("T")
27+
TT = TypeVar("TT")
2828

2929

3030
class WiringConfiguration:

‎src/dependency_injector/containers.pyx

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ if sys.version_info[:2] >= (3, 6):
2828
from .wiring import wire, unwire
2929
else:
3030
def wire(*args, **kwargs):
31-
raise NotImplementedError('Wiring requires Python 3.6 or above')
31+
raise NotImplementedError("Wiring requires Python 3.6 or above")
3232

3333
def unwire(*args, **kwargs):
34-
raise NotImplementedError('Wiring requires Python 3.6 or above')
34+
raise NotImplementedError("Wiring requires Python 3.6 or above")
3535

3636
if sys.version_info[:2] == (3, 5):
3737
warnings.warn(
@@ -137,17 +137,17 @@ class DynamicContainer(Container):
137137
If value of attribute is provider, it will be added into providers
138138
dictionary.
139139
140-
:param name: Attribute's name
140+
:param name: Attribute name
141141
:type name: object
142142
143-
:param value: Attribute's value
143+
:param value: Attribute value
144144
:type value: object
145145
146146
:rtype: None
147147
"""
148148
if isinstance(value, providers.Provider) \
149149
and not isinstance(value, providers.Self) \
150-
and name != 'parent':
150+
and name != "parent":
151151
_check_provider_type(self, value)
152152

153153
self.providers[name] = value
@@ -163,7 +163,7 @@ class DynamicContainer(Container):
163163
If value of attribute is provider, it will be deleted from providers
164164
dictionary.
165165
166-
:param name: Attribute's name
166+
:param name: Attribute name
167167
:type name: object
168168
169169
:rtype: None
@@ -229,8 +229,8 @@ class DynamicContainer(Container):
229229
:rtype: None
230230
"""
231231
if overriding is self:
232-
raise errors.Error('Container {0} could not be overridden '
233-
'with itself'.format(self))
232+
raise errors.Error("Container {0} could not be overridden "
233+
"with itself".format(self))
234234

235235
self.overridden += (overriding,)
236236

@@ -262,7 +262,7 @@ class DynamicContainer(Container):
262262
:rtype: None
263263
"""
264264
if not self.overridden:
265-
raise errors.Error('Container {0} is not overridden'.format(self))
265+
raise errors.Error("Container {0} is not overridden".format(self))
266266

267267
self.overridden = self.overridden[:-1]
268268

@@ -364,7 +364,7 @@ class DynamicContainer(Container):
364364
while any(resource.initialized for resource in resources):
365365
resources_to_shutdown = list(_independent_resources(resources))
366366
if not resources_to_shutdown:
367-
raise RuntimeError('Unable to resolve resources shutdown order')
367+
raise RuntimeError("Unable to resolve resources shutdown order")
368368
futures = []
369369
for resource in resources_to_shutdown:
370370
result = resource.shutdown()
@@ -376,7 +376,7 @@ class DynamicContainer(Container):
376376
while any(resource.initialized for resource in resources):
377377
resources_to_shutdown = list(_independent_resources(resources))
378378
if not resources_to_shutdown:
379-
raise RuntimeError('Unable to resolve resources shutdown order')
379+
raise RuntimeError("Unable to resolve resources shutdown order")
380380
for resource in resources_to_shutdown:
381381
resource.shutdown()
382382

@@ -393,7 +393,7 @@ class DynamicContainer(Container):
393393
config.load()
394394

395395
def apply_container_providers_overridings(self):
396-
"""Apply container providers' overridings."""
396+
"""Apply container providers overridings."""
397397
for provider in self.traverse(types=[providers.Container]):
398398
provider.apply_overridings()
399399

@@ -419,12 +419,12 @@ class DynamicContainer(Container):
419419

420420
container_name = self.parent_name if self.parent_name else self.__class__.__name__
421421
undefined_names = [
422-
f'"{dependency.parent_name if dependency.parent_name else dependency}"'
422+
f"\"{dependency.parent_name if dependency.parent_name else dependency}\""
423423
for dependency in undefined
424424
]
425425
raise errors.Error(
426-
f'Container "{container_name}" has undefined dependencies: '
427-
f'{", ".join(undefined_names)}',
426+
f"Container \"{container_name}\" has undefined dependencies: "
427+
f"{', '.join(undefined_names)}",
428428
)
429429

430430
def from_schema(self, schema):
@@ -441,9 +441,9 @@ class DynamicContainer(Container):
441441
"""
442442
if yaml is None:
443443
raise errors.Error(
444-
'Unable to load yaml schema - PyYAML is not installed. '
445-
'Install PyYAML or install Dependency Injector with yaml extras: '
446-
'"pip install dependency-injector[yaml]"'
444+
"Unable to load yaml schema - PyYAML is not installed. "
445+
"Install PyYAML or install Dependency Injector with yaml extras: "
446+
"\"pip install dependency-injector[yaml]\""
447447
)
448448

449449
if loader is None:
@@ -466,7 +466,7 @@ class DynamicContainer(Container):
466466
if container_provider is provider:
467467
return provider_name
468468
else:
469-
raise errors.Error(f'Can not resolve name for provider "{provider}"')
469+
raise errors.Error(f"Can not resolve name for provider \"{provider}\"")
470470

471471
@property
472472
def parent_name(self):
@@ -525,11 +525,11 @@ class DeclarativeContainerMetaClass(type):
525525
"instead got {0}".format(wiring_config)
526526
)
527527

528-
attributes['containers'] = containers
529-
attributes['inherited_providers'] = inherited_providers
530-
attributes['cls_providers'] = cls_providers
531-
attributes['providers'] = all_providers
532-
attributes['wiring_config'] = wiring_config
528+
attributes["containers"] = containers
529+
attributes["inherited_providers"] = inherited_providers
530+
attributes["cls_providers"] = cls_providers
531+
attributes["providers"] = all_providers
532+
attributes["wiring_config"] = wiring_config
533533

534534
cls = <type>type.__new__(mcs, class_name, bases, attributes)
535535

@@ -551,15 +551,15 @@ class DeclarativeContainerMetaClass(type):
551551
If value of attribute is provider, it will be added into providers
552552
dictionary.
553553
554-
:param name: Attribute's name
554+
:param name: Attribute name
555555
:type name: object
556556
557-
:param value: Attribute's value
557+
:param value: Attribute value
558558
:type value: object
559559
560560
:rtype: None
561561
"""
562-
if isinstance(value, providers.Provider) and name != '__self__':
562+
if isinstance(value, providers.Provider) and name != "__self__":
563563
_check_provider_type(cls, value)
564564

565565
if isinstance(value, providers.CHILD_PROVIDERS):
@@ -575,7 +575,7 @@ class DeclarativeContainerMetaClass(type):
575575
If value of attribute is provider, it will be deleted from providers
576576
dictionary.
577577
578-
:param name: Attribute's name
578+
:param name: Attribute name
579579
:type name: object
580580
581581
:rtype: None
@@ -611,7 +611,7 @@ class DeclarativeContainerMetaClass(type):
611611
if container_provider is provider:
612612
return provider_name
613613
else:
614-
raise errors.Error(f'Can not resolve name for provider "{provider}"')
614+
raise errors.Error(f"Can not resolve name for provider \"{provider}\"")
615615

616616
@property
617617
def parent_name(cls):
@@ -628,9 +628,9 @@ class DeclarativeContainerMetaClass(type):
628628
continue
629629

630630
if self is not None and value is not self:
631-
raise errors.Error('Container can have only one "Self" provider')
631+
raise errors.Error("Container can have only one \"Self\" provider")
632632

633-
if name != '__self__':
633+
if name != "__self__":
634634
alt_names.append(name)
635635

636636
self = value
@@ -727,8 +727,8 @@ class DeclarativeContainer(Container):
727727
container.wiring_config = copy_module.deepcopy(cls.wiring_config)
728728
container.declarative_parent = cls
729729

730-
copied_providers = providers.deepcopy({ **cls.providers, **{'@@self@@': cls.__self__}})
731-
copied_self = copied_providers.pop('@@self@@')
730+
copied_providers = providers.deepcopy({ **cls.providers, **{"@@self@@": cls.__self__}})
731+
copied_self = copied_providers.pop("@@self@@")
732732
copied_self.set_container(container)
733733

734734
container.__self__ = copied_self
@@ -762,8 +762,8 @@ class DeclarativeContainer(Container):
762762
:rtype: None
763763
"""
764764
if issubclass(cls, overriding):
765-
raise errors.Error('Container {0} could not be overridden '
766-
'with itself or its subclasses'.format(cls))
765+
raise errors.Error("Container {0} could not be overridden "
766+
"with itself or its subclasses".format(cls))
767767

768768
cls.overridden += (overriding,)
769769

@@ -780,7 +780,7 @@ class DeclarativeContainer(Container):
780780
:rtype: None
781781
"""
782782
if not cls.overridden:
783-
raise errors.Error('Container {0} is not overridden'.format(cls))
783+
raise errors.Error("Container {0} is not overridden".format(cls))
784784

785785
cls.overridden = cls.overridden[:-1]
786786

@@ -833,7 +833,7 @@ def override(object container):
833833
container.
834834
:type container: :py:class:`DeclarativeContainer`
835835
836-
:return: Declarative container's overriding decorator.
836+
:return: Declarative container overriding decorator.
837837
:rtype: callable(:py:class:`DeclarativeContainer`)
838838
"""
839839
def _decorator(object overriding_container):
@@ -853,7 +853,7 @@ def copy(object base_container):
853853
:param base_container: Container that should be copied by decorated container.
854854
:type base_container: :py:class:`DeclarativeContainer`
855855
856-
:return: Declarative container's copying decorator.
856+
:return: Declarative container copying decorator.
857857
:rtype: callable(:py:class:`DeclarativeContainer`)
858858
"""
859859
def _get_memo_for_matching_names(new_providers, base_providers):
@@ -864,7 +864,7 @@ def copy(object base_container):
864864
source_provider = base_providers[new_provider_name]
865865
memo[id(source_provider)] = new_provider
866866

867-
if hasattr(new_provider, 'providers') and hasattr(source_provider, 'providers'):
867+
if hasattr(new_provider, "providers") and hasattr(source_provider, "providers"):
868868
sub_memo = _get_memo_for_matching_names(new_provider.providers, source_provider.providers)
869869
memo.update(sub_memo)
870870
return memo
@@ -892,13 +892,13 @@ cpdef bint is_container(object instance):
892892
893893
:rtype: bool
894894
"""
895-
return getattr(instance, '__IS_CONTAINER__', False) is True
895+
return getattr(instance, "__IS_CONTAINER__", False) is True
896896

897897

898898
cpdef object _check_provider_type(object container, object provider):
899899
if not isinstance(provider, container.provider_type):
900-
raise errors.Error('{0} can contain only {1} '
901-
'instances'.format(container, container.provider_type))
900+
raise errors.Error("{0} can contain only {1} "
901+
"instances".format(container, container.provider_type))
902902

903903

904904
cpdef bint _any_relative_string_imports_in(object modules):

‎src/dependency_injector/providers.c

Lines changed: 15888 additions & 13619 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/dependency_injector/providers.pxd

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ cdef class Delegate(Provider):
3838
cpdef object _provide(self, tuple args, dict kwargs)
3939

4040

41+
cdef class Aggregate(Provider):
42+
cdef dict __providers
43+
44+
cdef Provider __get_provider(self, object provider_name)
45+
46+
4147
cdef class Dependency(Provider):
4248
cdef object __instance_of
4349
cdef object __default
@@ -142,10 +148,8 @@ cdef class FactoryDelegate(Delegate):
142148
pass
143149

144150

145-
cdef class FactoryAggregate(Provider):
146-
cdef dict __factories
147-
148-
cdef Factory __get_factory(self, object factory_name)
151+
cdef class FactoryAggregate(Aggregate):
152+
pass
149153

150154

151155
# Singleton providers
@@ -359,11 +363,11 @@ cdef inline tuple __separate_prefixed_kwargs(dict kwargs):
359363
cdef dict prefixed_kwargs = {}
360364

361365
for key, value in kwargs.items():
362-
if '__' not in key:
366+
if "__" not in key:
363367
plain_kwargs[key] = value
364368
continue
365369

366-
index = key.index('__')
370+
index = key.index("__")
367371
prefix, name = key[:index], key[index+2:]
368372

369373
if prefix not in prefixed_kwargs:

‎src/dependency_injector/providers.pyi

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ from . import resources
3535

3636

3737
Injection = Any
38-
ProviderParent = Union['Provider', Any]
39-
T = TypeVar('T')
40-
TT = TypeVar('TT')
41-
P = TypeVar('P', bound='Provider')
42-
BS = TypeVar('BS', bound='BaseSingleton')
38+
ProviderParent = Union["Provider", Any]
39+
T = TypeVar("T")
40+
TT = TypeVar("TT")
41+
P = TypeVar("P", bound="Provider")
42+
BS = TypeVar("BS", bound="BaseSingleton")
4343

4444

4545
class Provider(Generic[T]):
@@ -104,6 +104,21 @@ class Delegate(Provider[Provider]):
104104
def set_provides(self, provides: Optional[Provider]) -> Delegate: ...
105105

106106

107+
class Aggregate(Provider[T]):
108+
def __init__(self, provider_dict: Optional[_Dict[Any, Provider[T]]] = None, **provider_kwargs: Provider[T]): ...
109+
def __getattr__(self, provider_name: Any) -> Provider[T]: ...
110+
111+
@overload
112+
def __call__(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> T: ...
113+
@overload
114+
def __call__(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
115+
def async_(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
116+
117+
@property
118+
def providers(self) -> _Dict[Any, Provider[T]]: ...
119+
def set_providers(self, provider_dict: Optional[_Dict[Any, Provider[T]]] = None, **provider_kwargs: Provider[T]) -> Aggregate[T]: ...
120+
121+
107122
class Dependency(Provider[T]):
108123
def __init__(self, instance_of: Type[T] = object, default: Optional[Union[Provider, Any]] = None) -> None: ...
109124
def __getattr__(self, name: str) -> Any: ...
@@ -143,10 +158,10 @@ class DependenciesContainer(Object):
143158

144159

145160
class Callable(Provider[T]):
146-
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
161+
def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
147162
@property
148163
def provides(self) -> Optional[_Callable[..., T]]: ...
149-
def set_provides(self, provides: Optional[_Callable[..., T]]) -> Callable[T]: ...
164+
def set_provides(self, provides: Optional[Union[_Callable[..., T], str]]) -> Callable[T]: ...
150165
@property
151166
def args(self) -> Tuple[Injection]: ...
152167
def add_args(self, *args: Injection) -> Callable[T]: ...
@@ -205,7 +220,7 @@ class ConfigurationOption(Provider[Any]):
205220
def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any] = None, envs_required: bool = False) -> None: ...
206221
def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
207222
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
208-
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
223+
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False, as_: Optional[_Callable[..., Any]] = None) -> None: ...
209224
def from_value(self, value: Any) -> None: ...
210225

211226

@@ -215,7 +230,7 @@ class TypedConfigurationOption(Callable[T]):
215230

216231

217232
class Configuration(Object[Any]):
218-
DEFAULT_NAME: str = 'config'
233+
DEFAULT_NAME: str = "config"
219234
def __init__(
220235
self,
221236
name: str = DEFAULT_NAME,
@@ -262,18 +277,18 @@ class Configuration(Object[Any]):
262277
def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any] = None, envs_required: bool = False) -> None: ...
263278
def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
264279
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
265-
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
280+
def from_env(self, name: str, default: Optional[Any] = None, required: bool = False, as_: Optional[_Callable[..., Any]] = None) -> None: ...
266281
def from_value(self, value: Any) -> None: ...
267282

268283

269284
class Factory(Provider[T]):
270285
provided_type: Optional[Type]
271-
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
286+
def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
272287
@property
273288
def cls(self) -> Type[T]: ...
274289
@property
275290
def provides(self) -> Optional[_Callable[..., T]]: ...
276-
def set_provides(self, provides: Optional[_Callable[..., T]]) -> Factory[T]: ...
291+
def set_provides(self, provides: Optional[Union[_Callable[..., T], str]]) -> Factory[T]: ...
277292
@property
278293
def args(self) -> Tuple[Injection]: ...
279294
def add_args(self, *args: Injection) -> Factory[T]: ...
@@ -302,29 +317,21 @@ class FactoryDelegate(Delegate):
302317
def __init__(self, factory: Factory): ...
303318

304319

305-
class FactoryAggregate(Provider[T]):
306-
def __init__(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]): ...
307-
def __getattr__(self, factory_name: Any) -> Factory[T]: ...
308-
309-
@overload
310-
def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> T: ...
311-
@overload
312-
def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
313-
def async_(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
314-
320+
class FactoryAggregate(Aggregate[T]):
321+
def __getattr__(self, provider_name: Any) -> Factory[T]: ...
315322
@property
316323
def factories(self) -> _Dict[Any, Factory[T]]: ...
317-
def set_factories(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]) -> FactoryAggregate[T]: ...
324+
def set_factories(self, provider_dict: Optional[_Dict[Any, Factory[T]]] = None, **provider_kwargs: Factory[T]) -> FactoryAggregate[T]: ...
318325

319326

320327
class BaseSingleton(Provider[T]):
321328
provided_type = Optional[Type]
322-
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
329+
def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
323330
@property
324331
def cls(self) -> Type[T]: ...
325332
@property
326333
def provides(self) -> Optional[_Callable[..., T]]: ...
327-
def set_provides(self, provides: Optional[_Callable[..., T]]) -> BaseSingleton[T]: ...
334+
def set_provides(self, provides: Optional[Union[_Callable[..., T], str]]) -> BaseSingleton[T]: ...
328335
@property
329336
def args(self) -> Tuple[Injection]: ...
330337
def add_args(self, *args: Injection) -> BaseSingleton[T]: ...
@@ -370,7 +377,7 @@ class AbstractSingleton(BaseSingleton[T]):
370377

371378

372379
class SingletonDelegate(Delegate):
373-
def __init__(self, factory: BaseSingleton): ...
380+
def __init__(self, singleton: BaseSingleton): ...
374381

375382

376383
class List(Provider[_List]):
@@ -403,7 +410,7 @@ class Resource(Provider[T]):
403410
@overload
404411
def __init__(self, provides: Optional[_Callable[..., _Coroutine[Injection, Injection, T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
405412
@overload
406-
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
413+
def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
407414
@property
408415
def provides(self) -> Optional[_Callable[..., Any]]: ...
409416
def set_provides(self, provides: Optional[Any]) -> Resource[T]: ...
@@ -483,7 +490,9 @@ class MethodCaller(Provider, ProvidedInstanceFluentInterface):
483490
class OverridingContext(Generic[T]):
484491
def __init__(self, overridden: Provider, overriding: Provider): ...
485492
def __enter__(self) -> T: ...
486-
def __exit__(self, *_: Any) -> None: ...
493+
def __exit__(self, *_: Any) -> None:
494+
pass
495+
...
487496

488497

489498
class BaseSingletonResetContext(Generic[T]):

‎src/dependency_injector/providers.pyx

Lines changed: 380 additions & 294 deletions
Large diffs are not rendered by default.

‎src/dependency_injector/resources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import TypeVar, Generic, Optional
55

66

7-
T = TypeVar('T')
7+
T = TypeVar("T")
88

99

1010
class Resource(Generic[T], metaclass=abc.ABCMeta):

‎src/dependency_injector/schema.py

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ def __init__(self, schema: ContainerSchema) -> None:
1919

2020
def process(self):
2121
"""Process schema."""
22-
self._create_providers(self._schema['container'])
23-
self._setup_injections(self._schema['container'])
22+
self._create_providers(self._schema["container"])
23+
self._setup_injections(self._schema["container"])
2424

2525
def get_providers(self):
2626
"""Return providers."""
@@ -36,11 +36,11 @@ def _create_providers(
3636
for provider_name, data in provider_schema.items():
3737
provider = None
3838

39-
if 'provider' in data:
40-
provider_type = _get_provider_cls(data['provider'])
39+
if "provider" in data:
40+
provider_type = _get_provider_cls(data["provider"])
4141
args = []
4242

43-
# provides = data.get('provides')
43+
# provides = data.get("provides")
4444
# if provides:
4545
# provides = _import_string(provides)
4646
# if provides:
@@ -69,38 +69,38 @@ def _setup_injections( # noqa: C901
6969
args = []
7070
kwargs = {}
7171

72-
provides = data.get('provides')
72+
provides = data.get("provides")
7373
if provides:
74-
if isinstance(provides, str) and provides.startswith('container.'):
75-
provides = self._resolve_provider(provides[len('container.'):])
74+
if isinstance(provides, str) and provides.startswith("container."):
75+
provides = self._resolve_provider(provides[len("container."):])
7676
else:
7777
provides = _import_string(provides)
7878
provider.set_provides(provides)
7979

80-
arg_injections = data.get('args')
80+
arg_injections = data.get("args")
8181
if arg_injections:
8282
for arg in arg_injections:
8383
injection = None
8484

85-
if isinstance(arg, str) and arg.startswith('container.'):
86-
injection = self._resolve_provider(arg[len('container.'):])
85+
if isinstance(arg, str) and arg.startswith("container."):
86+
injection = self._resolve_provider(arg[len("container."):])
8787

8888
# TODO: refactoring
8989
if isinstance(arg, dict):
9090
provider_args = []
91-
provider_type = _get_provider_cls(arg.get('provider'))
92-
provides = arg.get('provides')
91+
provider_type = _get_provider_cls(arg.get("provider"))
92+
provides = arg.get("provides")
9393
if provides:
94-
if isinstance(provides, str) and provides.startswith('container.'):
95-
provides = self._resolve_provider(provides[len('container.'):])
94+
if isinstance(provides, str) and provides.startswith("container."):
95+
provides = self._resolve_provider(provides[len("container."):])
9696
else:
9797
provides = _import_string(provides)
9898
provider_args.append(provides)
99-
for provider_arg in arg.get('args', []):
99+
for provider_arg in arg.get("args", []):
100100
if isinstance(provider_arg, str) \
101-
and provider_arg.startswith('container.'):
101+
and provider_arg.startswith("container."):
102102
provider_args.append(
103-
self._resolve_provider(provider_arg[len('container.'):]),
103+
self._resolve_provider(provider_arg[len("container."):]),
104104
)
105105
injection = provider_type(*provider_args)
106106

@@ -111,30 +111,30 @@ def _setup_injections( # noqa: C901
111111
if args:
112112
provider.add_args(*args)
113113

114-
kwarg_injections = data.get('kwargs')
114+
kwarg_injections = data.get("kwargs")
115115
if kwarg_injections:
116116
for name, arg in kwarg_injections.items():
117117
injection = None
118118

119-
if isinstance(arg, str) and arg.startswith('container.'):
120-
injection = self._resolve_provider(arg[len('container.'):])
119+
if isinstance(arg, str) and arg.startswith("container."):
120+
injection = self._resolve_provider(arg[len("container."):])
121121

122122
# TODO: refactoring
123123
if isinstance(arg, dict):
124124
provider_args = []
125-
provider_type = _get_provider_cls(arg.get('provider'))
126-
provides = arg.get('provides')
125+
provider_type = _get_provider_cls(arg.get("provider"))
126+
provides = arg.get("provides")
127127
if provides:
128-
if isinstance(provides, str) and provides.startswith('container.'):
129-
provides = self._resolve_provider(provides[len('container.'):])
128+
if isinstance(provides, str) and provides.startswith("container."):
129+
provides = self._resolve_provider(provides[len("container."):])
130130
else:
131131
provides = _import_string(provides)
132132
provider_args.append(provides)
133-
for provider_arg in arg.get('args', []):
133+
for provider_arg in arg.get("args", []):
134134
if isinstance(provider_arg, str) \
135-
and provider_arg.startswith('container.'):
135+
and provider_arg.startswith("container."):
136136
provider_args.append(
137-
self._resolve_provider(provider_arg[len('container.'):]),
137+
self._resolve_provider(provider_arg[len("container."):]),
138138
)
139139
injection = provider_type(*provider_args)
140140

@@ -149,17 +149,17 @@ def _setup_injections( # noqa: C901
149149
self._setup_injections(provider_schema=data, container=provider)
150150

151151
def _resolve_provider(self, name: str) -> Optional[providers.Provider]:
152-
segments = name.split('.')
152+
segments = name.split(".")
153153
try:
154154
provider = getattr(self._container, segments[0])
155155
except AttributeError:
156156
return None
157157

158158
for segment in segments[1:]:
159-
parentheses = ''
160-
if '(' in segment and ')' in segment:
161-
parentheses = segment[segment.find('('):segment.rfind(')')+1]
162-
segment = segment.replace(parentheses, '')
159+
parentheses = ""
160+
if "(" in segment and ")" in segment:
161+
parentheses = segment[segment.find("("):segment.rfind(")")+1]
162+
segment = segment.replace(parentheses, "")
163163

164164
try:
165165
provider = getattr(provider, segment)
@@ -190,7 +190,7 @@ def _get_provider_cls(provider_cls_name: str) -> Type[providers.Provider]:
190190
if custom_provider_type:
191191
return custom_provider_type
192192

193-
raise SchemaError(f'Undefined provider class "{provider_cls_name}"')
193+
raise SchemaError(f"Undefined provider class \"{provider_cls_name}\"")
194194

195195

196196
def _fetch_provider_cls_from_std(provider_cls_name: str) -> Optional[Type[providers.Provider]]:
@@ -201,24 +201,24 @@ def _import_provider_cls(provider_cls_name: str) -> Optional[Type[providers.Prov
201201
try:
202202
cls = _import_string(provider_cls_name)
203203
except (ImportError, ValueError) as exception:
204-
raise SchemaError(f'Can not import provider "{provider_cls_name}"') from exception
204+
raise SchemaError(f"Can not import provider \"{provider_cls_name}\"") from exception
205205
except AttributeError:
206206
return None
207207
else:
208208
if isinstance(cls, type) and not issubclass(cls, providers.Provider):
209-
raise SchemaError(f'Provider class "{cls}" is not a subclass of providers base class')
209+
raise SchemaError(f"Provider class \"{cls}\" is not a subclass of providers base class")
210210
return cls
211211

212212

213213
def _import_string(string_name: str) -> Optional[object]:
214-
segments = string_name.split('.')
214+
segments = string_name.split(".")
215215

216216
if len(segments) == 1:
217217
member = getattr(builtins, segments[0], None)
218218
if member:
219219
return member
220220

221-
module_name = '.'.join(segments[:-1])
221+
module_name = ".".join(segments[:-1])
222222
if not module_name:
223223
return None
224224

‎src/dependency_injector/wiring.py

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,27 @@ class GenericMeta(type):
6868
)
6969

7070
__all__ = (
71-
'wire',
72-
'unwire',
73-
'inject',
74-
'as_int',
75-
'as_float',
76-
'as_',
77-
'required',
78-
'invariant',
79-
'provided',
80-
'Provide',
81-
'Provider',
82-
'Closing',
83-
'register_loader_containers',
84-
'unregister_loader_containers',
85-
'install_loader',
86-
'uninstall_loader',
87-
'is_loader_installed',
71+
"wire",
72+
"unwire",
73+
"inject",
74+
"as_int",
75+
"as_float",
76+
"as_",
77+
"required",
78+
"invariant",
79+
"provided",
80+
"Provide",
81+
"Provider",
82+
"Closing",
83+
"register_loader_containers",
84+
"unregister_loader_containers",
85+
"install_loader",
86+
"uninstall_loader",
87+
"is_loader_installed",
8888
)
8989

90-
T = TypeVar('T')
91-
F = TypeVar('F', bound=Callable[..., Any])
90+
T = TypeVar("T")
91+
F = TypeVar("F", bound=Callable[..., Any])
9292
Container = Any
9393

9494

@@ -107,10 +107,10 @@ def get_callables_from_module(self, module: ModuleType) -> Iterator[Callable[...
107107
continue
108108
yield patched
109109

110-
def add_attribute(self, patched: 'PatchedAttribute'):
110+
def add_attribute(self, patched: "PatchedAttribute"):
111111
self._attributes.add(patched)
112112

113-
def get_attributes_from_module(self, module: ModuleType) -> Iterator['PatchedAttribute']:
113+
def get_attributes_from_module(self, module: ModuleType) -> Iterator["PatchedAttribute"]:
114114
for attribute in self._attributes:
115115
if not attribute.is_in_module(module):
116116
continue
@@ -125,7 +125,7 @@ def clear_module_attributes(self, module: ModuleType):
125125

126126
class PatchedAttribute:
127127

128-
def __init__(self, member: Any, name: str, marker: '_Marker'):
128+
def __init__(self, member: Any, name: str, marker: "_Marker"):
129129
self.member = member
130130
self.name = name
131131
self.marker = marker
@@ -143,7 +143,7 @@ def is_in_module(self, module: ModuleType) -> bool:
143143

144144
class ProvidersMap:
145145

146-
CONTAINER_STRING_ID = '<container>'
146+
CONTAINER_STRING_ID = "<container>"
147147

148148
def __init__(self, container):
149149
self._container = container
@@ -159,7 +159,7 @@ def __init__(self, container):
159159
def resolve_provider(
160160
self,
161161
provider: Union[providers.Provider, str],
162-
modifier: Optional['Modifier'] = None,
162+
modifier: Optional["Modifier"] = None,
163163
) -> Optional[providers.Provider]:
164164
if isinstance(provider, providers.Delegate):
165165
return self._resolve_delegate(provider)
@@ -182,13 +182,13 @@ def resolve_provider(
182182
def _resolve_string_id(
183183
self,
184184
id: str,
185-
modifier: Optional['Modifier'] = None,
185+
modifier: Optional["Modifier"] = None,
186186
) -> Optional[providers.Provider]:
187187
if id == self.CONTAINER_STRING_ID:
188188
return self._container.__self__
189189

190190
provider = self._container
191-
for segment in id.split('.'):
191+
for segment in id.split("."):
192192
try:
193193
provider = getattr(provider, segment)
194194
except AttributeError:
@@ -282,10 +282,10 @@ def _create_providers_map(
282282
original_container: Container,
283283
) -> Dict[providers.Provider, providers.Provider]:
284284
current_providers = current_container.providers
285-
current_providers['__self__'] = current_container.__self__
285+
current_providers["__self__"] = current_container.__self__
286286

287287
original_providers = original_container.providers
288-
original_providers['__self__'] = original_container.__self__
288+
original_providers["__self__"] = original_container.__self__
289289

290290
providers_map = {}
291291
for provider_name, current_provider in current_providers.items():
@@ -429,7 +429,7 @@ def _patch_method(
429429
method: Callable[..., Any],
430430
providers_map: ProvidersMap,
431431
) -> None:
432-
if hasattr(cls, '__dict__') \
432+
if hasattr(cls, "__dict__") \
433433
and name in cls.__dict__ \
434434
and isinstance(cls.__dict__[name], (classmethod, staticmethod)):
435435
method = cls.__dict__[name]
@@ -457,7 +457,7 @@ def _unpatch(
457457
name: str,
458458
fn: Callable[..., Any],
459459
) -> None:
460-
if hasattr(module, '__dict__') \
460+
if hasattr(module, "__dict__") \
461461
and name in module.__dict__ \
462462
and isinstance(module.__dict__[name], (classmethod, staticmethod)):
463463
method = module.__dict__[name]
@@ -472,7 +472,7 @@ def _unpatch(
472472
def _patch_attribute(
473473
member: Any,
474474
name: str,
475-
marker: '_Marker',
475+
marker: "_Marker",
476476
providers_map: ProvidersMap,
477477
) -> None:
478478
provider = providers_map.resolve_provider(marker.provider, marker.modifier)
@@ -487,7 +487,7 @@ def _patch_attribute(
487487
elif isinstance(marker, Provider):
488488
setattr(member, name, provider)
489489
else:
490-
raise Exception(f'Unknown type of marker {marker}')
490+
raise Exception(f"Unknown type of marker {marker}")
491491

492492

493493
def _unpatch_attribute(patched: PatchedAttribute) -> None:
@@ -502,16 +502,16 @@ def _fetch_reference_injections( # noqa: C901
502502
# - https://github.com/ets-labs/python-dependency-injector/issues/398
503503
if GenericAlias and any((
504504
fn is GenericAlias,
505-
getattr(fn, '__func__', None) is GenericAlias
505+
getattr(fn, "__func__", None) is GenericAlias
506506
)):
507507
fn = fn.__init__
508508

509509
try:
510510
signature = inspect.signature(fn)
511511
except ValueError as exception:
512-
if 'no signature found' in str(exception):
512+
if "no signature found" in str(exception):
513513
return {}, {}
514-
elif 'not supported by signature' in str(exception):
514+
elif "not supported by signature" in str(exception):
515515
return {}, {}
516516
else:
517517
raise exception
@@ -565,11 +565,11 @@ def _unbind_injections(fn: Callable[..., Any]) -> None:
565565

566566
def _fetch_modules(package):
567567
modules = [package]
568-
if not hasattr(package, '__path__') or not hasattr(package, '__name__'):
568+
if not hasattr(package, "__path__") or not hasattr(package, "__name__"):
569569
return modules
570570
for module_info in pkgutil.walk_packages(
571571
path=package.__path__,
572-
prefix=package.__name__ + '.',
572+
prefix=package.__name__ + ".",
573573
):
574574
module = importlib.import_module(module_info.name)
575575
modules.append(module)
@@ -670,13 +670,13 @@ def _is_fastapi_depends(param: Any) -> bool:
670670

671671

672672
def _is_patched(fn):
673-
return getattr(fn, '__wired__', False) is True
673+
return getattr(fn, "__wired__", False) is True
674674

675675

676676
def _is_declarative_container(instance: Any) -> bool:
677677
return (isinstance(instance, type)
678-
and getattr(instance, '__IS_CONTAINER__', False) is True
679-
and getattr(instance, 'declarative_parent', None) is None)
678+
and getattr(instance, "__IS_CONTAINER__", False) is True
679+
and getattr(instance, "declarative_parent", None) is None)
680680

681681

682682
class Modifier:
@@ -722,15 +722,15 @@ class RequiredModifier(Modifier):
722722
def __init__(self):
723723
self.type_modifier = None
724724

725-
def as_int(self) -> 'RequiredModifier':
725+
def as_int(self) -> "RequiredModifier":
726726
self.type_modifier = TypeModifier(int)
727727
return self
728728

729-
def as_float(self) -> 'RequiredModifier':
729+
def as_float(self) -> "RequiredModifier":
730730
self.type_modifier = TypeModifier(float)
731731
return self
732732

733-
def as_(self, type_: Type) -> 'RequiredModifier':
733+
def as_(self, type_: Type) -> "RequiredModifier":
734734
self.type_modifier = TypeModifier(type_)
735735
return self
736736

@@ -771,9 +771,9 @@ def invariant(id: str) -> InvariantModifier:
771771

772772
class ProvidedInstance(Modifier):
773773

774-
TYPE_ATTRIBUTE = 'attr'
775-
TYPE_ITEM = 'item'
776-
TYPE_CALL = 'call'
774+
TYPE_ATTRIBUTE = "attr"
775+
TYPE_ITEM = "item"
776+
TYPE_CALL = "call"
777777

778778
def __init__(self):
779779
self.segments = []

‎tests/typing/aggregate.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from dependency_injector import providers
2+
3+
4+
class Animal:
5+
...
6+
7+
8+
class Cat(Animal):
9+
...
10+
11+
12+
# Test 1: to check Aggregate provider
13+
provider1: providers.Aggregate[str] = providers.Aggregate(
14+
a=providers.Object("str1"),
15+
b=providers.Object("str2"),
16+
)
17+
provider_a_1: providers.Provider[str] = provider1.a
18+
provider_b_1: providers.Provider[str] = provider1.b
19+
val1: str = provider1("a")
20+
21+
provider1_set_non_string_keys: providers.Aggregate[str] = providers.Aggregate()
22+
provider1_set_non_string_keys.set_providers({Cat: providers.Object("str")})
23+
provider_set_non_string_1: providers.Provider[str] = provider1_set_non_string_keys.providers[Cat]
24+
25+
provider1_new_non_string_keys: providers.Aggregate[str] = providers.Aggregate(
26+
{Cat: providers.Object("str")},
27+
)
28+
factory_new_non_string_1: providers.Provider[str] = provider1_new_non_string_keys.providers[Cat]
29+
30+
provider1_no_explicit_typing = providers.Aggregate(a=providers.Object("str"))
31+
provider1_no_explicit_typing_factory: providers.Provider[str] = provider1_no_explicit_typing.providers["a"]
32+
provider1_no_explicit_typing_object: str = provider1_no_explicit_typing("a")

‎tests/typing/callable.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,7 @@ async def _async9() -> None:
6666
provider11 = providers.Callable[Animal](Cat)
6767
provides11: Optional[Callable[..., Animal]] = provider11.provides
6868
assert provides11 is Cat
69+
70+
# Test 12: to check string imports
71+
provider12: providers.Callable[dict] = providers.Callable("builtins.dict")
72+
provider12.set_provides("builtins.dict")

‎tests/typing/configuration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
config2.from_yaml("config.yml")
1616
config2.from_yaml(Path("config.yml"))
17+
1718
config2.from_env("ENV", "default")
19+
config2.from_env("ENV", as_=int, default=123)
20+
config2.from_env("ENV", as_=float, required=True)
21+
config2.from_env("ENV", as_=lambda env: str(env))
1822

1923
# Test 3: to check as_*() methods
2024
config3 = providers.Configuration()

‎tests/typing/coroutine.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ async def _coro() -> None:
99
# Test 1: to check the return type
1010
provider1 = providers.Coroutine(_coro)
1111
var1: Coroutine = provider1()
12+
13+
# Test 2: to check string imports
14+
provider2: providers.Coroutine[None] = providers.Coroutine("_coro")
15+
provider2.set_provides("_coro")

‎tests/typing/factory.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,7 @@ async def _async11() -> None:
9999
assert issubclass(provided_cls13, Animal)
100100
provided_provides13: Optional[Callable[..., Animal]] = provider13.provides
101101
assert provided_provides13 is not None and provided_provides13() == Cat()
102+
103+
# Test 14: to check string imports
104+
provider14: providers.Factory[dict] = providers.Factory("builtins.dict")
105+
provider14.set_provides("builtins.dict")

‎tests/typing/resource.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ async def shutdown(self, resource: Optional[List[int]]) -> None:
9797
async def _provide8() -> None:
9898
var1: List[int] = await provider8() # type: ignore
9999
var2: List[int] = await provider8.async_()
100+
101+
102+
# Test 9: to check string imports
103+
provider9: providers.Resource[dict] = providers.Resource("builtins.dict")
104+
provider9.set_provides("builtins.dict")

‎tests/typing/singleton.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,7 @@ async def _async13() -> None:
8989
assert issubclass(provided_cls15, Animal)
9090
provided_provides15: Optional[Callable[..., Animal]] = provider15.provides
9191
assert provided_provides15 is not None and provided_provides15() == Cat()
92+
93+
# Test 16: to check string imports
94+
provider16: providers.Singleton[dict] = providers.Singleton("builtins.dict")
95+
provider16.set_provides("builtins.dict")

‎tests/unit/providers/callables/test_callable_py2_py3.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Callable provider tests."""
22

3+
import decimal
34
import sys
45

56
from dependency_injector import providers, errors
6-
from pytest import raises
7+
from pytest import raises, mark
78

89
from .common import example
910

@@ -29,6 +30,20 @@ def test_set_provides_returns_():
2930
assert provider.set_provides(object) is provider
3031

3132

33+
@mark.parametrize(
34+
"str_name,cls",
35+
[
36+
("dependency_injector.providers.Factory", providers.Factory),
37+
("decimal.Decimal", decimal.Decimal),
38+
("list", list),
39+
(".common.example", example),
40+
("test_is_provider", test_is_provider),
41+
],
42+
)
43+
def test_set_provides_string_imports(str_name, cls):
44+
assert providers.Callable(str_name).provides is cls
45+
46+
3247
def test_provided_instance_provider():
3348
provider = providers.Callable(example)
3449
assert isinstance(provider.provided, providers.ProvidedInstance)

‎tests/unit/providers/configuration/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ def environment_variables():
106106
os.environ["CONFIG_TEST_ENV"] = "test-value"
107107
os.environ["CONFIG_TEST_PATH"] = "test-path"
108108
os.environ["DEFINED"] = "defined"
109+
os.environ["EMPTY"] = ""
110+
os.environ["CONFIG_INT"] = "42"
109111
yield
110112
os.environ.pop("CONFIG_TEST_ENV", None)
111113
os.environ.pop("CONFIG_TEST_PATH", None)
112114
os.environ.pop("DEFINED", None)
115+
os.environ.pop("EMPTY", None)
116+
os.environ.pop("CONFIG_INT", None)

‎tests/unit/providers/configuration/test_from_env_py2_py3.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,54 @@ def test_option_default_none(config):
3131
assert config.option() is None
3232

3333

34+
def test_as_(config):
35+
config.from_env("CONFIG_INT", as_=int)
36+
assert config() == 42
37+
assert isinstance(config(), int)
38+
39+
40+
def test_as__default(config):
41+
config.from_env("UNDEFINED", as_=int, default="33")
42+
assert config() == 33
43+
assert isinstance(config(), int)
44+
45+
46+
def test_as__undefined_required(config):
47+
with raises(ValueError):
48+
config.from_env("UNDEFINED", as_=int, required=True)
49+
assert config() == {}
50+
51+
52+
def test_as__defined_empty(config):
53+
with raises(ValueError):
54+
config.from_env("EMPTY", as_=int)
55+
assert config() == {}
56+
57+
58+
def test_option_as_(config):
59+
config.option.from_env("CONFIG_INT", as_=int)
60+
assert config.option() == 42
61+
assert isinstance(config.option(), int)
62+
63+
64+
def test_option_as__default(config):
65+
config.option.from_env("UNDEFINED", as_=int, default="33")
66+
assert config.option() == 33
67+
assert isinstance(config.option(), int)
68+
69+
70+
def test_option_as__undefined_required(config):
71+
with raises(ValueError):
72+
config.option.from_env("UNDEFINED", as_=int, required=True)
73+
assert config.option() is None
74+
75+
76+
def test_option_as__defined_empty(config):
77+
with raises(ValueError):
78+
config.option.from_env("EMPTY", as_=int)
79+
assert config.option() is None
80+
81+
3482
@mark.parametrize("config_type", ["strict"])
3583
def test_undefined_in_strict_mode(config):
3684
with raises(ValueError):

‎tests/unit/providers/coroutines/test_coroutine_py35.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ def test_set_provides_returns_self():
3131
assert provider.set_provides(example) is provider
3232

3333

34+
@mark.parametrize(
35+
"str_name,cls",
36+
[
37+
(".common.example", example),
38+
("example", example),
39+
],
40+
)
41+
def test_set_provides_string_imports(str_name, cls):
42+
assert providers.Coroutine(str_name).provides is cls
43+
44+
3445
@mark.asyncio
3546
async def test_call_with_positional_args():
3647
provider = providers.Coroutine(example, 1, 2, 3, 4)

‎tests/unit/providers/factories/test_factory_aggregate_py2_py3.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,52 @@ def test_init_with_not_a_factory():
7878
)
7979

8080

81+
@mark.parametrize("factory_type", ["empty"])
82+
def test_init_optional_providers(factory_aggregate, factory_a, factory_b):
83+
factory_aggregate.set_providers(
84+
example_a=factory_a,
85+
example_b=factory_b,
86+
)
87+
assert factory_aggregate.providers == {
88+
"example_a": factory_a,
89+
"example_b": factory_b,
90+
}
91+
assert isinstance(factory_aggregate("example_a"), ExampleA)
92+
assert isinstance(factory_aggregate("example_b"), ExampleB)
93+
94+
95+
@mark.parametrize("factory_type", ["non-string-keys"])
96+
def test_set_factories_with_non_string_keys(factory_aggregate, factory_a, factory_b):
97+
factory_aggregate.set_providers({
98+
ExampleA: factory_a,
99+
ExampleB: factory_b,
100+
})
101+
102+
object_a = factory_aggregate(ExampleA, 1, 2, init_arg3=3, init_arg4=4)
103+
object_b = factory_aggregate(ExampleB, 11, 22, init_arg3=33, init_arg4=44)
104+
105+
assert isinstance(object_a, ExampleA)
106+
assert object_a.init_arg1 == 1
107+
assert object_a.init_arg2 == 2
108+
assert object_a.init_arg3 == 3
109+
assert object_a.init_arg4 == 4
110+
111+
assert isinstance(object_b, ExampleB)
112+
assert object_b.init_arg1 == 11
113+
assert object_b.init_arg2 == 22
114+
assert object_b.init_arg3 == 33
115+
assert object_b.init_arg4 == 44
116+
117+
assert factory_aggregate.providers == {
118+
ExampleA: factory_a,
119+
ExampleB: factory_b,
120+
}
121+
122+
123+
def test_set_providers_returns_self(factory_aggregate, factory_a):
124+
assert factory_aggregate.set_providers(example_a=factory_a) is factory_aggregate
125+
126+
81127
@mark.parametrize("factory_type", ["empty"])
82128
def test_init_optional_factories(factory_aggregate, factory_a, factory_b):
83129
factory_aggregate.set_factories(

‎tests/unit/providers/factories/test_factory_py2_py3.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Factory provider tests."""
22

3+
import decimal
34
import sys
45

56
from dependency_injector import providers, errors
6-
from pytest import raises
7+
from pytest import raises, mark
78

89
from .common import Example
910

@@ -29,6 +30,20 @@ def test_set_provides_returns_():
2930
assert provider.set_provides(object) is provider
3031

3132

33+
@mark.parametrize(
34+
"str_name,cls",
35+
[
36+
("dependency_injector.providers.Factory", providers.Factory),
37+
("decimal.Decimal", decimal.Decimal),
38+
("list", list),
39+
(".common.Example", Example),
40+
("test_is_provider", test_is_provider),
41+
],
42+
)
43+
def test_set_provides_string_imports(str_name, cls):
44+
assert providers.Factory(str_name).provides is cls
45+
46+
3247
def test_init_with_valid_provided_type():
3348
class ExampleProvider(providers.Factory):
3449
provided_type = Example

‎tests/unit/providers/resource/test_resource_py35.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""Resource provider tests."""
22

3+
import decimal
34
import sys
45
from typing import Any
56

67
from dependency_injector import containers, providers, resources, errors
7-
from pytest import raises
8+
from pytest import raises, mark
89

910

1011
def init_fn(*args, **kwargs):
@@ -27,6 +28,20 @@ def test_set_provides_returns_():
2728
assert provider.set_provides(init_fn) is provider
2829

2930

31+
@mark.parametrize(
32+
"str_name,cls",
33+
[
34+
("dependency_injector.providers.Factory", providers.Factory),
35+
("decimal.Decimal", decimal.Decimal),
36+
("list", list),
37+
(".test_resource_py35.test_is_provider", test_is_provider),
38+
("test_is_provider", test_is_provider),
39+
],
40+
)
41+
def test_set_provides_string_imports(str_name, cls):
42+
assert providers.Resource(str_name).provides is cls
43+
44+
3045
def test_provided_instance_provider():
3146
provider = providers.Resource(init_fn)
3247
assert isinstance(provider.provided, providers.ProvidedInstance)

‎tests/unit/providers/singleton/test_singleton_py2_py3.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Singleton provider tests."""
2-
2+
import decimal
33
import sys
44

55
from dependency_injector import providers, errors
6-
from pytest import fixture, raises
6+
from pytest import fixture, raises, mark
77

88
from .common import Example
99

@@ -49,6 +49,20 @@ def test_set_provides_returns_self(provider):
4949
assert provider.set_provides(object) is provider
5050

5151

52+
@mark.parametrize(
53+
"str_name,cls",
54+
[
55+
("dependency_injector.providers.Factory", providers.Factory),
56+
("decimal.Decimal", decimal.Decimal),
57+
("list", list),
58+
(".common.Example", Example),
59+
("test_is_provider", test_is_provider),
60+
],
61+
)
62+
def test_set_provides_string_imports(str_name, cls):
63+
assert providers.Singleton(str_name).provides is cls
64+
65+
5266
def test_init_with_valid_provided_type(singleton_cls):
5367
class ExampleProvider(singleton_cls):
5468
provided_type = Example
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
"""Aggregate provider tests."""
2+
3+
from dependency_injector import providers, errors
4+
from pytest import fixture, mark, raises
5+
6+
7+
class Example:
8+
def __init__(self, init_arg1=None, init_arg2=None, init_arg3=None, init_arg4=None):
9+
self.init_arg1 = init_arg1
10+
self.init_arg2 = init_arg2
11+
self.init_arg3 = init_arg3
12+
self.init_arg4 = init_arg4
13+
14+
self.attribute1 = None
15+
self.attribute2 = None
16+
17+
18+
class ExampleA(Example):
19+
pass
20+
21+
22+
class ExampleB(Example):
23+
pass
24+
25+
26+
@fixture
27+
def factory_a():
28+
return providers.Factory(ExampleA)
29+
30+
31+
@fixture
32+
def factory_b():
33+
return providers.Factory(ExampleB)
34+
35+
36+
@fixture
37+
def aggregate_type():
38+
return "default"
39+
40+
41+
@fixture
42+
def aggregate(aggregate_type, factory_a, factory_b):
43+
if aggregate_type == "empty":
44+
return providers.Aggregate()
45+
elif aggregate_type == "non-string-keys":
46+
return providers.Aggregate({
47+
ExampleA: factory_a,
48+
ExampleB: factory_b,
49+
})
50+
elif aggregate_type == "default":
51+
return providers.Aggregate(
52+
example_a=factory_a,
53+
example_b=factory_b,
54+
)
55+
else:
56+
raise ValueError("Unknown factory type \"{0}\"".format(aggregate_type))
57+
58+
59+
def test_is_provider(aggregate):
60+
assert providers.is_provider(aggregate) is True
61+
62+
63+
def test_is_delegated_provider(aggregate):
64+
assert providers.is_delegated(aggregate) is True
65+
66+
67+
@mark.parametrize("aggregate_type", ["non-string-keys"])
68+
def test_init_with_non_string_keys(aggregate, factory_a, factory_b):
69+
object_a = aggregate(ExampleA, 1, 2, init_arg3=3, init_arg4=4)
70+
object_b = aggregate(ExampleB, 11, 22, init_arg3=33, init_arg4=44)
71+
72+
assert isinstance(object_a, ExampleA)
73+
assert object_a.init_arg1 == 1
74+
assert object_a.init_arg2 == 2
75+
assert object_a.init_arg3 == 3
76+
assert object_a.init_arg4 == 4
77+
78+
assert isinstance(object_b, ExampleB)
79+
assert object_b.init_arg1 == 11
80+
assert object_b.init_arg2 == 22
81+
assert object_b.init_arg3 == 33
82+
assert object_b.init_arg4 == 44
83+
84+
assert aggregate.providers == {
85+
ExampleA: factory_a,
86+
ExampleB: factory_b,
87+
}
88+
89+
90+
def test_init_with_not_a_factory():
91+
with raises(errors.Error):
92+
providers.Aggregate(
93+
example_a=providers.Factory(ExampleA),
94+
example_b=object(),
95+
)
96+
97+
98+
@mark.parametrize("aggregate_type", ["empty"])
99+
def test_init_optional_providers(aggregate, factory_a, factory_b):
100+
aggregate.set_providers(
101+
example_a=factory_a,
102+
example_b=factory_b,
103+
)
104+
assert aggregate.providers == {
105+
"example_a": factory_a,
106+
"example_b": factory_b,
107+
}
108+
assert isinstance(aggregate("example_a"), ExampleA)
109+
assert isinstance(aggregate("example_b"), ExampleB)
110+
111+
112+
@mark.parametrize("aggregate_type", ["non-string-keys"])
113+
def test_set_providers_with_non_string_keys(aggregate, factory_a, factory_b):
114+
aggregate.set_providers({
115+
ExampleA: factory_a,
116+
ExampleB: factory_b,
117+
})
118+
119+
object_a = aggregate(ExampleA, 1, 2, init_arg3=3, init_arg4=4)
120+
object_b = aggregate(ExampleB, 11, 22, init_arg3=33, init_arg4=44)
121+
122+
assert isinstance(object_a, ExampleA)
123+
assert object_a.init_arg1 == 1
124+
assert object_a.init_arg2 == 2
125+
assert object_a.init_arg3 == 3
126+
assert object_a.init_arg4 == 4
127+
128+
assert isinstance(object_b, ExampleB)
129+
assert object_b.init_arg1 == 11
130+
assert object_b.init_arg2 == 22
131+
assert object_b.init_arg3 == 33
132+
assert object_b.init_arg4 == 44
133+
134+
assert aggregate.providers == {
135+
ExampleA: factory_a,
136+
ExampleB: factory_b,
137+
}
138+
139+
140+
def test_set_providers_returns_self(aggregate, factory_a):
141+
assert aggregate.set_providers(example_a=factory_a) is aggregate
142+
143+
144+
@mark.parametrize("aggregate_type", ["empty"])
145+
def test_init_optional_providers(aggregate, factory_a, factory_b):
146+
aggregate.set_providers(
147+
example_a=factory_a,
148+
example_b=factory_b,
149+
)
150+
assert aggregate.providers == {
151+
"example_a": factory_a,
152+
"example_b": factory_b,
153+
}
154+
assert isinstance(aggregate("example_a"), ExampleA)
155+
assert isinstance(aggregate("example_b"), ExampleB)
156+
157+
158+
@mark.parametrize("aggregate_type", ["non-string-keys"])
159+
def test_set_providers_with_non_string_keys(aggregate, factory_a, factory_b):
160+
aggregate.set_providers({
161+
ExampleA: factory_a,
162+
ExampleB: factory_b,
163+
})
164+
165+
object_a = aggregate(ExampleA, 1, 2, init_arg3=3, init_arg4=4)
166+
object_b = aggregate(ExampleB, 11, 22, init_arg3=33, init_arg4=44)
167+
168+
assert isinstance(object_a, ExampleA)
169+
assert object_a.init_arg1 == 1
170+
assert object_a.init_arg2 == 2
171+
assert object_a.init_arg3 == 3
172+
assert object_a.init_arg4 == 4
173+
174+
assert isinstance(object_b, ExampleB)
175+
assert object_b.init_arg1 == 11
176+
assert object_b.init_arg2 == 22
177+
assert object_b.init_arg3 == 33
178+
assert object_b.init_arg4 == 44
179+
180+
assert aggregate.providers == {
181+
ExampleA: factory_a,
182+
ExampleB: factory_b,
183+
}
184+
185+
186+
def test_set_providers_returns_self(aggregate, factory_a):
187+
assert aggregate.set_providers(example_a=factory_a) is aggregate
188+
189+
190+
def test_call(aggregate):
191+
object_a = aggregate("example_a", 1, 2, init_arg3=3, init_arg4=4)
192+
object_b = aggregate("example_b", 11, 22, init_arg3=33, init_arg4=44)
193+
194+
assert isinstance(object_a, ExampleA)
195+
assert object_a.init_arg1 == 1
196+
assert object_a.init_arg2 == 2
197+
assert object_a.init_arg3 == 3
198+
assert object_a.init_arg4 == 4
199+
200+
assert isinstance(object_b, ExampleB)
201+
assert object_b.init_arg1 == 11
202+
assert object_b.init_arg2 == 22
203+
assert object_b.init_arg3 == 33
204+
assert object_b.init_arg4 == 44
205+
206+
207+
def test_call_factory_name_as_kwarg(aggregate):
208+
object_a = aggregate(
209+
factory_name="example_a",
210+
init_arg1=1,
211+
init_arg2=2,
212+
init_arg3=3,
213+
init_arg4=4,
214+
)
215+
assert isinstance(object_a, ExampleA)
216+
assert object_a.init_arg1 == 1
217+
assert object_a.init_arg2 == 2
218+
assert object_a.init_arg3 == 3
219+
assert object_a.init_arg4 == 4
220+
221+
222+
def test_call_no_factory_name(aggregate):
223+
with raises(TypeError):
224+
aggregate()
225+
226+
227+
def test_call_no_such_provider(aggregate):
228+
with raises(errors.NoSuchProviderError):
229+
aggregate("unknown")
230+
231+
232+
def test_overridden(aggregate):
233+
with raises(errors.Error):
234+
aggregate.override(providers.Object(object()))
235+
236+
237+
def test_getattr(aggregate, factory_a, factory_b):
238+
assert aggregate.example_a is factory_a
239+
assert aggregate.example_b is factory_b
240+
241+
242+
def test_getattr_no_such_provider(aggregate):
243+
with raises(errors.NoSuchProviderError):
244+
aggregate.unknown
245+
246+
247+
def test_providers(aggregate, factory_a, factory_b):
248+
assert aggregate.providers == dict(
249+
example_a=factory_a,
250+
example_b=factory_b,
251+
)
252+
253+
254+
def test_deepcopy(aggregate):
255+
provider_copy = providers.deepcopy(aggregate)
256+
257+
assert aggregate is not provider_copy
258+
assert isinstance(provider_copy, type(aggregate))
259+
260+
assert aggregate.example_a is not provider_copy.example_a
261+
assert isinstance(aggregate.example_a, type(provider_copy.example_a))
262+
assert aggregate.example_a.cls is provider_copy.example_a.cls
263+
264+
assert aggregate.example_b is not provider_copy.example_b
265+
assert isinstance(aggregate.example_b, type(provider_copy.example_b))
266+
assert aggregate.example_b.cls is provider_copy.example_b.cls
267+
268+
269+
@mark.parametrize("aggregate_type", ["non-string-keys"])
270+
def test_deepcopy_with_non_string_keys(aggregate):
271+
provider_copy = providers.deepcopy(aggregate)
272+
273+
assert aggregate is not provider_copy
274+
assert isinstance(provider_copy, type(aggregate))
275+
276+
assert aggregate.providers[ExampleA] is not provider_copy.providers[ExampleA]
277+
assert isinstance(aggregate.providers[ExampleA], type(provider_copy.providers[ExampleA]))
278+
assert aggregate.providers[ExampleA].provides is provider_copy.providers[ExampleA].provides
279+
280+
assert aggregate.providers[ExampleB] is not provider_copy.providers[ExampleB]
281+
assert isinstance(aggregate.providers[ExampleB], type(provider_copy.providers[ExampleB]))
282+
assert aggregate.providers[ExampleB].provides is provider_copy.providers[ExampleB].provides
283+
284+
285+
def test_repr(aggregate):
286+
assert repr(aggregate) == (
287+
"<dependency_injector.providers."
288+
"Aggregate({0}) at {1}>".format(
289+
repr(aggregate.providers),
290+
hex(id(aggregate)),
291+
)
292+
)

‎tests/unit/providers/test_dependency_py2_py3.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ def test_default_attribute_provider():
7777
assert provider.default is default
7878

7979

80+
def test_default_with_empty_dict():
81+
# See: https://github.com/ets-labs/python-dependency-injector/issues/550
82+
default = {}
83+
provider = providers.Dependency(instance_of=dict, default=default)
84+
assert provider() == default
85+
assert provider.default() == default
86+
87+
88+
def test_default_with_empty_string():
89+
# See: https://github.com/ets-labs/python-dependency-injector/issues/550
90+
default = ""
91+
provider = providers.Dependency(instance_of=str, default=default)
92+
assert provider() == default
93+
assert provider.default() == default
94+
95+
8096
def test_is_defined(provider):
8197
assert provider.is_defined is False
8298

0 commit comments

Comments
 (0)
Please sign in to comment.