Skip to content

Commit 284dee6

Browse files
authored
Add with support for container.override_providers() (ets-labs#517)
* Add implementation, tests, and typing stub * Update documentation * Update changelog
1 parent 73a43e6 commit 284dee6

File tree

8 files changed

+2558
-1885
lines changed

8 files changed

+2558
-1885
lines changed

docs/containers/declarative.rst

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,39 @@ Injections in the declarative container are done the usual way:
3434
:language: python
3535
:lines: 3-
3636

37-
You can override the container providers when you create the container instance:
37+
You can override container providers while creating a container instance:
3838

3939
.. literalinclude:: ../../examples/containers/declarative_override_providers.py
4040
:language: python
4141
:lines: 3-
42+
:emphasize-lines: 13
43+
44+
Alternatively, you can call ``container.override_providers()`` method when the container instance
45+
already exists:
46+
47+
.. code-block:: python
48+
:emphasize-lines: 3
49+
50+
container = Container()
51+
52+
container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar))
53+
54+
assert isinstance(container.foo(), mock.Mock)
55+
assert isinstance(container.bar(), mock.Mock)
56+
57+
You can also use ``container.override_providers()`` with a context manager to reset
58+
provided overriding after the context is closed:
59+
60+
.. code-block:: python
61+
:emphasize-lines: 3
62+
63+
container = Container()
64+
65+
with container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar)):
66+
assert isinstance(container.foo(), mock.Mock)
67+
assert isinstance(container.bar(), mock.Mock)
68+
69+
assert isinstance(container.foo(), Foo)
70+
assert isinstance(container.bar(), Bar)
4271
4372
.. disqus::

docs/main/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Develop
1212
- Improve wiring with adding importing modules and packages from a string
1313
``container.wire(modules=["yourapp.module1"])``.
1414
- Add container wiring configuration ``wiring_config = containers.WiringConfiguration()``.
15+
- Add support of ``with`` statement for ``container.override_providers()`` method.
1516
- Update documentation and fix typos.
1617

1718
4.36.2

src/dependency_injector/containers.c

Lines changed: 2484 additions & 1882 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: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Container:
5050
def set_providers(self, **providers: Provider): ...
5151
def set_provider(self, name: str, provider: Provider) -> None: ...
5252
def override(self, overriding: C_Base) -> None: ...
53-
def override_providers(self, **overriding_providers: Union[Provider, Any]) -> None: ...
53+
def override_providers(self, **overriding_providers: Union[Provider, Any]) -> ProvidersOverridingContext[C_Base]: ...
5454
def reset_last_overriding(self) -> None: ...
5555
def reset_override(self) -> None: ...
5656
def is_auto_wiring_enabled(self) -> bool: ...
@@ -90,6 +90,12 @@ class DeclarativeContainer(Container):
9090
def __init__(self, **overriding_providers: Union[Provider, Any]) -> None: ...
9191

9292

93+
class ProvidersOverridingContext(Generic[T]):
94+
def __init__(self, container: T, overridden_providers: Iterable[Union[Provider, Any]]) -> None: ...
95+
def __enter__(self) -> T: ...
96+
def __exit__(self, *_: Any) -> None: ...
97+
98+
9399
class SingletonResetContext(Generic[T]):
94100
def __init__(self, container: T): ...
95101
def __enter__(self) -> T: ...

src/dependency_injector/containers.pyx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,12 @@ class DynamicContainer(Container):
240240
241241
:rtype: None
242242
"""
243+
overridden_providers = []
243244
for name, overriding_provider in six.iteritems(overriding_providers):
244245
container_provider = getattr(self, name)
245246
container_provider.override(overriding_provider)
247+
overridden_providers.append(container_provider)
248+
return ProvidersOverridingContext(self, overridden_providers)
246249

247250
def reset_last_overriding(self):
248251
"""Reset last overriding provider for each container providers.
@@ -784,6 +787,21 @@ class SingletonResetContext:
784787
self._container.reset_singletons()
785788

786789

790+
791+
class ProvidersOverridingContext:
792+
793+
def __init__(self, container, overridden_providers):
794+
self._container = container
795+
self._overridden_providers = overridden_providers
796+
797+
def __enter__(self):
798+
return self._container
799+
800+
def __exit__(self, *_):
801+
for provider in self._overridden_providers:
802+
provider.reset_last_overriding()
803+
804+
787805
def override(object container):
788806
""":py:class:`DeclarativeContainer` overriding decorator.
789807

tests/typing/declarative_container.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ class Container6(containers.DeclarativeContainer):
5858
container6: containers.Container = Container6()
5959

6060

61-
# Test 7: to override()
61+
# Test 7: to override_providers()
6262
class Container7(containers.DeclarativeContainer):
6363
provider = providers.Factory(str)
6464

6565
container7 = Container7()
6666
container7.override_providers(provider="new_value")
67+
with container7.override_providers(a=providers.Provider()):
68+
...

tests/typing/dynamic_container.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# Test 3: to check override_providers()
1515
container3 = containers.DynamicContainer()
1616
container3.override_providers(a=providers.Provider())
17+
with container3.override_providers(a=providers.Provider()):
18+
...
1719

1820
# Test 4: to check set_providers()
1921
container4 = containers.DynamicContainer()

tests/unit/containers/test_dynamic_py2_py3.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ def test_override_providers(self):
134134
self.assertIs(container_a.p11.last_overriding, p1)
135135
self.assertIs(container_a.p12.last_overriding, p2)
136136

137+
def test_override_providers_context_manager(self):
138+
p1 = providers.Provider()
139+
p2 = providers.Provider()
140+
container_a = ContainerA()
141+
142+
with container_a.override_providers(p11=p1, p12=p2) as container:
143+
self.assertIs(container, container_a)
144+
self.assertIs(container_a.p11.last_overriding, p1)
145+
self.assertIs(container_a.p12.last_overriding, p2)
146+
147+
self.assertIsNone(container_a.p11.last_overriding)
148+
self.assertIsNone(container_a.p12.last_overriding)
149+
137150
def test_override_providers_with_unknown_provider(self):
138151
container_a = ContainerA()
139152

0 commit comments

Comments
 (0)