Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4bfe645

Browse files
Add resource type parameter to init and shutdown resources using specialized providers (#858)
1 parent b411807 commit 4bfe645

File tree

5 files changed

+212
-7
lines changed

5 files changed

+212
-7
lines changed

‎docs/providers/resource.rst‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,72 @@ first argument.
252252
253253
.. _resource-provider-wiring-closing:
254254

255+
Scoping Resources using specialized subclasses
256+
----------------------------------------------
257+
258+
You can use specialized subclasses of ``Resource`` provider to initialize and shutdown resources by type.
259+
Allowing for example to only initialize a subgroup of resources.
260+
261+
.. code-block:: python
262+
263+
class ScopedResource(resources.Resource):
264+
pass
265+
266+
def init_service(name) -> Service:
267+
print(f"Init {name}")
268+
yield Service()
269+
print(f"Shutdown {name}")
270+
271+
class Container(containers.DeclarativeContainer):
272+
273+
scoped = ScopedResource(
274+
init_service,
275+
"scoped",
276+
)
277+
278+
generic = providers.Resource(
279+
init_service,
280+
"generic",
281+
)
282+
283+
284+
To initialize resources by type you can use ``init_resources(resource_type)`` and ``shutdown_resources(resource_type)``
285+
methods adding the resource type as an argument:
286+
287+
.. code-block:: python
288+
289+
def main():
290+
container = Container()
291+
container.init_resources(ScopedResource)
292+
# Generates:
293+
# >>> Init scoped
294+
295+
container.shutdown_resources(ScopedResource)
296+
# Generates:
297+
# >>> Shutdown scoped
298+
299+
300+
And to initialize all resources you can use ``init_resources()`` and ``shutdown_resources()`` without arguments:
301+
302+
.. code-block:: python
303+
304+
def main():
305+
container = Container()
306+
container.init_resources()
307+
# Generates:
308+
# >>> Init scoped
309+
# >>> Init generic
310+
311+
container.shutdown_resources()
312+
# Generates:
313+
# >>> Shutdown scoped
314+
# >>> Shutdown generic
315+
316+
317+
It works using the :ref:`traverse` method to find all resources of the specified type, selecting all resources
318+
which are instances of the specified type.
319+
320+
255321
Resources, wiring, and per-function execution scope
256322
---------------------------------------------------
257323

‎src/dependency_injector/containers.pyi‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ try:
2222
except ImportError:
2323
from typing_extensions import Self as _Self
2424

25-
from .providers import Provider, ProviderParent, Self
25+
from .providers import Provider, Resource, Self, ProviderParent
2626

2727
C_Base = TypeVar("C_Base", bound="Container")
2828
C = TypeVar("C", bound="DeclarativeContainer")
@@ -74,8 +74,8 @@ class Container:
7474
from_package: Optional[str] = None,
7575
) -> None: ...
7676
def unwire(self) -> None: ...
77-
def init_resources(self) -> Optional[Awaitable[None]]: ...
78-
def shutdown_resources(self) -> Optional[Awaitable[None]]: ...
77+
def init_resources(self, resource_type: Type[Resource[Any]] =Resource) -> Optional[Awaitable[None]]: ...
78+
def shutdown_resources(self, resource_type: Type[Resource[Any]] =Resource) -> Optional[Awaitable[None]]: ...
7979
def load_config(self) -> None: ...
8080
def apply_container_providers_overridings(self) -> None: ...
8181
def reset_singletons(self) -> SingletonResetContext[C_Base]: ...

‎src/dependency_injector/containers.pyx‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,15 @@ class DynamicContainer(Container):
315315
self.wired_to_modules.clear()
316316
self.wired_to_packages.clear()
317317

318-
def init_resources(self):
318+
def init_resources(self, resource_type=providers.Resource):
319319
"""Initialize all container resources."""
320+
321+
if not issubclass(resource_type, providers.Resource):
322+
raise TypeError("resource_type must be a subclass of Resource provider")
323+
320324
futures = []
321325

322-
for provider in self.traverse(types=[providers.Resource]):
326+
for provider in self.traverse(types=[resource_type]):
323327
resource = provider.init()
324328

325329
if __is_future_or_coroutine(resource):
@@ -328,8 +332,12 @@ class DynamicContainer(Container):
328332
if futures:
329333
return asyncio.gather(*futures)
330334

331-
def shutdown_resources(self):
335+
def shutdown_resources(self, resource_type=providers.Resource):
332336
"""Shutdown all container resources."""
337+
338+
if not issubclass(resource_type, providers.Resource):
339+
raise TypeError("resource_type must be a subclass of Resource provider")
340+
333341
def _independent_resources(resources):
334342
for resource in resources:
335343
for other_resource in resources:
@@ -360,7 +368,7 @@ class DynamicContainer(Container):
360368
for resource in resources_to_shutdown:
361369
resource.shutdown()
362370

363-
resources = list(self.traverse(types=[providers.Resource]))
371+
resources = list(self.traverse(types=[resource_type]))
364372
if any(resource.is_async_mode_enabled() for resource in resources):
365373
return _async_ordered_shutdown(resources)
366374
else:

‎tests/unit/containers/instance/test_async_resources_py36.py‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,121 @@ class Container(containers.DeclarativeContainer):
145145
await container.shutdown_resources()
146146
assert initialized_resources == ["r1", "r2", "r3", "r1", "r2", "r3"]
147147
assert shutdown_resources == ["r3", "r2", "r1", "r3", "r2", "r1"]
148+
149+
150+
@mark.asyncio
151+
async def test_init_and_shutdown_scoped_resources():
152+
initialized_resources = []
153+
shutdown_resources = []
154+
155+
def _sync_resource(name, **_):
156+
initialized_resources.append(name)
157+
yield name
158+
shutdown_resources.append(name)
159+
160+
async def _async_resource(name, **_):
161+
initialized_resources.append(name)
162+
yield name
163+
shutdown_resources.append(name)
164+
165+
166+
class ResourceA(providers.Resource):
167+
pass
168+
169+
170+
class ResourceB(providers.Resource):
171+
pass
172+
173+
174+
class Container(containers.DeclarativeContainer):
175+
resource_a = ResourceA(
176+
_sync_resource,
177+
name="ra1",
178+
)
179+
resource_b1 = ResourceB(
180+
_sync_resource,
181+
name="rb1",
182+
r1=resource_a,
183+
)
184+
resource_b2 = ResourceB(
185+
_async_resource,
186+
name="rb2",
187+
r2=resource_b1,
188+
)
189+
190+
container = Container()
191+
192+
container.init_resources(resource_type=ResourceA)
193+
assert initialized_resources == ["ra1"]
194+
assert shutdown_resources == []
195+
196+
container.shutdown_resources(resource_type=ResourceA)
197+
assert initialized_resources == ["ra1"]
198+
assert shutdown_resources == ["ra1"]
199+
200+
await container.init_resources(resource_type=ResourceB)
201+
assert initialized_resources == ["ra1", "ra1", "rb1", "rb2"]
202+
assert shutdown_resources == ["ra1"]
203+
204+
await container.shutdown_resources(resource_type=ResourceB)
205+
assert initialized_resources == ["ra1", "ra1", "rb1", "rb2"]
206+
assert shutdown_resources == ["ra1", "rb2", "rb1"]
207+
208+
209+
@mark.asyncio
210+
async def test_init_and_shutdown_all_scoped_resources_using_default_value():
211+
initialized_resources = []
212+
shutdown_resources = []
213+
214+
def _sync_resource(name, **_):
215+
initialized_resources.append(name)
216+
yield name
217+
shutdown_resources.append(name)
218+
219+
async def _async_resource(name, **_):
220+
initialized_resources.append(name)
221+
yield name
222+
shutdown_resources.append(name)
223+
224+
225+
class ResourceA(providers.Resource):
226+
pass
227+
228+
229+
class ResourceB(providers.Resource):
230+
pass
231+
232+
233+
class Container(containers.DeclarativeContainer):
234+
resource_a = ResourceA(
235+
_sync_resource,
236+
name="r1",
237+
)
238+
resource_b1 = ResourceB(
239+
_sync_resource,
240+
name="r2",
241+
r1=resource_a,
242+
)
243+
resource_b2 = ResourceB(
244+
_async_resource,
245+
name="r3",
246+
r2=resource_b1,
247+
)
248+
249+
container = Container()
250+
251+
await container.init_resources()
252+
assert initialized_resources == ["r1", "r2", "r3"]
253+
assert shutdown_resources == []
254+
255+
await container.shutdown_resources()
256+
assert initialized_resources == ["r1", "r2", "r3"]
257+
assert shutdown_resources == ["r3", "r2", "r1"]
258+
259+
await container.init_resources()
260+
assert initialized_resources == ["r1", "r2", "r3", "r1", "r2", "r3"]
261+
assert shutdown_resources == ["r3", "r2", "r1"]
262+
263+
await container.shutdown_resources()
264+
assert initialized_resources == ["r1", "r2", "r3", "r1", "r2", "r3"]
265+
assert shutdown_resources == ["r3", "r2", "r1", "r3", "r2", "r1"]

‎tests/unit/containers/instance/test_main_py2_py3.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,19 @@ class Container(containers.DeclarativeContainer):
325325
assert _init2.shutdown_counter == 2
326326

327327

328+
def test_init_shutdown_resources_wrong_type() -> None:
329+
class Container(containers.DeclarativeContainer):
330+
pass
331+
332+
c = Container()
333+
334+
with raises(TypeError, match=r"resource_type must be a subclass of Resource provider"):
335+
c.init_resources(int) # type: ignore[arg-type]
336+
337+
with raises(TypeError, match=r"resource_type must be a subclass of Resource provider"):
338+
c.shutdown_resources(int) # type: ignore[arg-type]
339+
340+
328341
def test_reset_singletons():
329342
class SubSubContainer(containers.DeclarativeContainer):
330343
singleton = providers.Singleton(object)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /