Skip to content
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 change: 1 addition & 0 deletions CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ Dependency Injector Contributors
================================

+ Roman Mogilatov
+ Konstantin vz'One Enchant
6 changes: 6 additions & 0 deletions dependency_injector/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
DelegatedFactory,
Singleton,
DelegatedSingleton,
ThreadLocalSingleton,
DelegatedThreadLocalSingleton
)


Expand All @@ -34,6 +36,10 @@

'Factory',
'DelegatedFactory',

'Singleton',
'DelegatedSingleton',

'ThreadLocalSingleton',
'DelegatedThreadLocalSingleton',
)
102 changes: 102 additions & 0 deletions dependency_injector/providers/creational.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Dependency injector creational providers."""

import threading

import six

from dependency_injector.providers.callable import Callable
Expand Down Expand Up @@ -247,3 +249,103 @@ def provide_injection(self):
:rtype: object
"""
return self


class ThreadLocalSingleton(Factory):
""":py:class:`ThreadLocalSingleton` is singleton based on thread locals.

:py:class:`ThreadLocalSingleton` provider creates instance once for each
thread and returns it on every call. :py:class:`ThreadLocalSingleton`
extends :py:class:`Factory`, so, please follow :py:class:`Factory`
documentation for getting familiar with injections syntax.

:py:class:`ThreadLocalSingleton` is thread-safe and could be used in
multithreading environment without any negative impact.

Retrieving of provided instance can be performed via calling
:py:class:`ThreadLocalSingleton` object:

.. code-block:: python

singleton = ThreadLocalSingleton(SomeClass)
some_object = singleton()

.. py:attribute:: provided_type

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

:type: type | None

.. py:attribute:: cls

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

:type: type
"""

__slots__ = ('local_storage',)

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

:param provides: Class or other callable that provides object
for creation.
:type provides: type | callable
"""
self.local_storage = threading.local()
super(ThreadLocalSingleton, self).__init__(provides, *args, **kwargs)

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

:rtype: None
"""
self.local_storage.instance = None

def _provide(self, *args, **kwargs):
"""Return provided instance.

:param args: Tuple of context positional arguments.
:type args: tuple[object]

:param kwargs: Dictionary of context keyword arguments.
:type kwargs: dict[str, object]

:rtype: object
"""
try:
instance = self.local_storage.instance
except AttributeError:
instance = super(ThreadLocalSingleton, self)._provide(*args,
**kwargs)
self.local_storage.instance = instance
finally:
return instance


class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
""":py:class:`ThreadLocalSingleton` that is injected "as is".

.. py:attribute:: provided_type

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

:type: type | None

.. py:attribute:: cls

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

:type: type
"""

def provide_injection(self):
"""Injection strategy implementation.

:rtype: object
"""
return self
Binary file modified docs/images/providers/providers_class_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion docs/main/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ follows `Semantic versioning`_

Development version
-------------------
- No features.
- Add ``ThreadLocalSingleton`` and ``DelegatedThreadLocalSingleton`` providers.
- Add documentation section about singleton providers and multi-threading.

2.0.0
------
Expand Down
19 changes: 19 additions & 0 deletions docs/providers/singleton.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,22 @@ declaring its subclasses.
Specialization of :py:class:`Singleton` providers is the same as
:py:class:`Factory` providers specialization, please follow
:ref:`factory_providers_specialization` section for examples.

Singleton providers and multi-threading
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:py:class:`Singleton` provider is thread-safe and could be used in
multi-threading applications without any negative impact. Race condition on
singleton's initialization is escaped by using a global reentrant mutex -
:py:obj:`dependency_injector.utils.GLOBAL_LOCK`.

Also there could be a need to use thread-scoped singletons and there is a
special provider for such case - :py:class:`ThreadLocalSingleton`.
:py:class:`ThreadLocalSingleton` provider creates instance once for each
thread and returns it on every call.

Example:

.. literalinclude:: ../../examples/providers/singleton_thread_locals.py
:language: python
:linenos:
50 changes: 50 additions & 0 deletions examples/providers/singleton_thread_locals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""`ThreadLocalSingleton` providers example."""

import threading
import Queue

import dependency_injector.providers as providers


def example(example_object, queue):
"""Example function that puts provided object in the provided queue."""
queue.put(example_object)

# Create thread-local singleton provider for some object (main thread):
thread_local_object = providers.ThreadLocalSingleton(object)

# Create singleton provider for thread-safe queue:
queue = providers.Singleton(Queue.Queue)

# Create callable provider for example(), inject dependencies:
example = providers.DelegatedCallable(example,
example_object=thread_local_object,
queue=queue)

# Create factory provider for threads that are targeted to execute example():
thread_factory = providers.Factory(threading.Thread,
target=example)

if __name__ == '__main__':
# Create 10 threads for concurrent execution of example():
threads = []
for thread_number in xrange(10):
threads.append(thread_factory(name='Thread{0}'.format(thread_number)))

# Start execution of all created threads:
for thread in threads:
thread.start()

# Wait while threads would complete their work:
for thread in threads:
thread.join()

# Making some asserts (main thread):
all_objects = set()

while not queue().empty():
all_objects.add(queue().get())

assert len(all_objects) == len(threads)
# Queue contains same number of objects as number of threads where
# thread-local singleton provider was used.
Loading