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
----------------
================
.. 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
~~~~~~~~~~~~~~~~

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
: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::

View File

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

View File

@ -1,4 +1,4 @@
"""`Factory` providers delegation example."""
"""`Factory` provider delegation example."""
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

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

View File

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

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
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

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 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