Merge branch 'release/4.36.0' into master

This commit is contained in:
Roman Mogylatov 2021-08-25 12:06:37 -04:00
commit cef6d35cfd
20 changed files with 4270 additions and 3851 deletions

View File

@ -3,5 +3,8 @@ source = src/dependency_injector
omit = tests/unit omit = tests/unit
plugins = Cython.Coverage plugins = Cython.Coverage
[report]
show_missing = true
[html] [html]
directory=reports/unittests/ directory=reports/unittests/

View File

@ -18,3 +18,5 @@ Dependency Injector Contributors
+ Shubhendra Singh Chauhan (withshubh) + Shubhendra Singh Chauhan (withshubh)
+ sonthonaxrk (sonthonaxrk) + sonthonaxrk (sonthonaxrk)
+ Ngo Thanh Loi (Leonn) (loingo95) + Ngo Thanh Loi (Leonn) (loingo95)
+ Thiago Hiromi (thiromi)
+ Felipe Rubio (krouw)

View File

@ -7,12 +7,32 @@ 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`_
4.35.3 4.36.0
------ ------
- Add support of non-string keys for ``FactoryAggregate`` provider.
- Improve ``FactoryAggregate`` typing stub.
- Improve resource subclasses typing and make shutdown definition optional
`PR #492 <https://github.com/ets-labs/python-dependency-injector/pull/492>`_.
Thanks to `@EdwardBlair <https://github.com/EdwardBlair>`_ for suggesting the improvement.
- Fix type annotations for ``.provides``.
Thanks to `Thiago Hiromi @thiromi <https://github.com/thiromi>`_ for the fix
`PR #491 <https://github.com/ets-labs/python-dependency-injector/pull/491>`_.
- Fix environment variables interpolation examples in configuration provider docs ``{$ENV} -> ${ENV}``.
Thanks to `Felipe Rubio @krouw <https://github.com/krouw>`_ for reporting the issue and
fixing yaml example `PR #494 <https://github.com/ets-labs/python-dependency-injector/pull/494>`_.
- Fix ``@containers.copy()`` decorator to respect dependencies on parent providers. - Fix ``@containers.copy()`` decorator to respect dependencies on parent providers.
See issue `#477 <https://github.com/ets-labs/python-dependency-injector/issues/477>`_. See issue `#477 <https://github.com/ets-labs/python-dependency-injector/issues/477>`_.
Thanks to `Andrey Torsunov @gtors <https://github.com/gtors>`_ for reporting the issue. Thanks to `Andrey Torsunov @gtors <https://github.com/gtors>`_ for reporting the issue.
- Fix typing stub for ``container.override_providers()`` to accept other types besides ``Provider``. - Fix typing stub for ``container.override_providers()`` to accept other types besides ``Provider``.
- Fix runtime issue with generic typing in resource initializer classes ``resources.Resource``
and ``resources.AsyncResource``.
See issue `#488 <https://github.com/ets-labs/python-dependency-injector/issues/488>`_.
Thanks to `@EdwardBlair <https://github.com/EdwardBlair>`_ for reporting the issue.
4.35.3
------
- *This release was removed from PyPI. It was inconsistently published because project has
reached a PyPI size limit. Changes from this release are published on PyPI in next version.*
4.35.2 4.35.2
------ ------

View File

@ -50,9 +50,9 @@ where ``examples/providers/configuration/config.ini`` is:
.. code-block:: ini .. code-block:: ini
[section] [section]
option1 = {$ENV_VAR} option1 = ${ENV_VAR}
option2 = {$ENV_VAR}/path option2 = ${ENV_VAR}/path
option3 = {$ENV_VAR:default} option3 = ${ENV_VAR:default}
See also: :ref:`configuration-envs-interpolation`. See also: :ref:`configuration-envs-interpolation`.
@ -77,9 +77,9 @@ where ``examples/providers/configuration/config.yml`` is:
.. code-block:: ini .. code-block:: ini
section: section:
option1: {$ENV_VAR} option1: ${ENV_VAR}
option2: {$ENV_VAR}/path option2: ${ENV_VAR}/path
option3: {$ENV_VAR:default} option3: ${ENV_VAR:default}
See also: :ref:`configuration-envs-interpolation`. See also: :ref:`configuration-envs-interpolation`.
@ -208,7 +208,7 @@ variable ``ENV_NAME`` is undefined, configuration provider will substitute value
.. code-block:: ini .. code-block:: ini
[section] [section]
option = {$ENV_NAME:default} option = ${ENV_NAME:default}
If you'd like to specify a default value for environment variable inside of the application you can use If you'd like to specify a default value for environment variable inside of the application you can use
``os.environ.setdefault()``. ``os.environ.setdefault()``.
@ -380,7 +380,7 @@ an undefined environment variable without a default value.
.. code-block:: ini .. code-block:: ini
section: section:
option: {$UNDEFINED} option: ${UNDEFINED}
.. code-block:: python .. code-block:: python

View File

@ -148,13 +148,11 @@ provider with two peculiarities:
Factory aggregate Factory aggregate
----------------- -----------------
:py:class:`FactoryAggregate` provider aggregates multiple factories. When you call the :py:class:`FactoryAggregate` provider aggregates multiple factories.
``FactoryAggregate`` it delegates the call to one of the factories.
The aggregated factories are associated with the string names. When you call the The aggregated factories are associated with the string keys. When you call the
``FactoryAggregate`` you have to provide one of the these names as a first argument. ``FactoryAggregate`` you have to provide one of the these keys as a first argument.
``FactoryAggregate`` looks for the factory with a matching name and delegates it the work. The ``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
rest of the arguments are passed to the delegated ``Factory``.
.. image:: images/factory_aggregate.png .. image:: images/factory_aggregate.png
:width: 100% :width: 100%
@ -165,12 +163,12 @@ rest of the arguments are passed to the delegated ``Factory``.
: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 of the You can get a dictionary of the aggregated factories using the ``.factories`` attribute.
``FactoryAggregate``. To get a game factories dictionary from the previous example you can use To get a game factories dictionary from the previous example you can use
``game_factory.factories`` attribute. ``game_factory.factories`` 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")``.
.. note:: .. note::
You can not override the ``FactoryAggregate`` provider. You can not override the ``FactoryAggregate`` provider.
@ -178,4 +176,22 @@ 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:
.. code-block:: python
providers.FactoryAggregate({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
Example:
.. literalinclude:: ../../examples/providers/factory_aggregate_non_string_keys.py
:language: python
:lines: 3-
:emphasize-lines: 30-33,39-40
.. disqus:: .. disqus::

View File

@ -0,0 +1,45 @@
"""`FactoryAggregate` provider with non-string keys example."""
from dependency_injector import containers, providers
class Command:
...
class CommandA(Command):
...
class CommandB(Command):
...
class Handler:
...
class HandlerA(Handler):
...
class HandlerB(Handler):
...
class Container(containers.DeclarativeContainer):
handler_factory = providers.FactoryAggregate({
CommandA: providers.Factory(HandlerA),
CommandB: providers.Factory(HandlerB),
})
if __name__ == "__main__":
container = Container()
handler_a = container.handler_factory(CommandA)
handler_b = container.handler_factory(CommandB)
assert isinstance(handler_a, HandlerA)
assert isinstance(handler_b, HandlerB)

View File

@ -1,6 +1,6 @@
"""Top-level package.""" """Top-level package."""
__version__ = '4.35.3' __version__ = '4.36.0'
"""Version number. """Version number.
:type: str :type: str

File diff suppressed because it is too large Load Diff

View File

@ -142,7 +142,7 @@ cdef class FactoryDelegate(Delegate):
cdef class FactoryAggregate(Provider): cdef class FactoryAggregate(Provider):
cdef dict __factories cdef dict __factories
cdef Factory __get_factory(self, str factory_name) cdef Factory __get_factory(self, object factory_name)
# Singleton providers # Singleton providers

View File

@ -84,6 +84,7 @@ class Provider(Generic[T]):
class Object(Provider[T]): class Object(Provider[T]):
def __init__(self, provides: Optional[T] = None) -> None: ... def __init__(self, provides: Optional[T] = None) -> None: ...
@property
def provides(self) -> Optional[T]: ... def provides(self) -> Optional[T]: ...
def set_provides(self, provides: Optional[T]) -> Object: ... def set_provides(self, provides: Optional[T]) -> Object: ...
@ -144,7 +145,7 @@ class DependenciesContainer(Object):
class Callable(Provider[T]): class Callable(Provider[T]):
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ... def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@property @property
def provides(self) -> Optional[T]: ... def provides(self) -> Optional[_Callable[..., T]]: ...
def set_provides(self, provides: Optional[_Callable[..., T]]) -> Callable[T]: ... def set_provides(self, provides: Optional[_Callable[..., T]]) -> Callable[T]: ...
@property @property
def args(self) -> Tuple[Injection]: ... def args(self) -> Tuple[Injection]: ...
@ -249,9 +250,9 @@ class Factory(Provider[T]):
provided_type: Optional[Type] provided_type: Optional[Type]
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ... def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@property @property
def cls(self) -> T: ... def cls(self) -> Type[T]: ...
@property @property
def provides(self) -> T: ... def provides(self) -> Optional[_Callable[..., T]]: ...
def set_provides(self, provides: Optional[_Callable[..., T]]) -> Factory[T]: ... def set_provides(self, provides: Optional[_Callable[..., T]]) -> Factory[T]: ...
@property @property
def args(self) -> Tuple[Injection]: ... def args(self) -> Tuple[Injection]: ...
@ -281,28 +282,28 @@ class FactoryDelegate(Delegate):
def __init__(self, factory: Factory): ... def __init__(self, factory: Factory): ...
class FactoryAggregate(Provider): class FactoryAggregate(Provider[T]):
def __init__(self, **factories: Factory): ... def __init__(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]): ...
def __getattr__(self, factory_name: str) -> Factory: ... def __getattr__(self, factory_name: Any) -> Factory[T]: ...
@overload @overload
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Any: ... def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> T: ...
@overload @overload
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Awaitable[Any]: ... def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
def async_(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Awaitable[Any]: ... def async_(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
@property @property
def factories(self) -> _Dict[str, Factory]: ... def factories(self) -> _Dict[Any, Factory[T]]: ...
def set_factories(self, **factories: Factory) -> FactoryAggregate: ... def set_factories(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]) -> FactoryAggregate[T]: ...
class BaseSingleton(Provider[T]): class BaseSingleton(Provider[T]):
provided_type = Optional[Type] provided_type = Optional[Type]
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ... def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@property @property
def cls(self) -> T: ... def cls(self) -> Type[T]: ...
@property @property
def provides(self) -> T: ... def provides(self) -> Optional[_Callable[..., T]]: ...
def set_provides(self, provides: Optional[_Callable[..., T]]) -> BaseSingleton[T]: ... def set_provides(self, provides: Optional[_Callable[..., T]]) -> BaseSingleton[T]: ...
@property @property
def args(self) -> Tuple[Injection]: ... def args(self) -> Tuple[Injection]: ...

View File

@ -2486,10 +2486,10 @@ cdef class FactoryAggregate(Provider):
__IS_DELEGATED__ = True __IS_DELEGATED__ = True
def __init__(self, **factories): def __init__(self, factories_dict_=None, **factories_kwargs):
"""Initialize provider.""" """Initialize provider."""
self.__factories = {} self.__factories = {}
self.set_factories(**factories) self.set_factories(factories_dict_, **factories_kwargs)
super(FactoryAggregate, self).__init__() super(FactoryAggregate, self).__init__()
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
@ -2499,7 +2499,7 @@ 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_factories(deepcopy(self.factories, memo))
self._copy_overridings(copied, memo) self._copy_overridings(copied, memo)
@ -2521,13 +2521,23 @@ cdef class FactoryAggregate(Provider):
"""Return dictionary of factories, read-only.""" """Return dictionary of factories, read-only."""
return self.__factories return self.__factories
def set_factories(self, **factories): def set_factories(self, factories_dict_=None, **factories_kwargs):
"""Set factories.""" """Set factories."""
factories = {}
factories.update(factories_kwargs)
if factories_dict_:
factories.update(factories_dict_)
for factory in factories.values(): for factory in factories.values():
if isinstance(factory, Factory) is False: if isinstance(factory, Factory) is False:
raise Error( raise Error(
'{0} can aggregate only instances of {1}, given - {2}' '{0} can aggregate only instances of {1}, given - {2}'.format(
.format(self.__class__, Factory, factory)) self.__class__,
Factory,
factory,
),
)
self.__factories = factories self.__factories = factories
return self return self
@ -2539,8 +2549,7 @@ cdef class FactoryAggregate(Provider):
:return: Overriding context. :return: Overriding context.
:rtype: :py:class:`OverridingContext` :rtype: :py:class:`OverridingContext`
""" """
raise Error( raise Error('{0} providers could not be overridden'.format(self.__class__))
'{0} providers could not be overridden'.format(self.__class__))
@property @property
def related(self): def related(self):
@ -2561,12 +2570,10 @@ cdef class FactoryAggregate(Provider):
return self.__get_factory(factory_name)(*args, **kwargs) return self.__get_factory(factory_name)(*args, **kwargs)
cdef Factory __get_factory(self, str factory_name): cdef Factory __get_factory(self, object factory_key):
if factory_name not in self.__factories: if factory_key not in self.__factories:
raise NoSuchProviderError( raise NoSuchProviderError('{0} does not contain factory with name {1}'.format(self, factory_key))
'{0} does not contain factory with name {1}'.format( return <Factory> self.__factories[factory_key]
self, factory_name))
return <Factory> self.__factories[factory_name]
cdef class BaseSingleton(Provider): cdef class BaseSingleton(Provider):

View File

@ -1,42 +1,27 @@
"""Resources module.""" """Resources module."""
import abc import abc
import sys from typing import TypeVar, Generic, Optional
from typing import TypeVar, Generic
if sys.version_info < (3, 7):
from typing import GenericMeta
else:
class GenericMeta(type):
...
T = TypeVar('T') T = TypeVar('T')
class ResourceMeta(GenericMeta, abc.ABCMeta): class Resource(Generic[T], metaclass=abc.ABCMeta):
def __getitem__(cls, item):
# Spike for Python 3.6
return cls(item)
class Resource(Generic[T], metaclass=ResourceMeta):
@abc.abstractmethod @abc.abstractmethod
def init(self, *args, **kwargs) -> T: def init(self, *args, **kwargs) -> Optional[T]:
... ...
@abc.abstractmethod def shutdown(self, resource: Optional[T]) -> None:
def shutdown(self, resource: T) -> None:
... ...
class AsyncResource(Generic[T], metaclass=ResourceMeta): class AsyncResource(Generic[T], metaclass=abc.ABCMeta):
@abc.abstractmethod @abc.abstractmethod
async def init(self, *args, **kwargs) -> T: async def init(self, *args, **kwargs) -> Optional[T]:
... ...
@abc.abstractmethod async def shutdown(self, resource: Optional[T]) -> None:
async def shutdown(self, resource: T) -> None:
... ...

View File

@ -1,4 +1,4 @@
from typing import Tuple, Any, Dict from typing import Callable, Optional, Tuple, Any, Dict, Type
from dependency_injector import providers from dependency_injector import providers
@ -56,3 +56,13 @@ provider9 = providers.Callable(Cat)
async def _async9() -> None: async def _async9() -> None:
animal1: Animal = await provider9(1, 2, 3, b='1', c=2, e=0.0) # type: ignore animal1: Animal = await provider9(1, 2, 3, b='1', c=2, e=0.0) # type: ignore
animal2: Animal = await provider9.async_(1, 2, 3, b='1', c=2, e=0.0) animal2: Animal = await provider9.async_(1, 2, 3, b='1', c=2, e=0.0)
# Test 10: to check the .provides
provider10 = providers.Callable(Cat)
provides10: Optional[Callable[..., Cat]] = provider10.provides
assert provides10 is Cat
# Test 11: to check the .provides for explicit typevar
provider11 = providers.Callable[Animal](Cat)
provides11: Optional[Callable[..., Animal]] = provider11.provides
assert provides11 is Cat

View File

@ -1,5 +1,6 @@
from dependency_injector import providers from typing import Optional
from dependency_injector import providers
# Test 1: to check the return type # Test 1: to check the return type
provider1 = providers.Delegate(providers.Provider()) provider1 = providers.Delegate(providers.Provider())
@ -10,3 +11,7 @@ provider2 = providers.Delegate(providers.Provider())
async def _async2() -> None: async def _async2() -> None:
var1: providers.Provider = await provider2() # type: ignore var1: providers.Provider = await provider2() # type: ignore
var2: providers.Provider = await provider2.async_() var2: providers.Provider = await provider2.async_()
# Test 3: to check class type from provider
provider3 = providers.Delegate(providers.Provider())
provided_provides: Optional[providers.Provider] = provider3.provides

View File

@ -1,4 +1,4 @@
from typing import Tuple, Any, Dict from typing import Callable, Optional, Tuple, Any, Dict, Type
from dependency_injector import providers from dependency_injector import providers
@ -55,13 +55,26 @@ animal7: Animal = provider7(1, 2, 3, b='1', c=2, e=0.0)
provider8 = providers.FactoryDelegate(providers.Factory(object)) provider8 = providers.FactoryDelegate(providers.Factory(object))
# Test 9: to check FactoryAggregate provider # Test 9: to check FactoryAggregate provider
provider9 = providers.FactoryAggregate( provider9: providers.FactoryAggregate[str] = providers.FactoryAggregate(
a=providers.Factory(object), a=providers.Factory(str, "str1"),
b=providers.Factory(object), b=providers.Factory(str, "str2"),
) )
factory_a_9: providers.Factory = provider9.a factory_a_9: providers.Factory[str] = provider9.a
factory_b_9: providers.Factory = provider9.b factory_b_9: providers.Factory[str] = provider9.b
val9: Any = provider9('a') val9: str = provider9('a')
provider9_set_non_string_keys: providers.FactoryAggregate[str] = providers.FactoryAggregate()
provider9_set_non_string_keys.set_factories({Cat: providers.Factory(str, "str")})
factory_set_non_string_9: providers.Factory[str] = provider9_set_non_string_keys.factories[Cat]
provider9_new_non_string_keys: providers.FactoryAggregate[str] = providers.FactoryAggregate(
{Cat: providers.Factory(str, "str")},
)
factory_new_non_string_9: providers.Factory[str] = provider9_new_non_string_keys.factories[Cat]
provider9_no_explicit_typing = providers.FactoryAggregate(a=providers.Factory(str, "str"))
provider9_no_explicit_typing_factory: providers.Factory[str] = provider9_no_explicit_typing.factories["a"]
provider9_no_explicit_typing_object: str = provider9_no_explicit_typing("a")
# Test 10: to check the explicit typing # Test 10: to check the explicit typing
factory10: providers.Provider[Animal] = providers.Factory(Cat) factory10: providers.Provider[Animal] = providers.Factory(Cat)
@ -72,3 +85,17 @@ provider11 = providers.Factory(Cat)
async def _async11() -> None: async def _async11() -> None:
animal1: Animal = await provider11(1, 2, 3, b='1', c=2, e=0.0) # type: ignore animal1: Animal = await provider11(1, 2, 3, b='1', c=2, e=0.0) # type: ignore
animal2: Animal = await provider11.async_(1, 2, 3, b='1', c=2, e=0.0) animal2: Animal = await provider11.async_(1, 2, 3, b='1', c=2, e=0.0)
# Test 12: to check class type from .provides
provider12 = providers.Factory(Cat)
provided_cls12: Type[Animal] = provider12.cls
assert issubclass(provided_cls12, Animal)
provided_provides12: Optional[Callable[..., Animal]] = provider12.provides
assert provided_provides12 is not None and provided_provides12() == Cat()
# Test 13: to check class from .provides with explicit typevar
provider13 = providers.Factory[Animal](Cat)
provided_cls13: Type[Animal] = provider13.cls
assert issubclass(provided_cls13, Animal)
provided_provides13: Optional[Callable[..., Animal]] = provider13.provides
assert provided_provides13 is not None and provided_provides13() == Cat()

View File

@ -1,3 +1,5 @@
from typing import Type, Optional
from dependency_injector import providers from dependency_injector import providers
@ -17,3 +19,7 @@ provider3 = providers.Object(int(3))
async def _async3() -> None: async def _async3() -> None:
var1: int = await provider3() # type: ignore var1: int = await provider3() # type: ignore
var2: int = await provider3.async_() var2: int = await provider3.async_()
# Test 4: to check class type from provider
provider4 = providers.Object(int('1'))
provided_provides: Optional[int] = provider4.provides

View File

@ -1,4 +1,4 @@
from typing import List, Iterator, Generator, AsyncIterator, AsyncGenerator from typing import List, Iterator, Generator, AsyncIterator, AsyncGenerator, Optional
from dependency_injector import providers, resources from dependency_injector import providers, resources
@ -35,7 +35,7 @@ class MyResource4(resources.Resource[List[int]]):
def init(self, *args, **kwargs) -> List[int]: def init(self, *args, **kwargs) -> List[int]:
return [] return []
def shutdown(self, resource: List[int]) -> None: def shutdown(self, resource: Optional[List[int]]) -> None:
... ...
@ -87,7 +87,7 @@ class MyResource8(resources.AsyncResource[List[int]]):
async def init(self, *args, **kwargs) -> List[int]: async def init(self, *args, **kwargs) -> List[int]:
return [] return []
async def shutdown(self, resource: List[int]) -> None: async def shutdown(self, resource: Optional[List[int]]) -> None:
... ...

View File

@ -1,4 +1,4 @@
from typing import Tuple, Any, Dict from typing import Callable, Optional, Tuple, Any, Dict, Type
from dependency_injector import providers from dependency_injector import providers
@ -75,3 +75,17 @@ provider13 = providers.Singleton(Cat)
async def _async13() -> None: async def _async13() -> None:
animal1: Animal = await provider13(1, 2, 3, b='1', c=2, e=0.0) # type: ignore animal1: Animal = await provider13(1, 2, 3, b='1', c=2, e=0.0) # type: ignore
animal2: Animal = await provider13.async_(1, 2, 3, b='1', c=2, e=0.0) animal2: Animal = await provider13.async_(1, 2, 3, b='1', c=2, e=0.0)
# Test 14: to check class from .provides
provider14 = providers.Singleton(Cat)
provided_cls14: Type[Cat] = provider14.cls
assert issubclass(provided_cls14, Cat)
provided_provides14: Optional[Callable[..., Cat]] = provider14.provides
assert provided_provides14 is not None and provided_provides14() == Cat()
# Test 15: to check class from .provides with explicit typevar
provider15 = providers.Singleton[Animal](Cat)
provided_cls15: Type[Animal] = provider15.cls
assert issubclass(provided_cls15, Animal)
provided_provides15: Optional[Callable[..., Animal]] = provider15.provides
assert provided_provides15 is not None and provided_provides15() == Cat()

View File

@ -5,6 +5,7 @@ import sys
import unittest import unittest
from dependency_injector import ( from dependency_injector import (
containers,
providers, providers,
errors, errors,
) )
@ -498,7 +499,8 @@ class FactoryAggregateTests(unittest.TestCase):
self.example_b_factory = providers.Factory(self.ExampleB) self.example_b_factory = providers.Factory(self.ExampleB)
self.factory_aggregate = providers.FactoryAggregate( self.factory_aggregate = providers.FactoryAggregate(
example_a=self.example_a_factory, example_a=self.example_a_factory,
example_b=self.example_b_factory) example_b=self.example_b_factory,
)
def test_is_provider(self): def test_is_provider(self):
self.assertTrue(providers.is_provider(self.factory_aggregate)) self.assertTrue(providers.is_provider(self.factory_aggregate))
@ -506,6 +508,35 @@ class FactoryAggregateTests(unittest.TestCase):
def test_is_delegated_provider(self): def test_is_delegated_provider(self):
self.assertTrue(providers.is_delegated(self.factory_aggregate)) self.assertTrue(providers.is_delegated(self.factory_aggregate))
def test_init_with_non_string_keys(self):
factory = providers.FactoryAggregate({
self.ExampleA: self.example_a_factory,
self.ExampleB: self.example_b_factory,
})
object_a = factory(self.ExampleA, 1, 2, init_arg3=3, init_arg4=4)
object_b = factory(self.ExampleB, 11, 22, init_arg3=33, init_arg4=44)
self.assertIsInstance(object_a, self.ExampleA)
self.assertEqual(object_a.init_arg1, 1)
self.assertEqual(object_a.init_arg2, 2)
self.assertEqual(object_a.init_arg3, 3)
self.assertEqual(object_a.init_arg4, 4)
self.assertIsInstance(object_b, self.ExampleB)
self.assertEqual(object_b.init_arg1, 11)
self.assertEqual(object_b.init_arg2, 22)
self.assertEqual(object_b.init_arg3, 33)
self.assertEqual(object_b.init_arg4, 44)
self.assertEqual(
factory.factories,
{
self.ExampleA: self.example_a_factory,
self.ExampleB: self.example_b_factory,
},
)
def test_init_with_not_a_factory(self): def test_init_with_not_a_factory(self):
with self.assertRaises(errors.Error): with self.assertRaises(errors.Error):
providers.FactoryAggregate( providers.FactoryAggregate(
@ -528,7 +559,37 @@ class FactoryAggregateTests(unittest.TestCase):
self.assertIsInstance(provider('example_a'), self.ExampleA) self.assertIsInstance(provider('example_a'), self.ExampleA)
self.assertIsInstance(provider('example_b'), self.ExampleB) self.assertIsInstance(provider('example_b'), self.ExampleB)
def test_set_provides_returns_self(self): def test_set_factories_with_non_string_keys(self):
factory = providers.FactoryAggregate()
factory.set_factories({
self.ExampleA: self.example_a_factory,
self.ExampleB: self.example_b_factory,
})
object_a = factory(self.ExampleA, 1, 2, init_arg3=3, init_arg4=4)
object_b = factory(self.ExampleB, 11, 22, init_arg3=33, init_arg4=44)
self.assertIsInstance(object_a, self.ExampleA)
self.assertEqual(object_a.init_arg1, 1)
self.assertEqual(object_a.init_arg2, 2)
self.assertEqual(object_a.init_arg3, 3)
self.assertEqual(object_a.init_arg4, 4)
self.assertIsInstance(object_b, self.ExampleB)
self.assertEqual(object_b.init_arg1, 11)
self.assertEqual(object_b.init_arg2, 22)
self.assertEqual(object_b.init_arg3, 33)
self.assertEqual(object_b.init_arg4, 44)
self.assertEqual(
factory.factories,
{
self.ExampleA: self.example_a_factory,
self.ExampleB: self.example_b_factory,
},
)
def test_set_factories_returns_self(self):
provider = providers.FactoryAggregate() provider = providers.FactoryAggregate()
self.assertIs(provider.set_factories(example_a=self.example_a_factory), provider) self.assertIs(provider.set_factories(example_a=self.example_a_factory), provider)
@ -603,6 +664,24 @@ class FactoryAggregateTests(unittest.TestCase):
self.assertIsInstance(self.factory_aggregate.example_b, type(provider_copy.example_b)) self.assertIsInstance(self.factory_aggregate.example_b, type(provider_copy.example_b))
self.assertIs(self.factory_aggregate.example_b.cls, provider_copy.example_b.cls) self.assertIs(self.factory_aggregate.example_b.cls, provider_copy.example_b.cls)
def test_deepcopy_with_non_string_keys(self):
factory_aggregate = providers.FactoryAggregate({
self.ExampleA: self.example_a_factory,
self.ExampleB: self.example_b_factory,
})
provider_copy = providers.deepcopy(factory_aggregate)
self.assertIsNot(factory_aggregate, provider_copy)
self.assertIsInstance(provider_copy, type(factory_aggregate))
self.assertIsNot(factory_aggregate.factories[self.ExampleA], provider_copy.factories[self.ExampleA])
self.assertIsInstance(factory_aggregate.factories[self.ExampleA], type(provider_copy.factories[self.ExampleA]))
self.assertIs(factory_aggregate.factories[self.ExampleA].cls, provider_copy.factories[self.ExampleA].cls)
self.assertIsNot(factory_aggregate.factories[self.ExampleB], provider_copy.factories[self.ExampleB])
self.assertIsInstance(factory_aggregate.factories[self.ExampleB], type(provider_copy.factories[self.ExampleB]))
self.assertIs(factory_aggregate.factories[self.ExampleB].cls, provider_copy.factories[self.ExampleB].cls)
def test_repr(self): def test_repr(self):
self.assertEqual(repr(self.factory_aggregate), self.assertEqual(repr(self.factory_aggregate),
'<dependency_injector.providers.' '<dependency_injector.providers.'

View File

@ -1,8 +1,9 @@
"""Dependency injector resource provider unit tests.""" """Dependency injector resource provider unit tests."""
import asyncio import asyncio
import inspect
import unittest import unittest
from typing import Any
from dependency_injector import containers, providers, resources, errors from dependency_injector import containers, providers, resources, errors
@ -145,6 +146,35 @@ class ResourceTests(unittest.TestCase):
self.assertEqual(TestResource.init_counter, 2) self.assertEqual(TestResource.init_counter, 2)
self.assertEqual(TestResource.shutdown_counter, 2) self.assertEqual(TestResource.shutdown_counter, 2)
def test_init_class_generic_typing(self):
# See issue: https://github.com/ets-labs/python-dependency-injector/issues/488
class TestDependency:
...
class TestResource(resources.Resource[TestDependency]):
def init(self, *args: Any, **kwargs: Any) -> TestDependency:
return TestDependency()
def shutdown(self, resource: TestDependency) -> None: ...
self.assertTrue(issubclass(TestResource, resources.Resource))
def test_init_class_abc_init_definition_is_required(self):
class TestResource(resources.Resource):
...
with self.assertRaises(TypeError) as context:
TestResource()
self.assertIn("Can't instantiate abstract class TestResource", str(context.exception))
self.assertIn("init", str(context.exception))
def test_init_class_abc_shutdown_definition_is_not_required(self):
class TestResource(resources.Resource):
def init(self):
...
self.assertTrue(hasattr(TestResource(), 'shutdown'))
def test_init_not_callable(self): def test_init_not_callable(self):
provider = providers.Resource(1) provider = providers.Resource(1)
with self.assertRaises(errors.Error): with self.assertRaises(errors.Error):
@ -449,6 +479,36 @@ class AsyncResourceTest(AsyncTestCase):
self.assertEqual(TestResource.init_counter, 2) self.assertEqual(TestResource.init_counter, 2)
self.assertEqual(TestResource.shutdown_counter, 2) self.assertEqual(TestResource.shutdown_counter, 2)
def test_init_async_class_generic_typing(self):
# See issue: https://github.com/ets-labs/python-dependency-injector/issues/488
class TestDependency:
...
class TestAsyncResource(resources.AsyncResource[TestDependency]):
async def init(self, *args: Any, **kwargs: Any) -> TestDependency:
return TestDependency()
async def shutdown(self, resource: TestDependency) -> None: ...
self.assertTrue(issubclass(TestAsyncResource, resources.AsyncResource))
def test_init_async_class_abc_init_definition_is_required(self):
class TestAsyncResource(resources.AsyncResource):
...
with self.assertRaises(TypeError) as context:
TestAsyncResource()
self.assertIn("Can't instantiate abstract class TestAsyncResource", str(context.exception))
self.assertIn("init", str(context.exception))
def test_init_async_class_abc_shutdown_definition_is_not_required(self):
class TestAsyncResource(resources.AsyncResource):
async def init(self):
...
self.assertTrue(hasattr(TestAsyncResource(), 'shutdown'))
self.assertTrue(inspect.iscoroutinefunction(TestAsyncResource.shutdown))
def test_init_with_error(self): def test_init_with_error(self):
async def _init(): async def _init():
raise RuntimeError() raise RuntimeError()