8000 Merge branch 'release/4.18.0' into master · kiq7/python-dependency-injector@78f623c · GitHub
[go: up one dir, main page]

Skip to content

Commit 78f623c

Browse files
committed
Merge branch 'release/4.18.0' into master
2 parents 35f280a + e80c56f commit 78f623c

File tree

14 files changed

+1226
-831
lines changed

14 files changed

+1226
-831
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test-py2: build
5252
coverage report --rcfile=./.coveragerc
5353
coverage html --rcfile=./.coveragerc
5454

55-
test-py3: build
55+
test: build
5656
# Unit tests with coverage report
5757
coverage erase
5858
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*py3*.py

docs/containers/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ Containers module API docs - :py:mod:`dependency_injector.containers`.
2323
dynamic
2424
specialization
2525
overriding
26+
reset_singletons
2627
traversal

docs/containers/reset_singletons.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Reset container singletons
2+
--------------------------
3+
4+
To reset all container singletons use method ``.reset_singletons()``.
5+
6+
.. literalinclude:: ../../examples/containers/reset_singletons.py
7+
:language: python
8+
:lines: 3-
9+
:emphasize-lines: 16
10+
11+
Method ``.reset_singletons()`` also resets singletons in sub-containers: ``providers.Container`` and
12+
``providers.DependenciesContainer.``
13+
14+
.. literalinclude:: ../../examples/containers/reset_singletons_subcontainers.py
15+
:language: python
16+
:lines: 3-
17+
:emphasize-lines: 21
18+
19+
.. disqus::

docs/main/changelog.rst

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

10+
4.18.0
11+
------
12+
- Add ``container.reset_singleton()`` method to reset container singletons.
13+
- Refactor ``container.apply_container_providers_overridings()`` to use ``container.traverse()``.
14+
This enables deep lazy initialization of ``Container`` providers.
15+
- Add tests for ``Selector`` provider.
16+
- Add tests for ``ProvidedInstance`` and ``MethodCaller`` providers.
17+
- Update Makefile to make Python 3 tests to be a default test command: ``make test``.
18+
1019
4.17.0
1120
------
1221
- Add ``FastAPI`` + ``SQLAlchemy`` example.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Container reset singletons example."""
2+
3+
from dependency_injector import containers, providers
4+
5+
6+
class Container(containers.DeclarativeContainer):
7+
8+
service1 = providers.Singleton(object)
9+
service2 = providers.Singleton(object)
10+
11+
12+
if __name__ == '__main__':
13+
container = Container()
14+
15+
service1 = container.service1()
16+
service2 = container.service2()
17+
18+
container.reset_singletons()
19+
20+
assert service1 is not container.service1()
21+
assert service2 is not container.service2()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Container reset singletons in subcontainer example."""
2+
3+
from dependency_injector import containers, providers
4+
5+
6+
class SubContainer(containers.DeclarativeContainer):
7+
8+
service = providers.Singleton(object)
9+
10+
11+
class Container(containers.DeclarativeContainer):
12+
13+
service = providers.Singleton(object)
14+
sub = providers.Container(SubContainer)
15+
16+
17+
if __name__ == '__main__':
18+
container = Container()
19+
20+
service1 = container.service()
21+
service2 = container.sub().service()
22+
23+
container.reset_singletons()
24+
25+
assert service1 is not container.service()
26+
assert service2 is not container.sub().service()

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

src/dependency_injector/containers.c

Lines changed: 991 additions & 821 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/dependency_injector/containers.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class Container:
4343
def unwire(self) -> None: ...
4444
def init_resources(self) -> Optional[Awaitable]: ...
4545
def shutdown_resources(self) -> Optional[Awaitable]: ...
46+
def apply_container_providers_overridings(self) -> None: ...
47+
def reset_singletons(self) -> None: ...
4648
@overload
4749
def traverse(self, types: Optional[Sequence[Type]] = None) -> Iterator[Provider]: ...
4850
@classmethod

src/dependency_injector/containers.pyx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,14 @@ class DynamicContainer(Container):
268268

269269
def apply_container_providers_overridings(self):
270270
"""Apply container providers' overridings."""
271-
for provider in self.providers.values():
272-
if not isinstance(provider, providers.Container):
273-
continue
271+
for provider in self.traverse(types=[providers.Container]):
274272
provider.apply_overridings()
275273

274+
def reset_singletons(self):
275+
"""Reset all container singletons."""
276+
for provider in self.traverse(types=[providers.Singleton]):
277+
provider.reset()
278+
276279

277280
class DeclarativeContainerMetaClass(type):
278281
"""Declarative inversion of control container meta class."""

tests/unit/containers/test_dynamic_py2_py3.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_override_providers_with_unknown_provider(self):
140140
with self.assertRaises(AttributeError):
141141
container_a.override_providers(unknown=providers.Provider())
142142

143-
def test_reset_last_overridding(self):
143+
def test_reset_last_overriding(self):
144144
class _Container(containers.DeclarativeContainer):
145145
p11 = providers.Provider()
146146

@@ -164,7 +164,7 @@ class _OverridingContainer2(containers.DeclarativeContainer):
164164
self.assertEqual(container.p11.overridden,
165165
(overriding_container1.p11,))
166166

167-
def test_reset_last_overridding_when_not_overridden(self):
167+
def test_reset_last_overriding_when_not_overridden(self):
168168
container = ContainerA()
169169

170170
with self.assertRaises(errors.Error):
@@ -287,3 +287,51 @@ class Container(containers.DeclarativeContainer):
287287
self.assertEqual(_init1.shutdown_counter, 2)
288288
self.assertEqual(_init2.init_counter, 2)
289289
self.assertEqual(_init2.shutdown_counter, 2)
290+
291+
def test_reset_singletons(self):
292+
class SubSubContainer(containers.DeclarativeContainer):
293+
singleton = providers.Singleton(object)
294+
295+
class SubContainer(containers.DeclarativeContainer):
296+
singleton = providers.Singleton(object)
297+
sub_sub_container = providers.Container(SubSubContainer)
298+
299+
class Container(containers.DeclarativeContainer):
300+
singleton = providers.Singleton(object)
301+
sub_container = providers.Container(SubContainer)
302+
303+
container = Container()
304+
305+
obj11 = container.singleton()
306+
obj12 = container.sub_container().singleton()
307+
obj13 = container.sub_container().sub_sub_container().singleton()
308+
309+
obj21 = container.singleton()
310+
obj22 = container.sub_container().singleton()
311+
obj23 = container.sub_container().sub_sub_container().singleton()
312+
313+
self.assertIs(obj11, obj21)
314+
self.assertIs(obj12, obj22)
315+
self.assertIs(obj13, obj23)
316+
317+
container.reset_singletons()
318+
319+
obj31 = container.singleton()
320+
obj32 = container.sub_container().singleton()
321+
obj33 = container.sub_container().sub_sub_container().singleton()
322+
323+
obj41 = container.singleton()
324+
obj42 = container.sub_container().singleton()
325+
obj43 = container.sub_container().sub_sub_container().singleton()
326+
327+
self.assertIsNot(obj11, obj31)
328+
self.assertIsNot(obj12, obj32)
329+
self.assertIsNot(obj13, obj33)
330+
331+
self.assertIsNot(obj21, obj31)
332+
self.assertIsNot(obj22, obj32)
333+
self.assertIsNot(obj23, obj33)
334+
335+
self.assertIs(obj31, obj41)
336+
self.assertIs(obj32, obj42)
337+
self.assertIs(obj33, obj43)
< 10000 div role="region" aria-labelledby="heading-:R6dlab:" class="position-relative" style="contain:layout">

tests/unit/providers/test_container_py2_py3.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ def test_override_by_not_a_container(self):
135135
provider.override(providers.Object('foo'))
136136

137137
def test_lazy_overriding(self):
138+
# See: https://github.com/ets-labs/python-dependency-injector/issues/354
139+
138140
class D(containers.DeclarativeContainer):
139141
foo = providers.Object("foo")
140142

@@ -150,4 +152,26 @@ class B(containers.DeclarativeContainer):
150152
b = B(d=D())
151153
result = b.a().bar()
152154
self.assertEqual(result, 'foo++')
153-
# See: https://github.com/ets-labs/python-dependency-injector/issues/354
155+
156+
def test_lazy_overriding_deep(self):
157+
# Extended version of test_lazy_overriding()
158+
159+
class D(containers.DeclarativeContainer):
160+
foo = providers.Object("foo")
161+
162+
class C(containers.DeclarativeContainer):
163+
d = providers.DependenciesContainer()
164+
bar = providers.Callable(lambda f: f + "++", d.foo.provided)
165+
166+
class A(containers.DeclarativeContainer):
167+
d = providers.DependenciesContainer()
168+
c = providers.Container(C, d=d)
169+
170+
class B(containers.DeclarativeContainer):
171+
d = providers.DependenciesContainer()
172+
173+
a = providers.Container(A, d=d)
174+
175+
b = B(d=D())
176+
result = b.a().c().bar()
177+
self.assertEqual(result, 'foo++')

tests/unit/providers/test_provided_instance_py2_py3.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ def __init__(self, value):
1010
self.value = value
1111
self.values = [self.value]
1212

13-
def get_value(self):
13+
def __call__(self):
1414
return self.value
1515

1616
def __getitem__(self, item):
1717
return self.values[item]
1818

19+
def get_value(self):
20+
return self.value
21+
22+
def get_closure(self):
23+
def closure():
24+
return self.value
25+
return closure
26+
1927

2028
class Client:
2129
def __init__(self, value):
@@ -45,6 +53,15 @@ class Container(containers.DeclarativeContainer):
4553
Client,
4654
value=service.provided.get_value.call(),
4755
)
56+
client_method_closure_call = providers.Factory(
57+
Client,
58+
value=service.provided.get_closure.call().call(),
59+
)
60+
61+
client_provided_call = providers.Factory(
62+
Client,
63+
value=service.provided.call(),
64+
)
4865

4966

5067
class ProvidedInstanceTests(unittest.TestCase):
@@ -71,6 +88,14 @@ def test_method_call(self):
7188
client = self.container.client_method_call()
7289
self.assertEqual(client.value, 'foo')
7390

91+
def test_method_closure_call(self):
92+
client = self.container.client_method_closure_call()
93+
self.assertEqual(client.value, 10000 'foo')
94+
95+
def test_provided_call(self):
96+
client = self.container.client_provided_call()
97+
self.assertEqual(client.value, 'foo')
98+
7499
def test_call_overridden(self):
75100
value = 'bar'
76101
with self.container.service.override(Service(value)):

tests/unit/providers/test_selector_py2_py3.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import unittest2 as unittest
88

9-
from dependency_injector import providers
9+
from dependency_injector import providers, errors
1010

1111

1212
class SelectorTests(unittest.TestCase):
@@ -33,6 +33,28 @@ def test_call(self):
3333
with self.selector.override('two'):
3434
self.assertEqual(provider(), 2)
3535

36+
def test_call_undefined_provider(self):
37+
provider = providers.Selector(
38+
self.selector,
39+
one=providers.Object(1),
40+
two=providers.Object(2),
41+
)
42+
43+
with self.selector.override('three'):
44+
with self.assertRaises(errors.Error):
45+
provider()
46+
47+
def test_call_selector_is_none(self):
48+
provider = providers.Selector(
49+
self.selector,
50+
one=providers.Object(1),
51+
two=providers.Object(2),
52+
)
53+
54+
with self.selector.override(None):
55+
with self.assertRaises(errors.Error):
56+
provider()
57+
3658
def test_call_any_callable(self):
3759
provider = providers.Selector(
3860
functools.partial(next, itertools.cycle(['one', 'two'])),
@@ -70,6 +92,19 @@ def test_getattr(self):
7092
self.assertIs(provider.one, provider_one)
7193
self.assertIs(provider.two, provider_two)
7294

95+
def test_getattr_attribute_error(self):
96+
provider_one = providers.Object(1)
97+
provider_two = providers.Object(2)
98+
99+
provider = providers.Selector(
100+
self.selector,
101+
one=provider_one,
102+
two=provider_two,
103+
)
104+
105+
with self.assertRaises(AttributeError):
106+
_ = provider.provider_three
107+
73108
def test_call_overridden(self):
74109
provider = providers.Selector(self.selector, sample=providers.Object(1))
75110
overriding_provider1 = providers.Selector(self.selector, sample=providers.Object(2))
@@ -81,6 +116,18 @@ def test_call_overridden(self):
81116
with self.selector.override('sample'):
82117
self.assertEqual(provider(), 3)
83118

119+
def test_providers_attribute(self):
120+
provider_one = providers.Object(1)
121+
provider_two = providers.Object(2)
122+
123+
provider = providers.Selector(
124+
self.selector,
125+
one=provider_one,
126+
two=provider_two,
127+
)
128+
129+
self.assertEqual(provider.providers, {'one': provider_one, 'two': provider_two})
130+
84131
def test_deepcopy(self):
85132
provider = providers.Selector(self.selector)
86133

0 commit comments

Comments
 (0)
0