Skip to content

Commit 742e73a

Browse files
authored
Aggregate provider (ets-labs#544)
* Add implementation and typing stubs * Add tests * Add typing tests * Refactor FactoryAggregate * Update changelog * Add Aggregate provider docs and example * Update cross links between Aggregate, Selector, and FactoryAggregate docs * Add wording improvements to the docs
1 parent cfadd8c commit 742e73a

File tree

13 files changed

+12785
-11330
lines changed

13 files changed

+12785
-11330
lines changed

docs/main/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ follows `Semantic versioning`_
99

1010
Development version
1111
-------------------
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.
1216
- Add argument ``as_`` to the ``config.from_env()`` method for the explicit type casting
1317
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.
1420
- Add ``.providers`` attribute to the ``FactoryAggregate`` provider. It is an alias for
1521
``FactoryAggregate.factories`` attribute.
1622
- Add ``.set_providers()`` method to the ``FactoryAggregate`` provider. It is an alias for

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/factory.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,17 @@ provider with two peculiarities:
145145
:lines: 3-
146146
:emphasize-lines: 34
147147

148+
.. _factory-aggregate-provider:
149+
148150
Factory aggregate
149151
-----------------
150152

151153
:py:class:`FactoryAggregate` provider aggregates multiple factories.
152154

155+
.. seealso::
156+
:ref:`aggregate-provider` – it's a successor of ``FactoryAggregate`` provider that can aggregate
157+
any type of provider, not only ``Factory``.
158+
153159
The aggregated factories are associated with the string keys. When you call the
154160
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
155161
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.

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/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

0 commit comments

Comments
 (0)