Skip to content

Commit 9cb8e60

Browse files
Feature: Context local provider (#442)
Co-authored-by: Rollo Konig Brock <rollo@b2c2.com>
1 parent 155f598 commit 9cb8e60

File tree

9 files changed

+14931
-12186
lines changed

9 files changed

+14931
-12186
lines changed

src/dependency_injector/containers.c

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

src/dependency_injector/providers.c

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

src/dependency_injector/providers.pxd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ cdef class ThreadLocalSingleton(BaseSingleton):
175175
cpdef object _provide(self, tuple args, dict kwargs)
176176

177177

178+
cdef class ContextLocalSingleton(BaseSingleton):
179+
180+
cpdef object _provide(self, tuple args, dict kwargs)
181+
182+
178183
cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
179184
pass
180185

src/dependency_injector/providers.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ class DelegatedThreadSafeSingleton(ThreadSafeSingleton[T]): ...
336336
class ThreadLocalSingleton(BaseSingleton[T]): ...
337337

338338

339+
class ContextLocalSingleton(BaseSingleton[T]): ...
340+
341+
339342
class DelegatedThreadLocalSingleton(ThreadLocalSingleton[T]): ...
340343

341344

src/dependency_injector/providers.pyx

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import types
1313
import threading
1414
import warnings
1515

16+
try:
17+
import contextvars
18+
except ImportError:
19+
contextvars = None
20+
21+
1622
try:
1723
import asyncio
1824
except ImportError:
@@ -2928,6 +2934,89 @@ cdef class ThreadLocalSingleton(BaseSingleton):
29282934
future_result.set_result(instance)
29292935

29302936

2937+
cdef class ContextLocalSingleton(BaseSingleton):
2938+
"""Context-local singleton provides single objects in scope of a context.
2939+
2940+
.. py:attribute:: provided_type
2941+
2942+
If provided type is defined, provider checks that providing class is
2943+
its subclass.
2944+
2945+
:type: type | None
2946+
2947+
.. py:attribute:: cls
2948+
:noindex:
2949+
2950+
Class that provides object.
2951+
Alias for :py:attr:`provides`.
2952+
2953+
:type: type
2954+
"""
2955+
_none = object()
2956+
2957+
def __init__(self, provides=None, *args, **kwargs):
2958+
"""Initializer.
2959+
2960+
:param provides: Provided type.
2961+
:type provides: type
2962+
"""
2963+
if not contextvars:
2964+
raise RuntimeError(
2965+
'Contextvars library not found. This provider '
2966+
'requires Python 3.7 or a backport of contextvars. '
2967+
'To install a backport run "pip install contextvars".'
2968+
)
2969+
2970+
super(ContextLocalSingleton, self).__init__(provides, *args, **kwargs)
2971+
self.__storage = contextvars.ContextVar('__storage', default=self._none)
2972+
2973+
def reset(self):
2974+
"""Reset cached instance, if any.
2975+
2976+
:rtype: None
2977+
"""
2978+
instance = self.__storage.get()
2979+
if instance is self._none:
2980+
return SingletonResetContext(self)
2981+
2982+
if __is_future_or_coroutine(instance):
2983+
asyncio.ensure_future(instance).cancel()
2984+
2985+
self.__storage.set(self._none)
2986+
2987+
return SingletonResetContext(self)
2988+
2989+
cpdef object _provide(self, tuple args, dict kwargs):
2990+
"""Return single instance."""
2991+
cdef object instance
2992+
2993+
instance = self.__storage.get()
2994+
2995+
if instance is self._none:
2996+
instance = __factory_call(self.__instantiator, args, kwargs)
2997+
2998+
if __is_future_or_coroutine(instance):
2999+
future_result = asyncio.Future()
3000+
instance = asyncio.ensure_future(instance)
3001+
instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
3002+
self.__storage.set(future_result)
3003+
return future_result
3004+
3005+
self.__storage.set(instance)
3006+
3007+
return instance
3008+
3009+
def _async_init_instance(self, future_result, result):
3010+
try:
3011+
instance = result.result()
3012+
except Exception as exception:
3013+
self.__storage.set(self._none)
3014+
future_result.set_exception(exception)
3015+
else:
3016+
self.__storage.set(instance)
3017+
future_result.set_result(instance)
3018+
3019+
29313020
cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
29323021
"""Delegated thread-local singleton is injected "as is".
29333022
@@ -4645,4 +4734,4 @@ cpdef str _class_qualname(object instance):
46454734
name = getattr(instance.__class__, '__qualname__', None)
46464735
if not name:
46474736
name = '.'.join((instance.__class__.__module__, instance.__class__.__name__))
4648-
return name
4737+
return name

0 commit comments

Comments
 (0)