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 cef6d35

Browse files
committedAug 25, 2021
Merge branch 'release/4.36.0' into master
2 parents 48df949 + 902913c commit cef6d35

20 files changed

+4269
-3850
lines changed
 

‎.coveragerc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ source = src/dependency_injector
33
omit = tests/unit
44
plugins = Cython.Coverage
55

6+
[report]
7+
show_missing = true
8+
69
[html]
710
directory=reports/unittests/

‎CONTRIBUTORS.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ Dependency Injector Contributors
1818
+ Shubhendra Singh Chauhan (withshubh)
1919
+ sonthonaxrk (sonthonaxrk)
2020
+ Ngo Thanh Loi (Leonn) (loingo95)
21+
+ Thiago Hiromi (thiromi)
22+
+ Felipe Rubio (krouw)

‎docs/main/changelog.rst

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

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

1737
4.35.2
1838
------

‎docs/providers/configuration.rst

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ where ``examples/providers/configuration/config.ini`` is:
5050
.. code-block:: ini
5151
5252
[section]
53-
option1 = {$ENV_VAR}
54-
option2 = {$ENV_VAR}/path
55-
option3 = {$ENV_VAR:default}
53+
option1 = ${ENV_VAR}
54+
option2 = ${ENV_VAR}/path
55+
option3 = ${ENV_VAR:default}
5656
5757
See also: :ref:`configuration-envs-interpolation`.
5858

@@ -77,9 +77,9 @@ where ``examples/providers/configuration/config.yml`` is:
7777
.. code-block:: ini
7878
7979
section:
80-
option1: {$ENV_VAR}
81-
option2: {$ENV_VAR}/path
82-
option3: {$ENV_VAR:default}
80+
option1: ${ENV_VAR}
81+
option2: ${ENV_VAR}/path
82+
option3: ${ENV_VAR:default}
8383
8484
See also: :ref:`configuration-envs-interpolation`.
8585

@@ -208,7 +208,7 @@ variable ``ENV_NAME`` is undefined, configuration provider will substitute value
208208
.. code-block:: ini
209209
210210
[section]
211-
option = {$ENV_NAME:default}
211+
option = ${ENV_NAME:default}
212212
213213
If you'd like to specify a default value for environment variable inside of the application you can use
214214
``os.environ.setdefault()``.
@@ -380,7 +380,7 @@ an undefined environment variable without a default value.
380380
.. code-block:: ini
381381
382382
section:
383-
option: {$UNDEFINED}
383+
option: ${UNDEFINED}
384384
385385
.. code-block:: python
386386

‎docs/providers/factory.rst

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,11 @@ provider with two peculiarities:
148148
Factory aggregate
149149
-----------------
150150

151-
:py:class:`FactoryAggregate` provider aggregates multiple factories. When you call the
152-
``FactoryAggregate`` it delegates the call to one of the factories.
151+
:py:class:`FactoryAggregate` provider aggregates multiple factories.
153152

154-
The aggregated factories are associated with the string names. When you call the
155-
``FactoryAggregate`` you have to provide one of the these names as a first argument.
156-
``FactoryAggregate`` looks for the factory with a matching name and delegates it the work. The
157-
rest of the arguments are passed to the delegated ``Factory``.
153+
The aggregated factories are associated with the string keys. When you call the
154+
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
155+
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
158156

159157
.. image:: images/factory_aggregate.png
160158
:width: 100%
@@ -165,17 +163,35 @@ rest of the arguments are passed to the delegated ``Factory``.
165163
:lines: 3-
166164
:emphasize-lines: 33-37,47
167165

168-
You can get a dictionary of the aggregated factories using the ``.factories`` attribute of the
169-
``FactoryAggregate``. To get a game factories dictionary from the previous example you can use
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
170168
``game_factory.factories`` attribute.
171169

172170
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the
173-
previous example you can do ``chess = game_factory.chess('John', 'Jane')``.
171+
previous example you can do ``chess = game_factory.chess("John", "Jane")``.
174172

175173
.. note::
176174
You can not override the ``FactoryAggregate`` provider.
177175

178176
.. note::
179177
When you inject the ``FactoryAggregate`` provider it is passed "as is".
180178

179+
To use non-string keys or keys with ``.`` and ``-`` you can provide a dictionary as a positional argument:
180+
181+
.. code-block:: python
182+
183+
providers.FactoryAggregate({
184+
SomeClass: providers.Factory(...),
185+
"key.with.periods": providers.Factory(...),
186+
"key-with-dashes": providers.Factory(...),
187+
})
188+
189+
Example:
190+
191+
.. literalinclude:: ../../examples/providers/factory_aggregate_non_string_keys.py
192+
:language: python
193+
:lines: 3-
194+
:emphasize-lines: 30-33,39-40
195+
196+
181197
.. disqus::
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""`FactoryAggregate` provider with non-string keys example."""
2+
3+
from dependency_injector import containers, providers
4+
5+
6+
class Command:
7+
...
8+
9+
10+
class CommandA(Command):
11+
...
12+
13+
14+
class CommandB(Command):
15+
...
16+
17+
18+
class Handler:
19+
...
20+
21+
22+
class HandlerA(Handler):
23+
...
24+
25+
26+
class HandlerB(Handler):
27+
...
28+
29+
30+
class Container(containers.DeclarativeContainer):
31+
32+
handler_factory = providers.FactoryAggregate({
33+
CommandA: providers.Factory(HandlerA),
34+
CommandB: providers.Factory(HandlerB),
35+
})
36+
37+
38+
if __name__ == "__main__":
39+
container = Container()
40+
41+
handler_a = container.handler_factory(CommandA)
42+
handler_b = container.handler_factory(CommandB)
43+
44+
assert isinstance(handler_a, HandlerA)
45+
assert isinstance(handler_b, HandlerB)

‎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.35.3'
3+
__version__ = '4.36.0'
44
"""Version number.
55
66
:type: str

‎src/dependency_injector/providers.c

Lines changed: 3903 additions & 3764 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ cdef class FactoryDelegate(Delegate):
142142
cdef class FactoryAggregate(Provider):
143143
cdef dict __factories
144144

145-
cdef Factory __get_factory(self, str factory_name)
145+
cdef Factory __get_factory(self, object factory_name)
146146

147147

148148
# Singleton providers

‎src/dependency_injector/providers.pyi

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class Provider(Generic[T]):
8484

8585
class Object(Provider[T]):
8686
def __init__(self, provides: Optional[T] = None) -> None: ...
87+
@property
8788
def provides(self) -> Optional[T]: ...
8889
def set_provides(self, provides: Optional[T]) -> Object: ...
8990

@@ -144,7 +145,7 @@ class DependenciesContainer(Object):
144145
class Callable(Provider[T]):
145146
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
146147
@property
147-
def provides(self) -> Optional[T]: ...
148+
def provides(self) -> Optional[_Callable[..., T]]: ...
148149
def set_provides(self, provides: Optional[_Callable[..., T]]) -> Callable[T]: ...
149150
@property
150151
def args(self) -> Tuple[Injection]: ...
@@ -249,9 +250,9 @@ class Factory(Provider[T]):
249250
provided_type: Optional[Type]
250251
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
251252
@property
252-
def cls(self) -> T: ...
253+
def cls(self) -> Type[T]: ...
253254
@property
254-
def provides(self) -> T: ...
255+
def provides(self) -> Optional[_Callable[..., T]]: ...
255256
def set_provides(self, provides: Optional[_Callable[..., T]]) -> Factory[T]: ...
256257
@property
257258
def args(self) -> Tuple[Injection]: ...
@@ -281,28 +282,28 @@ class FactoryDelegate(Delegate):
281282
def __init__(self, factory: Factory): ...
282283

283284

284-
class FactoryAggregate(Provider):
285-
def __init__(self, **factories: Factory): ...
286-
def __getattr__(self, factory_name: str) -> Factory: ...
285+
class FactoryAggregate(Provider[T]):
286+
def __init__(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]): ...
287+
def __getattr__(self, factory_name: Any) -> Factory[T]: ...
287288

288289
@overload
289-
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Any: ...
290+
def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> T: ...
290291
@overload
291-
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Awaitable[Any]: ...
292-
def async_(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Awaitable[Any]: ...
292+
def __call__(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
293+
def async_(self, factory_name: Any, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
293294

294295
@property
295-
def factories(self) -> _Dict[str, Factory]: ...
296-
def set_factories(self, **factories: Factory) -> FactoryAggregate: ...
296+
def factories(self) -> _Dict[Any, Factory[T]]: ...
297+
def set_factories(self, dict_: Optional[_Dict[Any, Factory[T]]] = None, **factories: Factory[T]) -> FactoryAggregate[T]: ...
297298

298299

299300
class BaseSingleton(Provider[T]):
300301
provided_type = Optional[Type]
301302
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
302303
@property
303-
def cls(self) -> T: ...
304+
def cls(self) -> Type[T]: ...
304305
@property
305-
def provides(self) -> T: ...
306+
def provides(self) -> Optional[_Callable[..., T]]: ...
306307
def set_provides(self, provides: Optional[_Callable[..., T]]) -> BaseSingleton[T]: ...
307308
@property
308309
def args(self) -> Tuple[Injection]: ...

‎src/dependency_injector/providers.pyx

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,10 +2486,10 @@ cdef class FactoryAggregate(Provider):
24862486

24872487
__IS_DELEGATED__ = True
24882488

2489-
def __init__(self, **factories):
2489+
def __init__(self, factories_dict_=None, **factories_kwargs):
24902490
"""Initialize provider."""
24912491
self.__factories = {}
2492-
self.set_factories(**factories)
2492+
self.set_factories(factories_dict_, **factories_kwargs)
24932493
super(FactoryAggregate, self).__init__()
24942494

24952495
def __deepcopy__(self, memo):
@@ -2499,7 +2499,7 @@ cdef class FactoryAggregate(Provider):
24992499
return copied
25002500

25012501
copied = _memorized_duplicate(self, memo)
2502-
copied.set_factories(**deepcopy(self.factories, memo))
2502+
copied.set_factories(deepcopy(self.factories, memo))
25032503

25042504
self._copy_overridings(copied, memo)
25052505

@@ -2521,13 +2521,23 @@ cdef class FactoryAggregate(Provider):
25212521
"""Return dictionary of factories, read-only."""
25222522
return self.__factories
25232523

2524-
def set_factories(self, **factories):
2524+
def set_factories(self, factories_dict_=None, **factories_kwargs):
25252525
"""Set factories."""
2526+
factories = {}
2527+
factories.update(factories_kwargs)
2528+
if factories_dict_:
2529+
factories.update(factories_dict_)
2530+
25262531
for factory in factories.values():
25272532
if isinstance(factory, Factory) is False:
25282533
raise Error(
2529-
'{0} can aggregate only instances of {1}, given - {2}'
2530-
.format(self.__class__, Factory, factory))
2534+
'{0} can aggregate only instances of {1}, given - {2}'.format(
2535+
self.__class__,
2536+
Factory,
2537+
factory,
2538+
),
2539+
)
2540+
25312541
self.__factories = factories
25322542
return self
25332543

@@ -2539,8 +2549,7 @@ cdef class FactoryAggregate(Provider):
25392549
:return: Overriding context.
25402550
:rtype: :py:class:`OverridingContext`
25412551
"""
2542-
raise Error(
2543-
'{0} providers could not be overridden'.format(self.__class__))
2552+
raise Error('{0} providers could not be overridden'.format(self.__class__))
25442553

25452554
@property
25462555
def related(self):
@@ -2561,12 +2570,10 @@ cdef class FactoryAggregate(Provider):
25612570

25622571
return self.__get_factory(factory_name)(*args, **kwargs)
25632572

2564-
cdef Factory __get_factory(self, str factory_name):
2565-
if factory_name not in self.__factories:
2566-
raise NoSuchProviderError(
2567-
'{0} does not contain factory with name {1}'.format(
2568-
self, factory_name))
2569-
return <Factory> self.__factories[factory_name]
2573+
cdef Factory __get_factory(self, object factory_key):
2574+
if factory_key not in self.__factories:
2575+
raise NoSuchProviderError('{0} does not contain factory with name {1}'.format(self, factory_key))
2576+
return <Factory> self.__factories[factory_key]
25702577

25712578

25722579
cdef class BaseSingleton(Provider):

‎src/dependency_injector/resources.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,27 @@
11
"""Resources module."""
22

33
import abc
4-
import sys
5-
from typing import TypeVar, Generic
6-
7-
if sys.version_info < (3, 7):
8-
from typing import GenericMeta
9-
else:
10-
class GenericMeta(type):
11-
...
4+
from typing import TypeVar, Generic, Optional
125

136

147
T = TypeVar('T')
158

169

17-
class ResourceMeta(GenericMeta, abc.ABCMeta):
18-
def __getitem__(cls, item):
19-
# Spike for Python 3.6
20-
return cls(item)
21-
22-
23-
class Resource(Generic[T], metaclass=ResourceMeta):
10+
class Resource(Generic[T], metaclass=abc.ABCMeta):
2411

2512
@abc.abstractmethod
26-
def init(self, *args, **kwargs) -> T:
13+
def init(self, *args, **kwargs) -> Optional[T]:
2714
...
2815

29-
@abc.abstractmethod
30-
def shutdown(self, resource: T) -> None:
16+
def shutdown(self, resource: Optional[T]) -> None:
3117
...
3218

3319

34-
class AsyncResource(Generic[T], metaclass=ResourceMeta):
20+
class AsyncResource(Generic[T], metaclass=abc.ABCMeta):
3521

3622
@abc.abstractmethod
37-
async def init(self, *args, **kwargs) -> T:
23+
async def init(self, *args, **kwargs) -> Optional[T]:
3824
...
3925

40-
@abc.abstractmethod
41-
async def shutdown(self, resource: T) -> None:
26+
async def shutdown(self, resource: Optional[T]) -> None:
4227
...

‎tests/typing/callable.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Tuple, Any, Dict
1+
from typing import Callable, Optional, Tuple, Any, Dict, Type
22

33
from dependency_injector import providers
44

@@ -56,3 +56,13 @@ def create(cls) -> Animal:
5656
async def _async9() -> None:
5757
animal1: Animal = await provider9(1, 2, 3, b='1', c=2, e=0.0) # type: ignore
5858
animal2: Animal = await provider9.async_(1, 2, 3, b='1', c=2, e=0.0)
59+
60+
# Test 10: to check the .provides
61+
provider10 = providers.Callable(Cat)
62+
provides10: Optional[Callable[..., Cat]] = provider10.provides
63+
assert provides10 is Cat
64+
65+
# Test 11: to check the .provides for explicit typevar
66+
provider11 = providers.Callable[Animal](Cat)
67+
provides11: Optional[Callable[..., Animal]] = provider11.provides
68+
assert provides11 is Cat

‎tests/typing/delegate.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from dependency_injector import providers
1+
from typing import Optional
22

3+
from dependency_injector import providers
34

45
# Test 1: to check the return type
56
provider1 = providers.Delegate(providers.Provider())
@@ -10,3 +11,7 @@
1011
async def _async2() -> None:
1112
var1: providers.Provider = await provider2() # type: ignore
1213
var2: providers.Provider = await provider2.async_()
14+
15+
# Test 3: to check class type from provider
16+
provider3 = providers.Delegate(providers.Provider())
17+
provided_provides: Optional[providers.Provider] = provider3.provides

‎tests/typing/factory.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Tuple, Any, Dict
1+
from typing import Callable, Optional, Tuple, Any, Dict, Type
22

33
from dependency_injector import providers
44

@@ -55,13 +55,26 @@ def create(cls) -> Animal:
5555
provider8 = providers.FactoryDelegate(providers.Factory(object))
5656

5757
# Test 9: to check FactoryAggregate provider
58-
provider9 = providers.FactoryAggregate(
59-
a=providers.Factory(object),
60-
b=providers.Factory(object),
58+
provider9: providers.FactoryAggregate[str] = providers.FactoryAggregate(
59+
a=providers.Factory(str, "str1"),
60+
b=providers.Factory(str, "str2"),
6161
)
62-
factory_a_9: providers.Factory = provider9.a
63-
factory_b_9: providers.Factory = provider9.b
64-
val9: Any = provider9('a')
62+
factory_a_9: providers.Factory[str] = provider9.a
63+
factory_b_9: providers.Factory[str] = provider9.b
64+
val9: str = provider9('a')
65+
66+
provider9_set_non_string_keys: providers.FactoryAggregate[str] = providers.FactoryAggregate()
67+
provider9_set_non_string_keys.set_factories({Cat: providers.Factory(str, "str")})
68+
factory_set_non_string_9: providers.Factory[str] = provider9_set_non_string_keys.factories[Cat]
69+
70+
provider9_new_non_string_keys: providers.FactoryAggregate[str] = providers.FactoryAggregate(
71+
{Cat: providers.Factory(str, "str")},
72+
)
73+
factory_new_non_string_9: providers.Factory[str] = provider9_new_non_string_keys.factories[Cat]
74+
75+
provider9_no_explicit_typing = providers.FactoryAggregate(a=providers.Factory(str, "str"))
76+
provider9_no_explicit_typing_factory: providers.Factory[str] = provider9_no_explicit_typing.factories["a"]
77+
provider9_no_explicit_typing_object: str = provider9_no_explicit_typing("a")
6578

6679
# Test 10: to check the explicit typing
6780
factory10: providers.Provider[Animal] = providers.Factory(Cat)
@@ -72,3 +85,17 @@ def create(cls) -> Animal:
7285
async def _async11() -> None:
7386
animal1: Animal = await provider11(1, 2, 3, b='1', c=2, e=0.0) # type: ignore
7487
animal2: Animal = await provider11.async_(1, 2, 3, b='1', c=2, e=0.0)
88+
89+
# Test 12: to check class type from .provides
90+
provider12 = providers.Factory(Cat)
91+
provided_cls12: Type[Animal] = provider12.cls
92+
assert issubclass(provided_cls12, Animal)
93+
provided_provides12: Optional[Callable[..., Animal]] = provider12.provides
94+
assert provided_provides12 is not None and provided_provides12() == Cat()
95+
96+
# Test 13: to check class from .provides with explicit typevar
97+
provider13 = providers.Factory[Animal](Cat)
98+
provided_cls13: Type[Animal] = provider13.cls
99+
assert issubclass(provided_cls13, Animal)
100+
provided_provides13: Optional[Callable[..., Animal]] = provider13.provides
101+
assert provided_provides13 is not None and provided_provides13() == Cat()

‎tests/typing/object.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Type, Optional
2+
13
from dependency_injector import providers
24

35

@@ -17,3 +19,7 @@
1719
async def _async3() -> None:
1820
var1: int = await provider3() # type: ignore
1921
var2: int = await provider3.async_()
22+
23+
# Test 4: to check class type from provider
24+
provider4 = providers.Object(int('1'))
25+
provided_provides: Optional[int] = provider4.provides

‎tests/typing/resource.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Iterator, Generator, AsyncIterator, AsyncGenerator
1+
from typing import List, Iterator, Generator, AsyncIterator, AsyncGenerator, Optional
22

33
from dependency_injector import providers, resources
44

@@ -35,7 +35,7 @@ class MyResource4(resources.Resource[List[int]]):
3535
def init(self, *args, **kwargs) -> List[int]:
3636
return []
3737

38-
def shutdown(self, resource: List[int]) -> None:
38+
def shutdown(self, resource: Optional[List[int]]) -> None:
3939
...
4040

4141

@@ -87,7 +87,7 @@ class MyResource8(resources.AsyncResource[List[int]]):
8787
async def init(self, *args, **kwargs) -> List[int]:
8888
return []
8989

90-
async def shutdown(self, resource: List[int]) -> None:
90+
async def shutdown(self, resource: Optional[List[int]]) -> None:
9191
...
9292

9393

‎tests/typing/singleton.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Tuple, Any, Dict
1+
from typing import Callable, Optional, Tuple, Any, Dict, Type
22

33
from dependency_injector import providers
44

@@ -75,3 +75,17 @@ def create(cls) -> Animal:
7575
async def _async13() -> None:
7676
animal1: Animal = await provider13(1, 2, 3, b='1', c=2, e=0.0) # type: ignore
7777
animal2: Animal = await provider13.async_(1, 2, 3, b='1', c=2, e=0.0)
78+
79+
# Test 14: to check class from .provides
80+
provider14 = providers.Singleton(Cat)
81+
provided_cls14: Type[Cat] = provider14.cls
82+
assert issubclass(provided_cls14, Cat)
83+
provided_provides14: Optional[Callable[..., Cat]] = provider14.provides
84+
assert provided_provides14 is not None and provided_provides14() == Cat()
85+
86+
# Test 15: to check class from .provides with explicit typevar
87+
provider15 = providers.Singleton[Animal](Cat)
88+
provided_cls15: Type[Animal] = provider15.cls
89+
assert issubclass(provided_cls15, Animal)
90+
provided_provides15: Optional[Callable[..., Animal]] = provider15.provides
91+
assert provided_provides15 is not None and provided_provides15() == Cat()

‎tests/unit/providers/test_factories_py2_py3.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import unittest
66

77
from dependency_injector import (
8+
containers,
89
providers,
910
errors,
1011
)
@@ -498,14 +499,44 @@ def setUp(self):
498499
self.example_b_factory = providers.Factory(self.ExampleB)
499500
self.factory_aggregate = providers.FactoryAggregate(
500501
example_a=self.example_a_factory,
501-
example_b=self.example_b_factory)
502+
example_b=self.example_b_factory,
503+
)
502504

503505
def test_is_provider(self):
504506
self.assertTrue(providers.is_provider(self.factory_aggregate))
505507

506508
def test_is_delegated_provider(self):
507509
self.assertTrue(providers.is_delegated(self.factory_aggregate))
508510

511+
def test_init_with_non_string_keys(self):
512+
factory = providers.FactoryAggregate({
513+
self.ExampleA: self.example_a_factory,
514+
self.ExampleB: self.example_b_factory,
515+
})
516+
517+
object_a = factory(self.ExampleA, 1, 2, init_arg3=3, init_arg4=4)
518+
object_b = factory(self.ExampleB, 11, 22, init_arg3=33, init_arg4=44)
519+
520+
self.assertIsInstance(object_a, self.ExampleA)
521+
self.assertEqual(object_a.init_arg1, 1)
522+
self.assertEqual(object_a.init_arg2, 2)
523+
self.assertEqual(object_a.init_arg3, 3)
524+
self.assertEqual(object_a.init_arg4, 4)
525+
526+
self.assertIsInstance(object_b, self.ExampleB)
527+
self.assertEqual(object_b.init_arg1, 11)
528+
self.assertEqual(object_b.init_arg2, 22)
529+
self.assertEqual(object_b.init_arg3, 33)
530+
self.assertEqual(object_b.init_arg4, 44)
531+
532+
self.assertEqual(
533+
factory.factories,
534+
{
535+
self.ExampleA: self.example_a_factory,
536+
self.ExampleB: self.example_b_factory,
537+
},
538+
)
539+
509540
def test_init_with_not_a_factory(self):
510541
with self.assertRaises(errors.Error):
511542
providers.FactoryAggregate(
@@ -528,7 +559,37 @@ def test_init_optional_factories(self):
528559
self.assertIsInstance(provider('example_a'), self.ExampleA)
529560
self.assertIsInstance(provider('example_b'), self.ExampleB)
530561

531-
def test_set_provides_returns_self(self):
562+
def test_set_factories_with_non_string_keys(self):
563+
factory = providers.FactoryAggregate()
564+
factory.set_factories({
565+
self.ExampleA: self.example_a_factory,
566+
self.ExampleB: self.example_b_factory,
567+
})
568+
569+
object_a = factory(self.ExampleA, 1, 2, init_arg3=3, init_arg4=4)
570+
object_b = factory(self.ExampleB, 11, 22, init_arg3=33, init_arg4=44)
571+
572+
self.assertIsInstance(object_a, self.ExampleA)
573+
self.assertEqual(object_a.init_arg1, 1)
574+
self.assertEqual(object_a.init_arg2, 2)
575+
self.assertEqual(object_a.init_arg3, 3)
576+
self.assertEqual(object_a.init_arg4, 4)
577+
578+
self.assertIsInstance(object_b, self.ExampleB)
579+
self.assertEqual(object_b.init_arg1, 11)
580+
self.assertEqual(object_b.init_arg2, 22)
581+
self.assertEqual(object_b.init_arg3, 33)
582+
self.assertEqual(object_b.init_arg4, 44)
583+
584+
self.assertEqual(
585+
factory.factories,
586+
{
587+
self.ExampleA: self.example_a_factory,
588+
self.ExampleB: self.example_b_factory,
589+
},
590+
)
591+
592+
def test_set_factories_returns_self(self):
532593
provider = providers.FactoryAggregate()
533594
self.assertIs(provider.set_factories(example_a=self.example_a_factory), provider)
534595

@@ -603,6 +664,24 @@ def test_deepcopy(self):
603664
self.assertIsInstance(self.factory_aggregate.example_b, type(provider_copy.example_b))
604665
self.assertIs(self.factory_aggregate.example_b.cls, provider_copy.example_b.cls)
605666

667+
def test_deepcopy_with_non_string_keys(self):
668+
factory_aggregate = providers.FactoryAggregate({
669+
self.ExampleA: self.example_a_factory,
670+
self.ExampleB: self.example_b_factory,
671+
})
672+
provider_copy = providers.deepcopy(factory_aggregate)
673+
674+
self.assertIsNot(factory_aggregate, provider_copy)
675+
self.assertIsInstance(provider_copy, type(factory_aggregate))
676+
677+
self.assertIsNot(factory_aggregate.factories[self.ExampleA], provider_copy.factories[self.ExampleA])
678+
self.assertIsInstance(factory_aggregate.factories[self.ExampleA], type(provider_copy.factories[self.ExampleA]))
679+
self.assertIs(factory_aggregate.factories[self.ExampleA].cls, provider_copy.factories[self.ExampleA].cls)
680+
681+
self.assertIsNot(factory_aggregate.factories[self.ExampleB], provider_copy.factories[self.ExampleB])
682+
self.assertIsInstance(factory_aggregate.factories[self.ExampleB], type(provider_copy.factories[self.ExampleB]))
683+
self.assertIs(factory_aggregate.factories[self.ExampleB].cls, provider_copy.factories[self.ExampleB].cls)
684+
606685
def test_repr(self):
607686
self.assertEqual(repr(self.factory_aggregate),
608687
'<dependency_injector.providers.'

‎tests/unit/providers/test_resource_py35.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Dependency injector resource provider unit tests."""
22

33
import asyncio
4-
4+
import inspect
55
import unittest
6+
from typing import Any
67

78
from dependency_injector import containers, providers, resources, errors
89

@@ -145,6 +146,35 @@ def shutdown(self, _):
145146
self.assertEqual(TestResource.init_counter, 2)
146147
self.assertEqual(TestResource.shutdown_counter, 2)
147148

149+
def test_init_class_generic_typing(self):
150+
# See issue: https://github.com/ets-labs/python-dependency-injector/issues/488
151+
class TestDependency:
152+
...
153+
154+
class TestResource(resources.Resource[TestDependency]):
155+
def init(self, *args: Any, **kwargs: Any) -> TestDependency:
156+
return TestDependency()
157+
158+
def shutdown(self, resource: TestDependency) -> None: ...
159+
160+
self.assertTrue(issubclass(TestResource, resources.Resource))
161+
162+
def test_init_class_abc_init_definition_is_required(self):
163+
class TestResource(resources.Resource):
164+
...
165+
166+
with self.assertRaises(TypeError) as context:
167+
TestResource()
168+
169+
self.assertIn("Can't instantiate abstract class TestResource", str(context.exception))
170+
self.assertIn("init", str(context.exception))
171+
172+
def test_init_class_abc_shutdown_definition_is_not_required(self):
173+
class TestResource(resources.Resource):
174+
def init(self):
175+
...
176+
self.assertTrue(hasattr(TestResource(), 'shutdown'))
177+
148178
def test_init_not_callable(self):
149179
provider = providers.Resource(1)
150180
with self.assertRaises(errors.Error):
@@ -449,6 +479,36 @@ async def shutdown(self, resource_):
449479
self.assertEqual(TestResource.init_counter, 2)
450480
self.assertEqual(TestResource.shutdown_counter, 2)
451481

482+
def test_init_async_class_generic_typing(self):
483+
# See issue: https://github.com/ets-labs/python-dependency-injector/issues/488
484+
class TestDependency:
485+
...
486+
487+
class TestAsyncResource(resources.AsyncResource[TestDependency]):
488+
async def init(self, *args: Any, **kwargs: Any) -> TestDependency:
489+
return TestDependency()
490+
491+
async def shutdown(self, resource: TestDependency) -> None: ...
492+
493+
self.assertTrue(issubclass(TestAsyncResource, resources.AsyncResource))
494+
495+
def test_init_async_class_abc_init_definition_is_required(self):
496+
class TestAsyncResource(resources.AsyncResource):
497+
...
498+
499+
with self.assertRaises(TypeError) as context:
500+
TestAsyncResource()
501+
502+
self.assertIn("Can't instantiate abstract class TestAsyncResource", str(context.exception))
503+
self.assertIn("init", str(context.exception))
504+
505+
def test_init_async_class_abc_shutdown_definition_is_not_required(self):
506+
class TestAsyncResource(resources.AsyncResource):
507+
async def init(self):
508+
...
509+
self.assertTrue(hasattr(TestAsyncResource(), 'shutdown'))
510+
self.assertTrue(inspect.iscoroutinefunction(TestAsyncResource.shutdown))
511+
452512
def test_init_with_error(self):
453513
async def _init():
454514
raise RuntimeError()

0 commit comments

Comments
 (0)
Please sign in to comment.