Merge branch 'release/3.36.0' into master

This commit is contained in:
Roman Mogylatov 2020-09-02 21:28:29 -04:00
commit f5758d842f
31 changed files with 198 additions and 271 deletions

View File

@ -52,7 +52,7 @@ master_doc = 'index'
# General information about the project.
project = u'Dependency Injector'
copyright = u'2017, ETS Labs'
copyright = u'2020, ETS Labs'
author = u'ETS Labs'
# The version info for the project you're documenting, acts as replacement for

View File

@ -1,5 +1,5 @@
Overriding of the container
---------------------------
Container overriding
--------------------
.. currentmodule:: dependency_injector.containers

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -7,6 +7,15 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
3.36.0
------
- Update providers overriding documentation and rework examples.
- Update documentation on injecting provided object attributes, items or method calls.
- Update documentation and example on creating a custom provider.
- Update providers index documentation page to give better overview of providers functionality.
- Fix mypy stub of the ``Provider`` to specify the protected ``._copy_overridings()`` method.
- Update copyright year in the documentation.
3.35.1
------
- Fix minor issues in the providers documentation and examples.

View File

@ -1,5 +1,5 @@
Callable provider
-----------------
=================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Function,Method,Example

View File

@ -1,5 +1,5 @@
Configuration provider
----------------------
======================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
@ -20,7 +20,7 @@ Configuration provider
It implements the principle "use first, define later".
Loading from an INI file
~~~~~~~~~~~~~~~~~~~~~~~~
------------------------
``Configuration`` provider can load configuration from an ``ini`` file using the
:py:meth:`Configuration.from_ini` method:
@ -40,7 +40,7 @@ where ``examples/providers/configuration/config.ini`` is:
variable ``ENV_NAME``.
Loading from a YAML file
~~~~~~~~~~~~~~~~~~~~~~~~
------------------------
``Configuration`` provider can load configuration from a ``yaml`` file using the
:py:meth:`Configuration.from_yaml` method:
@ -74,7 +74,7 @@ variable ``ENV_NAME``.
*Don't forget to mirror the changes in the requirements file.*
Loading from a dictionary
~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------
``Configuration`` provider can load configuration from a Python ``dict`` using the
:py:meth:`Configuration.from_dict` method:
@ -85,7 +85,7 @@ Loading from a dictionary
:emphasize-lines: 6-13
Loading from an environment variable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
------------------------------------
``Configuration`` provider can load configuration from an environment variable using the
:py:meth:`Configuration.from_env` method:
@ -96,7 +96,7 @@ Loading from an environment variable
:emphasize-lines: 6-8
Loading from the multiple sources
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------------------------
``Configuration`` provider can load configuration from the multiple sources. Loaded
configuration is merged recursively over the existing configuration.
@ -112,7 +112,7 @@ where ``examples/providers/configuration/config.local.yml`` is:
:language: ini
Specifying the value type
~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------
You can specify the type of the injected configuration value explicitly.

View File

@ -1,5 +1,5 @@
Coroutine provider
------------------
==================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Coroutine,Asynchronous,

View File

@ -1,35 +1,45 @@
Writing of custom providers
---------------------------
.. _create-provider:
Creating a custom provider
==========================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Custom provider, Create
:description: This page demonstrates how to create a custom provider.
.. currentmodule:: dependency_injector.providers
List of *Dependency Injector* providers could be widened with custom providers.
You can create a custom provider.
Below are some tips and recommendations that have to be met:
To create a custom provider you need to follow these rules:
1. Every custom provider has to extend base provider class -
:py:class:`Provider`.
2. Custom provider's ``__init__()`` could be overridden, but parent's
initializer (:py:meth:`Provider.__init__`) has to be called.
3. Providing strategy has to be implemented in custom provider's
:py:meth:`Provider.__call__` method.
4. If custom provider is based on some standard providers, it is better to
use delegation of standard providers, then extending of them.
5. If custom provider defines any attributes, it is good to list them in
``__slots__`` attribute (as *Dependency Injector* does). It can save
some memory.
6. If custom provider deals with injections, it is strongly recommended
to be consistent with :py:class:`Factory`, :py:class:`Singleton` and
:py:class:`Callable` providers style.
Example:
.. image:: /images/providers/custom_provider.png
:width: 100%
:align: center
1. New provider class should inherit :py:class:`Provider`.
2. You need to implement the ``Provider._provide()`` method.
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
equivalent copy of a provider. All providers must be copied with a ``deepcopy()`` function
from the ``providers`` module. After the a new provider object is created use
``Provider._copy_overriding()`` method to copy all overriding providers. See the example
below.
4. If the new provider has a ``__init__()`` method, it should call the parent
``Provider.__init__()``.
.. literalinclude:: ../../examples/providers/custom_factory.py
:language: python
:lines: 3-
.. note::
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
2. When create a new provider follow the ``Factory``-like injections style. Consistency matters.
3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
will also save some memory.
.. note::
If you don't find needed provider in the ``providers`` module and experience troubles creating
one by your own - open a
`Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_.
I'll help you to resolve the issue if that's possible. If the new provider can be useful for
others I'll include it into the ``providers`` module.
.. disqus::

View File

@ -1,5 +1,5 @@
Dependency provider
-------------------
===================
.. currentmodule:: dependency_injector.providers

View File

@ -41,7 +41,7 @@ injected following these rules:
:lines: 3-
Passing arguments to the underlying providers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---------------------------------------------
``Factory`` provider can pass the arguments to the underlying providers. This helps when you need
to assemble a nested objects graph and pass the arguments deep inside.
@ -88,7 +88,7 @@ If ``<dependency>`` is found the underlying provider will receive the
.. _factory_providers_delegation:
Passing providers to the objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--------------------------------
When you need to inject the provider itself, but not the result of its call, use the ``.provider``
attribute of the provider that you're going to inject.
@ -105,7 +105,7 @@ attribute of the provider that you're going to inject.
.. _factory-specialize-provided-type:
Specializing the provided type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
------------------------------
You can create a specialized ``Factory`` provider that provides only specific type. For doing
this you need to create a subclass of the ``Factory`` provider and define the ``provided_type``
@ -119,7 +119,7 @@ class attribute.
.. _abstract-factory:
Abstract factory
~~~~~~~~~~~~~~~~
----------------
:py:class:`AbstractFactory` provider helps when you need to create a provider of some base class
and the particular implementation is not yet know. ``AbstractFactory`` provider is a ``Factory``
@ -138,7 +138,7 @@ provider with two peculiarities:
:emphasize-lines: 32
Factory aggregate
~~~~~~~~~~~~~~~~~
-----------------
:py:class:`FactoryAggregate` provider aggregates multiple factories. When you call the
``FactoryAggregate`` it delegates the call to one of the factories.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -7,7 +7,8 @@ Providers help to assemble the objects. They create objects and inject the depen
Each provider is a callable. You call the provider like a function when you need to create an
object. Provider retrieves the underlying dependencies and inject them into the created object.
It causes the cascade effect that helps to assemble object graphs.
It causes the cascade effect that helps to assemble object graphs. See ``Factory``, ``Singleton``,
``Callable`` and other provider docs below.
.. code-block:: bash
@ -23,10 +24,13 @@ It causes the cascade effect that helps to assemble object graphs.
└──> provider6()
Another providers feature is an overriding. Any of the providers can be overridden by another
provider. When provider is overridden it calls to the overriding provider instead of providing
the object by its own. This helps in testing. This also helps in overriding API clients with
stubs for the development or staging environment.
Another providers feature is an overriding. You can override any provider by another provider.
This helps in testing. This also helps in overriding API clients with stubs for the development
or staging environment. See the example at :ref:`provider-overriding`.
If you need to inject not the whole object but the parts see :ref:`provided-instance`.
To create a new provider see :ref:`create-provider`.
Providers module API docs - :py:mod:`dependency_injector.providers`

View File

@ -1,5 +1,5 @@
List provider
-------------
=============
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,List,Injection

View File

@ -1,5 +1,5 @@
Object provider
---------------
===============
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Object

View File

@ -1,47 +1,43 @@
Overriding of providers
-----------------------
.. _provider-overriding:
Provider overriding
===================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Override,Test,Unit
:description: This page demonstrates how to implement providers overriding. This helps in
testing and configuring the system for the multiple environments.
.. currentmodule:: dependency_injector.providers
Every provider could be overridden by another provider.
You can override any provider by another provider.
This gives opportunity to make system behaviour more flexible at some point.
The main feature is that while your code is using providers, it depends on
providers, but not on the objects that providers provide. As a result of this,
you can change providing by provider object to a different one, but still
compatible one, without changing your previously written code.
When provider is overridden it calls to the overriding provider instead of providing
the object by its own.
Provider overriding functionality has such interface:
This helps in testing. This also helps in overriding API clients with stubs for the development
or staging environment.
.. image:: /images/providers/provider_override.png
:width: 55%
:align: center
To override a provider you need to call the ``Provider.override()`` method. This method receives
a single argument called ``overriding``. If the ``overriding`` value is a provider, this provider
is called instead of the original. If value is not a provider, this value is returned instead of
calling the original provider.
+ :py:meth:`Provider.override()` - takes another provider that will be used
instead of current provider. This method could be called several times.
In such case, last passed provider would be used as overriding one.
+ :py:meth:`Provider.reset_override()` - resets all overriding providers.
Provider starts to behave itself like usual.
+ :py:meth:`Provider.reset_last_overriding()` - remove last overriding
provider from stack of overriding providers.
Example:
.. image:: /images/providers/overriding_simple.png
.. image:: images/overriding.png
:width: 80%
:align: center
.. literalinclude:: ../../examples/providers/overriding_simple.py
.. literalinclude:: ../../examples/providers/overriding.py
:language: python
:lines: 3-
Example:
You can override a provider multiple times. In that case the latest ``overriding`` value will be
used. The rest of the overriding values will form a stack.
.. image:: /images/providers/overriding_users_model.png
:width: 100%
:align: center
.. literalinclude:: ../../examples/providers/overriding_users_model.py
:language: python
To reset an overriding you can use the ``Provider.reset_override()`` or
``Provider.reset_last_overriding()`` methods.
You can use a context manager for overriding a provider ``with Provider.override():``. The
overriding will be reset when context closed.
.. disqus::

View File

@ -1,13 +1,16 @@
Injecting attributes, items, or call methods of the provided instance
---------------------------------------------------------------------
.. _provided-instance:
Injecting provided object attributes, items, or call its methods
================================================================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Attribute,Method,Call
:description: This page demonstrates how to inject attributes, items or call method of the
provided instance.
.. currentmodule:: dependency_injector.providers
In this section you will know how to inject provided instance attribute or item into the other
provider.
It also describes how to call a method of the provided instance and use the result of
this call as an injection value.
You can inject provided object attribute, item or result of its method call.
.. literalinclude:: ../../examples/providers/provided_instance.py
:language: python
@ -15,14 +18,14 @@ this call as an injection value.
:lines: 3-
To use the feature you should use the ``.provided`` attribute of the injected provider. This
attribute helps to specify what happens with the provided instance. You can retrieve an injection
value from:
attribute helps to specify what happens with the provided instance before the injection. You can
use any combination of the following:
- an attribute of the provided instance
- an item of the provided instance
- a call of the provided instance method
- an attribute of the provided object
- an item of the provided object
- a call of the provided object method
When you use the call of the provided instance method you can specify the injections into this
When you use a call of the provided instance method you can specify the injections for this
method like you do with any other provider.
You can do nested constructions:
@ -32,35 +35,24 @@ You can do nested constructions:
:emphasize-lines: 24-30
:lines: 3-
Attribute ``.provided`` is available for the providers that return instances. Providers that
have ``.provided`` attribute:
The ``.provided`` attribute is available for the next providers:
- :py:class:`Callable` and its subclasses
- :py:class:`Factory` and its subclasses
- :py:class:`Singleton` and its subclasses
- :py:class:`Callable` and its subclasses
- :py:class:`Object`
- :py:class:`List`
- :py:class:`Selector`
- :py:class:`Dependency`
Special providers like :py:class:`Configuration` or :py:class:`Delegate` do not have the
``.provided`` attribute.
Provider subclasses
-------------------
When you create a new provider subclass and want to implement the ``.provided`` attribute, you
should use the :py:class:`ProvidedInstance` provider.
should use the :py:class:`ProvidedInstance` provider. Add the ``.provided`` property
implementation to a new subclass:
.. code-block:: python
@property
def provided(self):
"""Return :py:class:`ProvidedInstance` provider."""
return ProvidedInstance(self)
In all other cases you should not use :py:class:`ProvidedInstance`, :py:class:`AttributeGetter`,
:py:class:`ItemGetter`, or :py:class:`MethodCaller` providers directly. Use the ``.provided``
attribute of the injected provider instead.
.. disqus::

View File

@ -1,7 +1,7 @@
.. _selector-provider:
Selector provider
-----------------
=================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,

View File

@ -1,5 +1,5 @@
Singleton provider
------------------
==================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Singleton,Pattern,Example,
@ -33,7 +33,7 @@ factories:
- :ref:`abstract-factory`
Resetting memorized object
~~~~~~~~~~~~~~~~~~~~~~~~~~
--------------------------
To reset a memorized object you need to call the ``.reset()`` method of the ``Singleton``
provider.
@ -48,7 +48,7 @@ provider.
managed by the garbage collector.
Using singleton with multiple threads
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------------------
``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
@ -67,7 +67,7 @@ There are two thread-safe singleton implementations out of the box:
:emphasize-lines: 11,12
Implementing scopes
~~~~~~~~~~~~~~~~~~~
-------------------
To implement a scoped singleton provider use a ``Singleton`` provider and reset its scope when
needed.

View File

@ -1,40 +1,42 @@
"""Custom `Factory` example."""
"""Custom provider example."""
import dependency_injector.providers as providers
from dependency_injector import providers
class User:
"""Example class User."""
class UsersFactory(providers.Provider):
"""Example users factory."""
class CustomFactory(providers.Provider):
__slots__ = ('_factory',)
def __init__(self):
"""Initialize instance."""
self._factory = providers.Factory(User)
def __init__(self, provides, *args, **kwargs):
self._factory = providers.Factory(provides, *args, **kwargs)
super().__init__()
def __call__(self, *args, **kwargs):
"""Return provided object.
def __deepcopy__(self, memo):
copied = memo.get(id(self))
if copied is not None:
return copied
Callable interface implementation.
"""
if self.last_overriding is not None:
return self.last_overriding._provide(args, kwargs)
copied = self.__class__(
self._factory.provides,
*providers.deepcopy(self._factory.args, memo),
**providers.deepcopy(self._factory.kwargs, memo),
)
self._copy_overridings(copied, memo)
return copied
def _provide(self, args, kwargs):
return self._factory(*args, **kwargs)
# Users factory:
users_factory = UsersFactory()
factory = CustomFactory(object)
# Creating several User objects:
user1 = users_factory()
user2 = users_factory()
# Making some asserts:
assert isinstance(user1, User)
assert isinstance(user2, User)
assert user1 is not user2
if __name__ == '__main__':
object1 = factory()
assert isinstance(object1, object)
object2 = factory()
assert isinstance(object1, object)
assert object1 is not object2

View File

@ -0,0 +1,42 @@
"""Simple providers overriding example."""
import unittest.mock
from dependency_injector import providers
class ApiClient:
...
class ApiClientStub(ApiClient):
...
class Service:
def __init__(self, api_client: ApiClient):
self._api_client = api_client
api_client_factory = providers.Factory(ApiClient)
service_factory = providers.Factory(
Service,
api_client=api_client_factory,
)
if __name__ == '__main__':
# 1. Use .override() to replace the API client with stub
api_client_factory.override(providers.Factory(ApiClientStub))
service1 = service_factory()
assert isinstance(service1.api_client, ApiClientStub)
# 2. Use .override() as a context manager to mock the API client in testing
with api_client_factory.override(unittest.mock.Mock(ApiClient)):
service3 = service_factory()
assert isinstance(service3.api_client, unittest.mock.Mock)
# 3. Use .reset_override() to get back to normal
api_client_factory.reset_override()
service3 = service_factory()
assert isinstance(service3.api_client, ApiClient)

View File

@ -1,36 +0,0 @@
"""Simple providers overriding example."""
import dependency_injector.providers as providers
class User:
"""Example class User."""
# Users factory:
users_factory = providers.Factory(User)
# Creating several User objects:
user1 = users_factory()
user2 = users_factory()
# Making some asserts:
assert user1 is not user2
assert isinstance(user1, User) and isinstance(user2, User)
# Extending User:
class SuperUser(User):
"""Example class SuperUser."""
# Overriding users factory:
users_factory.override(providers.Factory(SuperUser))
# Creating some more User objects using overridden users factory:
user3 = users_factory()
user4 = users_factory()
# Making some asserts:
assert user4 is not user3
assert isinstance(user3, SuperUser) and isinstance(user4, SuperUser)

View File

@ -1,95 +0,0 @@
"""Overriding user's model example."""
import dependency_injector.providers as providers
class User:
"""Example class User."""
def __init__(self, id, password):
"""Initialize instance."""
self.id = id
self.password = password
super().__init__()
class UsersService:
"""Example class UsersService."""
def __init__(self, user_cls):
"""Initialize instance."""
self.user_cls = user_cls
super().__init__()
def get_by_id(self, id):
"""Find user by his id and return user model."""
return self.user_cls(id=id, password='secret' + str(id))
# Users factory and UsersService provider:
users_service = providers.Factory(UsersService, user_cls=User)
# Getting several users and making some asserts:
user1 = users_service().get_by_id(1)
user2 = users_service().get_by_id(2)
assert isinstance(user1, User)
assert user1.id == 1
assert user1.password == 'secret1'
assert isinstance(user2, User)
assert user2.id == 2
assert user2.password == 'secret2'
assert user1 is not user2
# Extending user model and user service for adding custom attributes without
# making any changes to client's code.
class ExtendedUser(User):
"""Example class ExtendedUser."""
def __init__(self, id, password, first_name=None, last_name=None,
gender=None):
"""Initialize instance."""
self.first_name = first_name
self.last_name = last_name
self.gender = gender
super().__init__(id, password)
class ExtendedUsersService(UsersService):
"""Example class ExtendedUsersService."""
def get_by_id(self, id):
"""Find user by his id and return user model."""
user = super(ExtendedUsersService, self).get_by_id(id)
user.first_name = 'John' + str(id)
user.last_name = 'Smith' + str(id)
user.gender = 'male'
return user
# Overriding users_service provider:
extended_users_service = providers.Factory(ExtendedUsersService,
user_cls=ExtendedUser)
users_service.override(extended_users_service)
# Getting few other users users and making some asserts:
user3 = users_service().get_by_id(3)
user4 = users_service().get_by_id(4)
assert isinstance(user3, ExtendedUser)
assert user3.id == 3
assert user3.password == 'secret3'
assert user3.first_name == 'John3'
assert user3.last_name == 'Smith3'
assert isinstance(user4, ExtendedUser)
assert user4.id == 4
assert user4.password == 'secret4'
assert user4.first_name == 'John4'
assert user4.last_name == 'Smith4'
assert user3 is not user4

View File

@ -33,6 +33,7 @@ client_factory = providers.Factory(
value4=service.provided.get_value.call(),
)
if __name__ == '__main__':
client = client_factory()
assert client.value1 == client.value2 == client.value3 == 'foo'

View File

@ -31,6 +31,7 @@ demo_list = providers.List(
dependency.provided['foo']['baz'].call(service)['arg'].get_value.call(),
)
if __name__ == '__main__':
assert demo_list() == [
10,

View File

@ -1,6 +1,6 @@
"""Dependency injector top-level package."""
__version__ = '3.35.1'
__version__ = '3.36.0'
"""Version number that follows semantic versioning.
:type: str

View File

@ -43,6 +43,7 @@ class Provider(_Provider):
def delegate(self) -> Provider: ...
@property
def provider(self) -> Provider: ...
def _copy_overridings(self, copied: Provider, memo: Optional[Dict[str, Any]]) -> None: ...
class Object(Provider, Generic[T]):