Singleton docs update (#288)

* Update docblocks of factory provider examples

* Edit singleton docs
This commit is contained in:
Roman Mogylatov 2020-09-01 16:04:48 -04:00 committed by GitHub
parent 520945483f
commit 0bb30f91ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 150 additions and 180 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,5 +1,7 @@
.. _factory-provider:
Factory provider Factory provider
---------------- ================
.. meta:: .. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Factory,Abstract Factory, :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. .. note:: Any provider has a ``.provider`` attribute.
.. _factory-specialize-provided-type:
Specializing the provided type Specializing the provided type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -112,6 +116,8 @@ class attribute.
:lines: 3- :lines: 3-
:emphasize-lines: 12-14 :emphasize-lines: 12-14
.. _abstract-factory:
Abstract factory Abstract factory
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~

View File

@ -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 .. currentmodule:: dependency_injector.providers
:py:class:`Singleton` provider creates new instance of specified class on :py:class:`Singleton` provider provides single object. It memorizes the first created object and
first call and returns same instance on every next call. returns it on the rest of the calls.
Example:
.. image:: /images/providers/singleton.png
:width: 80%
:align: center
.. literalinclude:: ../../examples/providers/singleton.py .. literalinclude:: ../../examples/providers/singleton.py
:language: python :language: python
:lines: 3-
Singleton providers resetting ``Singleton`` provider handles an injection of the dependencies the same way like a
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :ref:`factory-provider`.
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.
.. note:: .. note::
Due that :py:class:`Singleton` provider creates specified class instance ``Singleton`` provider does dependencies injection only when creates the object. When the object
only on the first call, all injections are done once, during the first is created and memorized ``Singleton`` provider just returns it without applying the injections.
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.
This may cause some problems, for example, in case of trying to bind Specialization of the provided type and abstract singletons work the same like like for the
:py:class:`Factory` provider with :py:class:`Singleton` provider (provided factories:
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.
By the way, in such case, :py:class:`Delegate` or - :ref:`factory-specialize-provided-type`
:py:class:`DelegatedSingleton` provider can be useful - :ref:`abstract-factory`
. It makes possible to inject providers *as is*. Please check out
`Singleton providers delegation`_ section.
Singleton providers delegation Resetting memorized object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
:py:class:`Singleton` provider could be delegated to any other provider via To reset a memorized object you need to call the ``.reset()`` method of the ``Singleton``
any kind of injection. provider.
Delegation of :py:class:`Singleton` providers is the same as .. literalinclude:: ../../examples/providers/singleton_resetting.py
:py:class:`Factory` providers delegation, please follow :language: python
:ref:`factory_providers_delegation` section for examples (with exception :lines: 3-
of using :py:class:`DelegatedSingleton` instead of :emphasize-lines: 14
:py:class:`DelegatedFactory`).
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 Using singleton with multiple threads
declaring its subclasses. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Specialization of :py:class:`Singleton` providers is the same as ``Singleton`` provider is NOT thread-safe. You need to explicitly establish a synchronization for
:py:class:`Factory` providers specialization, please follow using the ``Singleton`` provider in the multi-threading application. Otherwise you could trap
:ref:`factory_providers_specialization` section for examples. 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 + :py:class:`ThreadSafeSingleton` - is a thread-safe version of a ``Singleton`` provider. You can use
must be explicitly overridden before calling. in multi-threading applications without additional synchronization.
+ :py:class:`ThreadLocalSingleton` - is a singleton provider that uses thread-locals as a storage.
Behaviour of :py:class:`AbstractSingleton` providers is the same as of This type of singleton will manage multiple objects - the one object for the one thread.
: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:
.. literalinclude:: ../../examples/providers/singleton_thread_locals.py .. literalinclude:: ../../examples/providers/singleton_thread_locals.py
:language: python :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:: .. disqus::

View File

@ -1,4 +1,4 @@
"""`Factory` providers example.""" """`Factory` provider example."""
from dependency_injector import providers from dependency_injector import providers

View File

@ -1,4 +1,4 @@
"""`Factory` providers delegation example.""" """`Factory` provider delegation example."""
from typing import Callable, List from typing import Callable, List

View File

@ -1,4 +1,4 @@
"""`Factory` providers init injections example.""" """`Factory` provider init injections example."""
from dependency_injector import providers from dependency_injector import providers

View File

@ -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 from dependency_injector import providers

View File

@ -1,19 +1,16 @@
"""`Singleton` providers example.""" """`Singleton` provider example."""
import collections from dependency_injector import providers
import dependency_injector.providers as 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: user_service_provider = providers.Singleton(UserService)
users_service1 = users_service_provider()
users_service2 = users_service_provider()
# 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

View File

@ -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)
# <class '__main__.ServiceProvider'> can provide only
# <class '__main__.BaseService'> instances

View File

@ -1,27 +1,19 @@
"""`Singleton` providers resetting example.""" """`Singleton` provider resetting example."""
import collections from dependency_injector import providers
import dependency_injector.providers as providers
UsersService = collections.namedtuple('UsersService', []) class UserService:
...
# Users service singleton provider:
users_service_provider = providers.Singleton(UsersService)
# Retrieving several UsersService objects: user_service_provider = providers.Singleton(UserService)
users_service1 = users_service_provider()
users_service2 = users_service_provider()
# Making some asserts:
assert users_service1 is users_service2
# Resetting of memorized instance: if __name__ == '__main__':
users_service_provider.reset() user_service1 = user_service_provider()
# Retrieving one more UserService object: user_service_provider.reset()
users_service3 = users_service_provider()
# Making some asserts: users_service2 = user_service_provider()
assert users_service3 is not users_service1 assert users_service2 is not user_service1

View File

@ -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()

View File

@ -1,53 +1,42 @@
"""`ThreadLocalSingleton` providers example.""" """`ThreadLocalSingleton` provider example."""
import threading import threading
import queue import queue
import dependency_injector.providers as providers from dependency_injector import providers
def example(example_object, queue_object): def put_in_queue(example_object, queue_object):
"""Put provided object in the provided queue."""
queue_object.put(example_object) queue_object.put(example_object)
# Create thread-local singleton provider for some object (main thread):
thread_local_object = providers.ThreadLocalSingleton(object) thread_local_object = providers.ThreadLocalSingleton(object)
queue_provider = providers.ThreadSafeSingleton(queue.Queue)
# Create singleton provider for thread-safe queue: put_in_queue = providers.Callable(
queue_factory = providers.ThreadSafeSingleton(queue.Queue) put_in_queue,
# Create callable provider for example(), inject dependencies:
example = providers.DelegatedCallable(
example,
example_object=thread_local_object, 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__': if __name__ == '__main__':
# Create 10 threads for concurrent execution of example():
threads = [] threads = []
for thread_number in range(10): for thread_number in range(10):
threads.append( threads.append(
thread_factory(name='Thread{0}'.format(thread_number)), thread_factory(name='Thread{0}'.format(thread_number)),
) )
# Start execution of all created threads:
for thread in threads: for thread in threads:
thread.start() thread.start()
# Wait while threads would complete their work:
for thread in threads: for thread in threads:
thread.join() thread.join()
# Making some asserts (main thread):
all_objects = set() all_objects = set()
while not queue_provider().empty():
while not queue_factory().empty(): all_objects.add(queue_provider().get())
all_objects.add(queue_factory().get())
assert len(all_objects) == len(threads) assert len(all_objects) == len(threads)
# Queue contains same number of objects as number of threads where # Queue contains same number of objects as number of threads where