From 5f6a0cf98b4a03b891f434cb0afe96e3bd6f895c Mon Sep 17 00:00:00 2001 From: ZipFile Date: Thu, 18 Jun 2026 16:19:41 +0000 Subject: [PATCH] Fix sync resources with async deps --- src/dependency_injector/providers.pxd | 1 + src/dependency_injector/providers.pyx | 21 +++++++++++++++++-- .../resource/test_async_resource_py35.py | 21 ++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/dependency_injector/providers.pxd b/src/dependency_injector/providers.pxd index c49f28cd..4332118d 100644 --- a/src/dependency_injector/providers.pxd +++ b/src/dependency_injector/providers.pxd @@ -227,6 +227,7 @@ cdef class Dict(Provider): cdef class ResourceState: cdef object resource cdef object shutdowner + cdef bint shutdowner_is_async cdef bint is_async cdef bint async_done diff --git a/src/dependency_injector/providers.pyx b/src/dependency_injector/providers.pyx index 337621f9..92ea4f37 100644 --- a/src/dependency_injector/providers.pyx +++ b/src/dependency_injector/providers.pyx @@ -3591,9 +3591,20 @@ cdef class ResourceState: def __cinit__(self, obj, error_callback, /): self.resource = None self.shutdowner = None + self.shutdowner_is_async = False self.is_async = False self.async_done = False + def __repr__(self): + return ( + f"{self.__class__.__name__}(" + f"resource={self.resource!r}, " + f"shutdowner={self.shutdowner!r}, " + f"shutdowner_is_async={self.shutdowner_is_async}, " + f"is_async={self.is_async}, " + f"async_done={self.async_done})" + ) + def __init__(self, obj, error_callback, /): if __is_future_or_coroutine(obj): self.from_coro(obj, error_callback) @@ -3612,7 +3623,10 @@ cdef class ResourceState: self.shutdowner = None if shutdowner is not None: - await shutdowner(None, None, None) + future = shutdowner(None, None, None) + + if self.shutdowner_is_async: + await future async def from_awaitable(self, awaitable, error_callback, /): try: @@ -3644,6 +3658,7 @@ cdef class ResourceState: raise self.shutdowner = acm.__aexit__ + self.shutdowner_is_async = True self.async_done = True return resource @@ -3835,7 +3850,9 @@ cdef class BaseResource(Provider): if state.is_async: return state.async_shutdown() elif state.shutdowner is not None: - state.shutdowner(None, None, None) + result = state.shutdowner(None, None, None) + + assert not __isfuture(result), "sync resource with async shutdowner" if self._async_mode == ASYNC_MODE_ENABLED: return NULL_AWAITABLE diff --git a/tests/unit/providers/resource/test_async_resource_py35.py b/tests/unit/providers/resource/test_async_resource_py35.py index 6458584d..eff10ca4 100644 --- a/tests/unit/providers/resource/test_async_resource_py35.py +++ b/tests/unit/providers/resource/test_async_resource_py35.py @@ -2,7 +2,7 @@ import asyncio import inspect -from contextlib import asynccontextmanager +from contextlib import asynccontextmanager, contextmanager from typing import Any from pytest import mark, raises @@ -346,3 +346,22 @@ async def _init(): assert result2 is resource assert _init.counter == 1 + + +@mark.asyncio +async def test_sync_resource_with_async_deps(): + @asynccontextmanager + async def resource_async(v): + await asyncio.sleep(0) + yield v + + @contextmanager + def resource_sync(_async): + yield _async + 1 + + _async = providers.Resource(resource_async, 1) + _sync = providers.Resource(resource_sync, _async) + + assert (await _sync()) == 2 + await _async.shutdown() + await _sync.shutdown()