Skip to content

Commit 403860a

Browse files
add context local resource with minimal tests
1 parent 059f78b commit 403860a

File tree

4 files changed

+615
-1
lines changed

4 files changed

+615
-1
lines changed

src/dependency_injector/providers.pxd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ cdef class Resource(Provider):
239239
cpdef object _provide(self, tuple args, dict kwargs)
240240

241241

242+
cdef class ContextLocalResource(Resource):
243+
cdef object _resource_context_var
244+
cdef object _shutdowner_context_var
245+
246+
cpdef object _provide(self, tuple args, dict kwargs)
247+
248+
242249
cdef class Container(Provider):
243250
cdef object _container_cls
244251
cdef dict _overriding_providers

src/dependency_injector/providers.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,8 @@ class Resource(Provider[T]):
525525
def init(self) -> Optional[Awaitable[T]]: ...
526526
def shutdown(self) -> Optional[Awaitable]: ...
527527

528+
class ContextLocalResource(Resource[T]):...
529+
528530
class Container(Provider[T]):
529531
def __init__(
530532
self,

src/dependency_injector/providers.pyx

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3186,7 +3186,7 @@ cdef class ThreadLocalSingleton(BaseSingleton):
31863186
return future_result
31873187

31883188
self._storage.instance = instance
3189-
3189+
31903190
return instance
31913191

31923192
def _async_init_instance(self, future_result, result):
@@ -3867,6 +3867,133 @@ cdef class Resource(Provider):
38673867
return self._resource
38683868

38693869

3870+
cdef class ContextLocalResource(Resource):
3871+
_none = object()
3872+
3873+
def __init__(self, provides=None, *args, **kwargs):
3874+
self._resource_context_var = ContextVar("_resource_context_var", default=self._none)
3875+
self._shutdowner_context_var = ContextVar("_shutdowner_context_var", default=self._none)
3876+
super().__init__(provides, *args, **kwargs)
3877+
3878+
def __deepcopy__(self, memo):
3879+
"""Create and return full copy of provider."""
3880+
copied = memo.get(id(self))
3881+
if copied is not None:
3882+
return copied
3883+
3884+
if self._resource_context_var.get() != self._none:
3885+
raise Error("Can not copy initialized resource")
3886+
copied = _memorized_duplicate(self, memo)
3887+
copied.set_provides(_copy_if_provider(self.provides, memo))
3888+
copied.set_args(*deepcopy_args(self, self.args, memo))
3889+
copied.set_kwargs(**deepcopy_kwargs(self, self.kwargs, memo))
3890+
3891+
self._copy_overridings(copied, memo)
3892+
3893+
return copied
3894+
3895+
@property
3896+
def initialized(self):
3897+
"""Check if resource is initialized."""
3898+
return self._resource_context_var.get() != self._none
3899+
3900+
3901+
def shutdown(self):
3902+
"""Shutdown resource."""
3903+
if self._resource_context_var.get() == self._none :
3904+
self._reset_all_contex_vars()
3905+
if self._async_mode == ASYNC_MODE_ENABLED:
3906+
return NULL_AWAITABLE
3907+
return
3908+
if self._shutdowner_context_var.get():
3909+
future = self._shutdowner_context_var.get()(None, None, None)
3910+
if __is_future_or_coroutine(future):
3911+
self._reset_all_contex_vars()
3912+
return ensure_future(self._shutdown_async(future))
3913+
3914+
3915+
self._reset_all_contex_vars()
3916+
if self._async_mode == ASYNC_MODE_ENABLED:
3917+
return NULL_AWAITABLE
3918+
3919+
def _reset_all_contex_vars(self):
3920+
self._resource_context_var.set(self._none)
3921+
self._shutdowner_context_var.set(self._none)
3922+
3923+
3924+
async def _shutdown_async(self, future) -> None:
3925+
await future
3926+
3927+
3928+
async def _handle_async_cm(self, obj) -> None:
3929+
resource = await obj.__aenter__()
3930+
return resource
3931+
3932+
async def _provide_async(self, future):
3933+
try:
3934+
obj = await future
3935+
3936+
if hasattr(obj, '__aenter__') and hasattr(obj, '__aexit__'):
3937+
resource = await obj.__aenter__()
3938+
shutdowner = obj.__aexit__
3939+
elif hasattr(obj, '__enter__') and hasattr(obj, '__exit__'):
3940+
resource = obj.__enter__()
3941+
shutdowner = obj.__exit__
3942+
else:
3943+
resource = obj
3944+
shutdowner = None
3945+
3946+
return resource, shutdowner
3947+
except:
3948+
raise
3949+
3950+
cpdef object _provide(self, tuple args, dict kwargs):
3951+
if self._resource_context_var.get() != self._none:
3952+
return self._resource_context_var.get()
3953+
obj = __call(
3954+
self._provides,
3955+
args,
3956+
self._args,
3957+
self._args_len,
3958+
kwargs,
3959+
self._kwargs,
3960+
self._kwargs_len,
3961+
self._async_mode,
3962+
)
3963+
3964+
if __is_future_or_coroutine(obj):
3965+
future_result = asyncio.Future()
3966+
future = ensure_future(self._provide_async(obj))
3967+
future.add_done_callback(functools.partial(self._async_init_instance, future_result))
3968+
return future_result
3969+
elif hasattr(obj, '__enter__') and hasattr(obj, '__exit__'):
3970+
resource = obj.__enter__()
3971+
self._resource_context_var.set(resource)
3972+
self._shutdowner_context_var.set(obj.__exit__)
3973+
elif hasattr(obj, '__aenter__') and hasattr(obj, '__aexit__'):
3974+
resource = ensure_future(self._handle_async_cm(obj))
3975+
self._resource_context_var.set(resource)
3976+
self._shutdowner_context_var.set(obj.__aexit__)
3977+
return resource
3978+
else:
3979+
self._resource_context_var.set(obj)
3980+
self._shutdowner_context_var.set(None)
3981+
3982+
return self._resource_context_var.get()
3983+
3984+
def _async_init_instance(self, future_result, result):
3985+
try:
3986+
resource, shutdowner = result.result()
3987+
except Exception as exception:
3988+
self._resource_context_var.set(self._none)
3989+
self._shutdowner_context_var.set(self._none)
3990+
future_result.set_exception(exception)
3991+
else:
3992+
self._resource_context_var.set(resource)
3993+
self._shutdowner_context_var.set(shutdowner)
3994+
future_result.set_result(resource)
3995+
3996+
38703997
cdef class Container(Provider):
38713998
"""Container provider provides an instance of declarative container.
38723999

0 commit comments

Comments
 (0)