diff --git a/docs/images/providers/provider_override.png b/docs/images/providers/provider_override.png deleted file mode 100644 index 00549da8..00000000 Binary files a/docs/images/providers/provider_override.png and /dev/null differ diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 2d5c5780..2170edba 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -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. diff --git a/docs/providers/images/overriding.png b/docs/providers/images/overriding.png new file mode 100644 index 00000000..6400c709 Binary files /dev/null and b/docs/providers/images/overriding.png differ diff --git a/docs/providers/overriding.rst b/docs/providers/overriding.rst index 7c0770bb..f8a9d7ab 100644 --- a/docs/providers/overriding.rst +++ b/docs/providers/overriding.rst @@ -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:: diff --git a/examples/providers/overriding.py b/examples/providers/overriding.py new file mode 100644 index 00000000..42c01de5 --- /dev/null +++ b/examples/providers/overriding.py @@ -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) diff --git a/examples/providers/overriding_simple.py b/examples/providers/overriding_simple.py deleted file mode 100644 index 96533d2c..00000000 --- a/examples/providers/overriding_simple.py +++ /dev/null @@ -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) diff --git a/examples/providers/overriding_users_model.py b/examples/providers/overriding_users_model.py deleted file mode 100644 index 7af62069..00000000 --- a/examples/providers/overriding_users_model.py +++ /dev/null @@ -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