Add .providers attribute and .set_providers() method to FactoryAggregate provider

This commit is contained in:
Roman Mogylatov 2021-11-26 19:50:19 +03:00
parent 541131e338
commit 7238482402
8 changed files with 4512 additions and 4220 deletions

View File

@ -7,6 +7,14 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_ follows `Semantic versioning`_
Development version
-------------------
- 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
``FactoryAggregate.set_factories()`` method.
- Refactor ``FactoryAggregate`` provider internals.
4.37.0 4.37.0
------ ------
- Add support of Python 3.10. - Add support of Python 3.10.

View File

@ -163,9 +163,9 @@ The aggregated factories are associated with the string keys. When you call the
:lines: 3- :lines: 3-
:emphasize-lines: 33-37,47 :emphasize-lines: 33-37,47
You can get a dictionary of the aggregated factories using the ``.factories`` attribute. You can get a dictionary of the aggregated providers using ``.providers`` attribute.
To get a game factories dictionary from the previous example you can use To get a game provider dictionary from the previous example you can use
``game_factory.factories`` attribute. ``game_factory.providers`` attribute.
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the
previous example you can do ``chess = game_factory.chess("John", "Jane")``. previous example you can do ``chess = game_factory.chess("John", "Jane")``.
@ -176,7 +176,7 @@ previous example you can do ``chess = game_factory.chess("John", "Jane")``.
.. note:: .. note::
When you inject the ``FactoryAggregate`` provider it is passed "as is". When you inject the ``FactoryAggregate`` provider it is passed "as is".
To use non-string keys or keys with ``.`` and ``-`` you can provide a dictionary as a positional argument: To use non-string keys or string keys with ``.`` and ``-``, you can provide a dictionary as a positional argument:
.. code-block:: python .. code-block:: python

View File

@ -1209,12 +1209,12 @@ struct __pyx_obj_19dependency_injector_9providers_FactoryDelegate {
* *
* *
* cdef class FactoryAggregate(Provider): # <<<<<<<<<<<<<< * cdef class FactoryAggregate(Provider): # <<<<<<<<<<<<<<
* cdef dict __factories * cdef dict __providers
* *
*/ */
struct __pyx_obj_19dependency_injector_9providers_FactoryAggregate { struct __pyx_obj_19dependency_injector_9providers_FactoryAggregate {
struct __pyx_obj_19dependency_injector_9providers_Provider __pyx_base; struct __pyx_obj_19dependency_injector_9providers_Provider __pyx_base;
PyObject *__pyx___factories; PyObject *__pyx___providers;
}; };
@ -2077,13 +2077,13 @@ static struct __pyx_vtabstruct_19dependency_injector_9providers_FactoryDelegate
* *
* *
* cdef class FactoryAggregate(Provider): # <<<<<<<<<<<<<< * cdef class FactoryAggregate(Provider): # <<<<<<<<<<<<<<
* cdef dict __factories * cdef dict __providers
* *
*/ */
struct __pyx_vtabstruct_19dependency_injector_9providers_FactoryAggregate { struct __pyx_vtabstruct_19dependency_injector_9providers_FactoryAggregate {
struct __pyx_vtabstruct_19dependency_injector_9providers_Provider __pyx_base; struct __pyx_vtabstruct_19dependency_injector_9providers_Provider __pyx_base;
struct __pyx_obj_19dependency_injector_9providers_Factory *(*__pyx___get_factory)(struct __pyx_obj_19dependency_injector_9providers_FactoryAggregate *, PyObject *); struct __pyx_obj_19dependency_injector_9providers_Provider *(*__pyx___get_provider)(struct __pyx_obj_19dependency_injector_9providers_FactoryAggregate *, PyObject *);
}; };
static struct __pyx_vtabstruct_19dependency_injector_9providers_FactoryAggregate *__pyx_vtabptr_19dependency_injector_9providers_FactoryAggregate; static struct __pyx_vtabstruct_19dependency_injector_9providers_FactoryAggregate *__pyx_vtabptr_19dependency_injector_9providers_FactoryAggregate;

File diff suppressed because it is too large Load Diff

View File

@ -143,9 +143,9 @@ cdef class FactoryDelegate(Delegate):
cdef class FactoryAggregate(Provider): cdef class FactoryAggregate(Provider):
cdef dict __factories cdef dict __providers
cdef Factory __get_factory(self, object factory_name) cdef Provider __get_provider(self, object provider_name)
# Singleton providers # Singleton providers

View File

@ -303,18 +303,22 @@ class FactoryDelegate(Delegate):
class FactoryAggregate(Provider[T]): class FactoryAggregate(Provider[T]):
def __init__(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]): ... def __init__(self, provider_dict: Optional[_Dict[Any, Provider[T]]] = None, **provider_kwargs: Provider[T]): ...
def __getattr__(self, factory_name: Any) -> Factory[T]: ... def __getattr__(self, provider_name: Any) -> Provider[T]: ...
@overload @overload
def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> T: ... def __call__(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> T: ...
@overload @overload
def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ... def __call__(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
def async_(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ... def async_(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
@property @property
def factories(self) -> _Dict[Any, Factory[T]]: ... def providers(self) -> _Dict[Any, Provider[T]]: ...
def set_factories(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]) -> FactoryAggregate[T]: ... def set_providers(self, provider_dict: Optional[_Dict[Any, Provider[T]]] = None, **provider_kwargs: Provider[T]) -> FactoryAggregate[T]: ...
@property
def factories(self) -> _Dict[Any, Provider[T]]: ...
def set_factories(self, provider_dict: Optional[_Dict[Any, Provider[T]]] = None, **provider_kwargs: Provider[T]) -> FactoryAggregate[T]: ...
class BaseSingleton(Provider[T]): class BaseSingleton(Provider[T]):

View File

@ -2558,17 +2558,17 @@ cdef class FactoryAggregate(Provider):
:py:class:`FactoryAggregate` is a delegated provider, meaning that it is :py:class:`FactoryAggregate` is a delegated provider, meaning that it is
injected "as is". injected "as is".
All aggregated factories could be retrieved as a read-only All aggregated providers can be retrieved as a read-only
dictionary :py:attr:`FactoryAggregate.factories` or just as an attribute of dictionary :py:attr:`FactoryAggregate.providers` or as an attribute of
:py:class:`FactoryAggregate`. :py:class:`FactoryAggregate`.
""" """
__IS_DELEGATED__ = True __IS_DELEGATED__ = True
def __init__(self, factories_dict_=None, **factories_kwargs): def __init__(self, provider_dict=None, **provider_kwargs):
"""Initialize provider.""" """Initialize provider."""
self.__factories = {} self.__providers = {}
self.set_factories(factories_dict_, **factories_kwargs) self.set_providers(provider_dict, **provider_kwargs)
super(FactoryAggregate, self).__init__() super(FactoryAggregate, self).__init__()
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
@ -2578,48 +2578,69 @@ cdef class FactoryAggregate(Provider):
return copied return copied
copied = _memorized_duplicate(self, memo) copied = _memorized_duplicate(self, memo)
copied.set_factories(deepcopy(self.factories, memo)) copied.set_providers(deepcopy(self.providers, memo))
self._copy_overridings(copied, memo) self._copy_overridings(copied, memo)
return copied return copied
def __getattr__(self, factory_name): def __getattr__(self, factory_name):
"""Return aggregated factory.""" """Return aggregated provider."""
return self.__get_factory(factory_name) return self.__get_provider(factory_name)
def __str__(self): def __str__(self):
"""Return string representation of provider. """Return string representation of provider.
:rtype: str :rtype: str
""" """
return represent_provider(provider=self, provides=self.factories) return represent_provider(provider=self, provides=self.providers)
@property @property
def factories(self): def providers(self):
"""Return dictionary of factories, read-only.""" """Return dictionary of providers, read-only.
return self.__factories
def set_factories(self, factories_dict_=None, **factories_kwargs): Alias for ``.factories`` attribute.
"""Set factories.""" """
factories = {} return dict(self.__providers)
factories.update(factories_kwargs)
if factories_dict_:
factories.update(factories_dict_)
for factory in factories.values(): def set_providers(self, provider_dict=None, **provider_kwargs):
if isinstance(factory, Factory) is False: """Set providers.
Alias for ``.set_factories()`` method.
"""
providers = {}
providers.update(provider_kwargs)
if provider_dict:
providers.update(provider_dict)
for provider in providers.values():
if not is_provider(provider):
raise Error( raise Error(
'{0} can aggregate only instances of {1}, given - {2}'.format( '{0} can aggregate only instances of {1}, given - {2}'.format(
self.__class__, self.__class__,
Factory, Provider,
factory, provider,
), ),
) )
self.__factories = factories self.__providers = providers
return self return self
@property
def factories(self):
"""Return dictionary of factories, read-only.
Alias for ``.providers()`` attribute.
"""
return self.providers
def set_factories(self, factory_dict=None, **factory_kwargs):
"""Set factories.
Alias for ``.set_providers()`` method.
"""
return self.set_providers(factory_dict, **factory_kwargs)
def override(self, _): def override(self, _):
"""Override provider with another provider. """Override provider with another provider.
@ -2633,26 +2654,26 @@ cdef class FactoryAggregate(Provider):
@property @property
def related(self): def related(self):
"""Return related providers generator.""" """Return related providers generator."""
yield from self.__factories.values() yield from self.__providers.values()
yield from super().related yield from super().related
cpdef object _provide(self, tuple args, dict kwargs): cpdef object _provide(self, tuple args, dict kwargs):
try: try:
factory_name = args[0] provider_name = args[0]
except IndexError: except IndexError:
try: try:
factory_name = kwargs.pop('factory_name') provider_name = kwargs.pop("factory_name")
except KeyError: except KeyError:
raise TypeError('Factory missing 1 required positional argument: \'factory_name\'') raise TypeError("Missing 1st required positional argument: \"provider_name\"")
else: else:
args = args[1:] args = args[1:]
return self.__get_factory(factory_name)(*args, **kwargs) return self.__get_provider(provider_name)(*args, **kwargs)
cdef Factory __get_factory(self, object factory_key): cdef Provider __get_provider(self, object provider_name):
if factory_key not in self.__factories: if provider_name not in self.__providers:
raise NoSuchProviderError('{0} does not contain factory with name {1}'.format(self, factory_key)) raise NoSuchProviderError("{0} does not contain provider with name {1}".format(self, provider_name))
return <Factory> self.__factories[factory_key] return <Provider> self.__providers[provider_name]
cdef class BaseSingleton(Provider): cdef class BaseSingleton(Provider):

View File

@ -78,6 +78,52 @@ def test_init_with_not_a_factory():
) )
@mark.parametrize("factory_type", ["empty"])
def test_init_optional_providers(factory_aggregate, factory_a, factory_b):
factory_aggregate.set_providers(
example_a=factory_a,
example_b=factory_b,
)
assert factory_aggregate.providers == {
"example_a": factory_a,
"example_b": factory_b,
}
assert isinstance(factory_aggregate("example_a"), ExampleA)
assert isinstance(factory_aggregate("example_b"), ExampleB)
@mark.parametrize("factory_type", ["non-string-keys"])
def test_set_factories_with_non_string_keys(factory_aggregate, factory_a, factory_b):
factory_aggregate.set_providers({
ExampleA: factory_a,
ExampleB: factory_b,
})
object_a = factory_aggregate(ExampleA, 1, 2, init_arg3=3, init_arg4=4)
object_b = factory_aggregate(ExampleB, 11, 22, init_arg3=33, init_arg4=44)
assert isinstance(object_a, ExampleA)
assert object_a.init_arg1 == 1
assert object_a.init_arg2 == 2
assert object_a.init_arg3 == 3
assert object_a.init_arg4 == 4
assert isinstance(object_b, ExampleB)
assert object_b.init_arg1 == 11
assert object_b.init_arg2 == 22
assert object_b.init_arg3 == 33
assert object_b.init_arg4 == 44
assert factory_aggregate.providers == {
ExampleA: factory_a,
ExampleB: factory_b,
}
def test_set_providers_returns_self(factory_aggregate, factory_a):
assert factory_aggregate.set_providers(example_a=factory_a) is factory_aggregate
@mark.parametrize("factory_type", ["empty"]) @mark.parametrize("factory_type", ["empty"])
def test_init_optional_factories(factory_aggregate, factory_a, factory_b): def test_init_optional_factories(factory_aggregate, factory_a, factory_b):
factory_aggregate.set_factories( factory_aggregate.set_factories(