Skip to content

Aggregate provider #544

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

Merged
merged 8 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/main/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ follows `Semantic versioning`_

Development version
-------------------
- Add new provider ``Aggregate``. It is a generalized version of ``FactoryAggregate`` that
can contain providers of any type, not only ``Factory``. See issue
`#530 <https://github.com/ets-labs/python-dependency-injector/issues/530>`_. Thanks to
`@zerlok (Danil Troshnev) <https://github.com/zerlok>`_ for suggesting the feature.
- Add argument ``as_`` to the ``config.from_env()`` method for the explicit type casting
of an environment variable value, e.g.: ``config.timeout.from_env("TIMEOUT", as_=int)``.
See issue `#533 <https://github.com/ets-labs/python-dependency-injector/issues/533>`_. Thanks to
`@gtors (Andrey Torsunov) <https://github.com/gtors>`_ for suggesting the feature.
- Add ``.providers`` attribute to the ``FactoryAggregate`` provider. It is an alias for
``FactoryAggregate.factories`` attribute.
- Add ``.set_providers()`` method to the ``FactoryAggregate`` provider. It is an alias for
Expand Down
72 changes: 72 additions & 0 deletions docs/providers/aggregate.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.. _aggregate-provider:

Aggregate provider
==================

.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
Aggregate,Polymorphism,Environment Variable,Flexibility
:description: Aggregate provider aggregates other providers.
This page demonstrates how to implement the polymorphism and increase the
flexibility of your application using the Aggregate provider.

:py:class:`Aggregate` provider aggregates a group of other providers.

.. currentmodule:: dependency_injector.providers

.. literalinclude:: ../../examples/providers/aggregate.py
:language: python
:lines: 3-
:emphasize-lines: 24-27

Each provider in the ``Aggregate`` is associated with a key. You can call aggregated providers by providing
their key as a first argument. All positional and keyword arguments following the key will be forwarded to
the called provider:

.. code-block:: python

yaml_reader = container.config_readers("yaml", "./config.yml", foo=...)

You can also retrieve an aggregated provider by providing its key as an attribute name:

.. code-block:: python

yaml_reader = container.config_readers.yaml("./config.yml", foo=...)

To retrieve a dictionary of aggregated providers, use ``.providers`` attribute:

.. code-block:: python

container.config_readers.providers == {
"yaml": <YAML provider>,
"json": <JSON provider>,
}

.. note::
You can not override the ``Aggregate`` provider.

.. note::
When you inject the ``Aggregate`` provider, it is passed "as is".

To use non-string keys or string keys with ``.`` and ``-``, provide a dictionary as a positional argument:

.. code-block:: python

aggregate = providers.Aggregate({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})

.. seealso::
:ref:`selector-provider` to make injections based on a configuration value, environment variable, or a result of a callable.

``Aggregate`` provider is different from the :ref:`selector-provider`. ``Aggregate`` provider doesn't select which provider
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
of both providers is similar.

.. note::
``Aggregate`` provider is a successor of :ref:`factory-aggregate-provider` provider. ``Aggregate`` provider doesn't have
a restriction on the provider type, while ``FactoryAggregate`` aggregates only ``Factory`` providers.

.. disqus::
6 changes: 6 additions & 0 deletions docs/providers/factory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,17 @@ provider with two peculiarities:
:lines: 3-
:emphasize-lines: 34

.. _factory-aggregate-provider:

Factory aggregate
-----------------

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

.. seealso::
:ref:`aggregate-provider` – it's a successor of ``FactoryAggregate`` provider that can aggregate
any type of provider, not only ``Factory``.

The aggregated factories are associated with the string keys. When you call the
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
Expand Down
1 change: 1 addition & 0 deletions docs/providers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
dict
configuration
resource
aggregate
selector
dependency
overriding
Expand Down
3 changes: 3 additions & 0 deletions docs/providers/selector.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ When a ``Selector`` provider is called, it gets a ``selector`` value and delegat
the provider with a matching name. The ``selector`` callable works as a switch: when the returned
value is changed the ``Selector`` provider will delegate the work to another provider.

.. seealso::
:ref:`aggregate-provider` to inject a group of providers.

.. disqus::
39 changes: 39 additions & 0 deletions examples/providers/aggregate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""`Aggregate` provider example."""

from dependency_injector import containers, providers


class ConfigReader:

def __init__(self, path):
self._path = path

def read(self):
print(f"Parsing {self._path} with {self.__class__.__name__}")
...


class YamlReader(ConfigReader):
...


class JsonReader(ConfigReader):
...


class Container(containers.DeclarativeContainer):

config_readers = providers.Aggregate(
yaml=providers.Factory(YamlReader),
json=providers.Factory(JsonReader),
)


if __name__ == "__main__":
container = Container()

yaml_reader = container.config_readers("yaml", "./config.yml")
yaml_reader.read() # Parsing ./config.yml with YamlReader

json_reader = container.config_readers("json", "./config.json")
json_reader.read() # Parsing ./config.json with JsonReader
Loading