diff --git a/docs/images/providers/singleton.png b/docs/images/providers/singleton.png deleted file mode 100644 index 06e8996f..00000000 Binary files a/docs/images/providers/singleton.png and /dev/null differ diff --git a/docs/providers/factory.rst b/docs/providers/factory.rst index 7708f25e..6050b530 100644 --- a/docs/providers/factory.rst +++ b/docs/providers/factory.rst @@ -1,5 +1,7 @@ +.. _factory-provider: + Factory provider ----------------- +================ .. meta:: :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Factory,Abstract Factory, @@ -100,6 +102,8 @@ attribute of the provider that you're going to inject. .. note:: Any provider has a ``.provider`` attribute. +.. _factory-specialize-provided-type: + Specializing the provided type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -112,6 +116,8 @@ class attribute. :lines: 3- :emphasize-lines: 12-14 +.. _abstract-factory: + Abstract factory ~~~~~~~~~~~~~~~~ diff --git a/docs/providers/singleton.rst b/docs/providers/singleton.rst index f479770f..e8fe4452 100644 --- a/docs/providers/singleton.rst +++ b/docs/providers/singleton.rst @@ -1,111 +1,100 @@ -Singleton providers -------------------- +Singleton provider +------------------ + +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Singleton,Pattern,Example, + Threads,Multithreading,Scoped + :description: Singleton provider helps to provide a single object. This page + demonstrates how to use a Singleton provider. It also provides the example + of using a singleton and thread locals singleton in the multi-threaded + environment. .. currentmodule:: dependency_injector.providers -:py:class:`Singleton` provider creates new instance of specified class on -first call and returns same instance on every next call. - -Example: - -.. image:: /images/providers/singleton.png - :width: 80% - :align: center +:py:class:`Singleton` provider provides single object. It memorizes the first created object and +returns it on the rest of the calls. .. literalinclude:: ../../examples/providers/singleton.py :language: python + :lines: 3- -Singleton providers resetting -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Created and memorized by :py:class:`Singleton` instance can be reset. Reset of -:py:class:`Singleton`'s memorized instance is done by clearing reference to -it. Further lifecycle of memorized instance is out of :py:class:`Singleton` -provider's control and depends on garbage collection strategy. - -Example: - -.. literalinclude:: ../../examples/providers/singleton_resetting.py - :language: python - -Singleton providers and injections -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Singleton` provider has same interface as :py:class:`Factory` -provider, so, all of the rules about injections are the same, as for -:py:class:`Factory` provider. +``Singleton`` provider handles an injection of the dependencies the same way like a +:ref:`factory-provider`. .. note:: - Due that :py:class:`Singleton` provider creates specified class instance - only on the first call, all injections are done once, during the first - call. Every next call, while instance has been already created - and memorized, no injections are done, :py:class:`Singleton` provider just - returns memorized earlier instance. + ``Singleton`` provider does dependencies injection only when creates the object. When the object + is created and memorized ``Singleton`` provider just returns it without applying the injections. - This may cause some problems, for example, in case of trying to bind - :py:class:`Factory` provider with :py:class:`Singleton` provider (provided - by dependent :py:class:`Factory` instance will be injected only once, - during the first call). Be aware that such behaviour was made with opened - eyes and is not a bug. +Specialization of the provided type and abstract singletons work the same like like for the +factories: - By the way, in such case, :py:class:`Delegate` or - :py:class:`DelegatedSingleton` provider can be useful - . It makes possible to inject providers *as is*. Please check out - `Singleton providers delegation`_ section. +- :ref:`factory-specialize-provided-type` +- :ref:`abstract-factory` -Singleton providers delegation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Resetting memorized object +~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Singleton` provider could be delegated to any other provider via -any kind of injection. +To reset a memorized object you need to call the ``.reset()`` method of the ``Singleton`` +provider. -Delegation of :py:class:`Singleton` providers is the same as -:py:class:`Factory` providers delegation, please follow -:ref:`factory_providers_delegation` section for examples (with exception -of using :py:class:`DelegatedSingleton` instead of -:py:class:`DelegatedFactory`). +.. literalinclude:: ../../examples/providers/singleton_resetting.py + :language: python + :lines: 3- + :emphasize-lines: 14 -Singleton providers specialization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: + Resetting of the memorized object clears the reference to it. Further object's lifecycle is + managed by the garbage collector. -:py:class:`Singleton` provider could be specialized for any kind of needs via -declaring its subclasses. +Using singleton with multiple threads +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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`` provider is NOT thread-safe. You need to explicitly establish a synchronization for +using the ``Singleton`` provider in the multi-threading application. Otherwise you could trap +into the race condition problem: ``Singleton`` will create multiple objects. -Abstract singleton providers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +There are two thread-safe singleton implementations out of the box: -:py:class:`AbstractSingleton` provider is a :py:class:`Singleton` provider that -must be explicitly overridden before calling. - -Behaviour of :py:class:`AbstractSingleton` providers is the same as of -:py:class:`AbstractFactory`, please follow :ref:`abstract_factory_providers` -section for examples (with exception of using :py:class:`AbstractSingleton` -provider instead of :py:class:`AbstractFactory`). - -Singleton providers and multi-threading -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Singleton` provider is NOT thread-safe and should be used in -multi-threading applications with manually controlled locking. - -:py:class:`ThreadSafeSingleton` is a thread-safe version of -:py:class:`Singleton` and could be used in multi-threading applications -without any additional locking. - -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: ++ :py:class:`ThreadSafeSingleton` - is a thread-safe version of a ``Singleton`` provider. You can use + in multi-threading applications without additional synchronization. ++ :py:class:`ThreadLocalSingleton` - is a singleton provider that uses thread-locals as a storage. + This type of singleton will manage multiple objects - the one object for the one thread. .. literalinclude:: ../../examples/providers/singleton_thread_locals.py :language: python + :lines: 3- + :emphasize-lines: 11,12 +Implementing scopes +~~~~~~~~~~~~~~~~~~~ + +To implement a scoped singleton provider use a ``Singleton`` provider and reset its scope when +needed. + +.. literalinclude:: ../../examples/providers/singleton_scoped.py + :language: python + :lines: 3- + +The output should look like this (each request a ``Service`` object has a different address): + +.. code-block:: + + * Serving Flask app "singleton_scoped" (lazy loading) + * Environment: production + WARNING: This is a development server. Do not use it in a production deployment. + Use a production WSGI server instead. + * Debug mode: off + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + <__main__.Service object at 0x1099a9d90> + 127.0.0.1 - - [25/Aug/2020 17:33:11] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9cd0> + 127.0.0.1 - - [25/Aug/2020 17:33:17] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9d00> + 127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9e50> + 127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9d90> + 127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 - .. disqus:: diff --git a/examples/providers/factory.py b/examples/providers/factory.py index 33453dd3..c83ded02 100644 --- a/examples/providers/factory.py +++ b/examples/providers/factory.py @@ -1,4 +1,4 @@ -"""`Factory` providers example.""" +"""`Factory` provider example.""" from dependency_injector import providers diff --git a/examples/providers/factory_delegation.py b/examples/providers/factory_delegation.py index f6bc02d1..00f3fec5 100644 --- a/examples/providers/factory_delegation.py +++ b/examples/providers/factory_delegation.py @@ -1,4 +1,4 @@ -"""`Factory` providers delegation example.""" +"""`Factory` provider delegation example.""" from typing import Callable, List diff --git a/examples/providers/factory_init_injections.py b/examples/providers/factory_init_injections.py index 682ace80..d720edb6 100644 --- a/examples/providers/factory_init_injections.py +++ b/examples/providers/factory_init_injections.py @@ -1,4 +1,4 @@ -"""`Factory` providers init injections example.""" +"""`Factory` provider init injections example.""" from dependency_injector import providers diff --git a/examples/providers/factory_init_injections_underlying.py b/examples/providers/factory_init_injections_underlying.py index cf49388e..5c9f196f 100644 --- a/examples/providers/factory_init_injections_underlying.py +++ b/examples/providers/factory_init_injections_underlying.py @@ -1,4 +1,4 @@ -"""`Factory` providers - building a complex object graph with deep init injections example.""" +"""`Factory` provider - passing injections to the underlying providers example.""" from dependency_injector import providers diff --git a/examples/providers/singleton.py b/examples/providers/singleton.py index 97fe5c02..9e94b76c 100644 --- a/examples/providers/singleton.py +++ b/examples/providers/singleton.py @@ -1,19 +1,16 @@ -"""`Singleton` providers example.""" +"""`Singleton` provider example.""" -import collections - -import dependency_injector.providers as providers +from dependency_injector import providers -UsersService = collections.namedtuple('UsersService', []) +class UserService: + ... -# Singleton provider creates new instance of specified class on first call -# and returns same instance on every next call. -users_service_provider = providers.Singleton(UsersService) -# Retrieving several UserService objects: -users_service1 = users_service_provider() -users_service2 = users_service_provider() +user_service_provider = providers.Singleton(UserService) -# Making some asserts: -assert users_service1 is users_service2 + +if __name__ == '__main__': + user_service1 = user_service_provider() + user_service2 = user_service_provider() + assert user_service1 is user_service2 diff --git a/examples/providers/singleton_provided_type.py b/examples/providers/singleton_provided_type.py deleted file mode 100644 index 1402fd5c..00000000 --- a/examples/providers/singleton_provided_type.py +++ /dev/null @@ -1,35 +0,0 @@ -"""`Singleton` specialization for limitation to provided type example.""" - -import dependency_injector.providers as providers -import dependency_injector.errors as errors - - -class BaseService: - """Base service class.""" - - -class UsersService(BaseService): - """Users service.""" - - -class PhotosService(BaseService): - """Photos service.""" - - -class ServiceProvider(providers.Singleton): - """Service provider.""" - - provided_type = BaseService - - -# Creating several service providers with BaseService instances: -users_service_provider = ServiceProvider(UsersService) -photos_service_provider = ServiceProvider(PhotosService) - -# Trying to create service provider with not a BaseService instance: -try: - some_service_provider = ServiceProvider(object) -except errors.Error as exception: - print(exception) - # can provide only - # instances diff --git a/examples/providers/singleton_resetting.py b/examples/providers/singleton_resetting.py index 8fe2f5a7..edec0594 100644 --- a/examples/providers/singleton_resetting.py +++ b/examples/providers/singleton_resetting.py @@ -1,27 +1,19 @@ -"""`Singleton` providers resetting example.""" +"""`Singleton` provider resetting example.""" -import collections - -import dependency_injector.providers as providers +from dependency_injector import providers -UsersService = collections.namedtuple('UsersService', []) +class UserService: + ... -# Users service singleton provider: -users_service_provider = providers.Singleton(UsersService) -# Retrieving several UsersService objects: -users_service1 = users_service_provider() -users_service2 = users_service_provider() +user_service_provider = providers.Singleton(UserService) -# Making some asserts: -assert users_service1 is users_service2 -# Resetting of memorized instance: -users_service_provider.reset() +if __name__ == '__main__': + user_service1 = user_service_provider() -# Retrieving one more UserService object: -users_service3 = users_service_provider() + user_service_provider.reset() -# Making some asserts: -assert users_service3 is not users_service1 + users_service2 = user_service_provider() + assert users_service2 is not user_service1 diff --git a/examples/providers/singleton_scoped.py b/examples/providers/singleton_scoped.py new file mode 100644 index 00000000..3ab27beb --- /dev/null +++ b/examples/providers/singleton_scoped.py @@ -0,0 +1,32 @@ +"""`Singleton` - flask request scope example.""" + +from dependency_injector import providers +from flask import Flask + + +class Service: + ... + + +service_provider = providers.Singleton(Service) + + +def index_view(): + service_1 = service_provider() + service_2 = service_provider() + assert service_1 is service_2 + print(service_1) + return 'Hello World!' + + +def teardown_context(request): + service_provider.reset() + return request + + +app = Flask(__name__) +app.add_url_rule('/', 'index', view_func=index_view) +app.after_request(teardown_context) + +if __name__ == '__main__': + app.run() diff --git a/examples/providers/singleton_thread_locals.py b/examples/providers/singleton_thread_locals.py index 7c1a0aa3..83325559 100644 --- a/examples/providers/singleton_thread_locals.py +++ b/examples/providers/singleton_thread_locals.py @@ -1,53 +1,42 @@ -"""`ThreadLocalSingleton` providers example.""" +"""`ThreadLocalSingleton` provider example.""" import threading import queue -import dependency_injector.providers as providers +from dependency_injector import providers -def example(example_object, queue_object): - """Put provided object in the provided queue.""" +def put_in_queue(example_object, queue_object): queue_object.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_factory = providers.ThreadSafeSingleton(queue.Queue) - -# Create callable provider for example(), inject dependencies: -example = providers.DelegatedCallable( - example, +queue_provider = providers.ThreadSafeSingleton(queue.Queue) +put_in_queue = providers.Callable( + put_in_queue, example_object=thread_local_object, - queue_object=queue_factory, + queue_object=queue_provider, +) +thread_factory = providers.Factory( + threading.Thread, + target=put_in_queue.provider, ) -# Create factory 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 range(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_factory().empty(): - all_objects.add(queue_factory().get()) + while not queue_provider().empty(): + all_objects.add(queue_provider().get()) assert len(all_objects) == len(threads) # Queue contains same number of objects as number of threads where