Update providers overriding documentation and rework examples

This commit is contained in:
Roman Mogylatov 2020-09-02 16:59:25 -04:00
parent dcc59ab0f4
commit f8648adaf7
7 changed files with 71 additions and 162 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -7,6 +7,10 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
Development version
-------------------
- Update providers overriding documentation and rework examples.
3.35.1
------
- Fix minor issues in the providers documentation and examples.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,47 +1,41 @@
Overriding of providers
-----------------------
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.
Any provider can be overridden 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

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