@@ -13,6 +13,12 @@ import types
1313import threading
1414import warnings
1515
16+ try :
17+ import contextvars
18+ except ImportError :
19+ contextvars = None
20+
21+
1622try :
1723 import asyncio
1824except 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+
29313020cdef 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