Skip to content

ContextLocalSingleton Provider Class #442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,332 changes: 710 additions & 622 deletions src/dependency_injector/containers.c

Large diffs are not rendered by default.

24,775 changes: 13,644 additions & 11,131 deletions src/dependency_injector/providers.c

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/dependency_injector/providers.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ cdef class ThreadLocalSingleton(BaseSingleton):
cpdef object _provide(self, tuple args, dict kwargs)


cdef class ContextLocalSingleton(BaseSingleton):

cpdef object _provide(self, tuple args, dict kwargs)


cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
pass

Expand Down
3 changes: 3 additions & 0 deletions src/dependency_injector/providers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,9 @@ class DelegatedThreadSafeSingleton(ThreadSafeSingleton[T]): ...
class ThreadLocalSingleton(BaseSingleton[T]): ...


class ContextLocalSingleton(BaseSingleton[T]): ...


class DelegatedThreadLocalSingleton(ThreadLocalSingleton[T]): ...


Expand Down
91 changes: 90 additions & 1 deletion src/dependency_injector/providers.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import types
import threading
import warnings

try:
import contextvars
except ImportError:
contextvars = None


try:
import asyncio
except ImportError:
Expand Down Expand Up @@ -2928,6 +2934,89 @@ cdef class ThreadLocalSingleton(BaseSingleton):
future_result.set_result(instance)


cdef class ContextLocalSingleton(BaseSingleton):
"""Context-local singleton provides single objects in scope of a context.

.. py:attribute:: provided_type

If provided type is defined, provider checks that providing class is
its subclass.

:type: type | None

.. py:attribute:: cls
:noindex:

Class that provides object.
Alias for :py:attr:`provides`.

:type: type
"""
_none = object()

def __init__(self, provides=None, *args, **kwargs):
"""Initializer.

:param provides: Provided type.
:type provides: type
"""
if not contextvars:
raise RuntimeError(
'Contextvars library not found. This provider '
'requires Python 3.7 or a backport of contextvars. '
'To install a backport run "pip install contextvars".'
)

super(ContextLocalSingleton, self).__init__(provides, *args, **kwargs)
self.__storage = contextvars.ContextVar('__storage', default=self._none)

def reset(self):
"""Reset cached instance, if any.

:rtype: None
"""
instance = self.__storage.get()
if instance is self._none:
return SingletonResetContext(self)

if __is_future_or_coroutine(instance):
asyncio.ensure_future(instance).cancel()

self.__storage.set(self._none)

return SingletonResetContext(self)

cpdef object _provide(self, tuple args, dict kwargs):
"""Return single instance."""
cdef object instance

instance = self.__storage.get()

if instance is self._none:
instance = __factory_call(self.__instantiator, args, kwargs)

if __is_future_or_coroutine(instance):
future_result = asyncio.Future()
instance = asyncio.ensure_future(instance)
instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
self.__storage.set(future_result)
return future_result

self.__storage.set(instance)

return instance

def _async_init_instance(self, future_result, result):
try:
instance = result.result()
except Exception as exception:
self.__storage.set(self._none)
future_result.set_exception(exception)
else:
self.__storage.set(instance)
future_result.set_result(instance)


cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
"""Delegated thread-local singleton is injected "as is".

Expand Down Expand Up @@ -4645,4 +4734,4 @@ cpdef str _class_qualname(object instance):
name = getattr(instance.__class__, '__qualname__', None)
if not name:
name = '.'.join((instance.__class__.__module__, instance.__class__.__name__))
return name
return name
Loading