mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-11-04 09:57:37 +03:00 
			
		
		
		
	Merge branch 'release/3.42.0' into master
This commit is contained in:
		
						commit
						ac8212a95b
					
				
							
								
								
									
										49
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								README.rst
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -66,11 +66,11 @@ Coupling and cohesion are about how tough the components are tied.
 | 
			
		|||
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
 | 
			
		||||
  to disassemble.
 | 
			
		||||
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
 | 
			
		||||
  assemble back or assemble a different way. It is an alternative to high coupling.
 | 
			
		||||
  assemble back or assemble a different way. It is an opposite to high coupling.
 | 
			
		||||
 | 
			
		||||
When the cohesion is high the coupling is low.
 | 
			
		||||
 | 
			
		||||
High cohesion brings the flexibility. Your code becomes easier to change and test.
 | 
			
		||||
Low coupling brings a flexibility. Your code becomes easier to change and test.
 | 
			
		||||
 | 
			
		||||
How to implement dependency injection?
 | 
			
		||||
--------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +124,6 @@ After:
 | 
			
		|||
   if __name__ == '__main__':
 | 
			
		||||
       service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
 | 
			
		||||
timeout from a configuration file or even get them from a database.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -133,16 +132,23 @@ stub or other compatible object.
 | 
			
		|||
 | 
			
		||||
Flexibility comes with a price.
 | 
			
		||||
 | 
			
		||||
Now you need to assemble your objects like this
 | 
			
		||||
``Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))``. The assembly code might get
 | 
			
		||||
duplicated and it'll become harder to change the application structure.
 | 
			
		||||
Now you need to assemble the objects like this::
 | 
			
		||||
 | 
			
		||||
What does Dependency Injector do?
 | 
			
		||||
---------------------------------
 | 
			
		||||
    service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
 | 
			
		||||
 | 
			
		||||
The assembly code might get duplicated and it'll become harder to change the application structure.
 | 
			
		||||
 | 
			
		||||
Here comes the ``Dependency Injector``.
 | 
			
		||||
 | 
			
		||||
What does the Dependency Injector do?
 | 
			
		||||
-------------------------------------
 | 
			
		||||
 | 
			
		||||
With the dependency injection pattern objects lose the responsibility of assembling the
 | 
			
		||||
dependencies. The ``Dependency Injector`` absorbs that responsibility.
 | 
			
		||||
 | 
			
		||||
``Dependency Injector`` helps to assemble the objects.
 | 
			
		||||
 | 
			
		||||
It provides you the container and the providers that help you describe objects assembly. When you
 | 
			
		||||
It provides a container and providers that help you with the objects assembly. When you
 | 
			
		||||
need an object you get it from the container. The rest of the assembly work is done by the
 | 
			
		||||
framework:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -151,19 +157,6 @@ framework:
 | 
			
		|||
   from dependency_injector import containers, providers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class ApiClient:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_key: str, timeout: int):
 | 
			
		||||
           self.api_key = api_key
 | 
			
		||||
           self.timeout = timeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Service:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_client: ApiClient):
 | 
			
		||||
           self.api_client = api_client
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Container(containers.DeclarativeContainer):
 | 
			
		||||
 | 
			
		||||
       config = providers.Configuration()
 | 
			
		||||
| 
						 | 
				
			
			@ -187,12 +180,14 @@ framework:
 | 
			
		|||
 | 
			
		||||
       service = container.service()
 | 
			
		||||
 | 
			
		||||
Retrieving of the ``Service`` instance now is done like this ``container.service()``.
 | 
			
		||||
Retrieving of the ``Service`` instance now is done like this::
 | 
			
		||||
 | 
			
		||||
    service = container.service()
 | 
			
		||||
 | 
			
		||||
Objects assembling is consolidated in the container. When you need to make a change you do it in
 | 
			
		||||
one place.
 | 
			
		||||
 | 
			
		||||
When doing the testing you call the ``container.api_client.override()`` to replace the real API
 | 
			
		||||
When doing a testing you call the ``container.api_client.override()`` to replace the real API
 | 
			
		||||
client with a mock:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
| 
						 | 
				
			
			@ -203,10 +198,10 @@ client with a mock:
 | 
			
		|||
   with container.api_client.override(mock.Mock()):
 | 
			
		||||
       service = container.service()
 | 
			
		||||
 | 
			
		||||
It helps in a testing. Also you can use it for configuring project for the different environments:
 | 
			
		||||
replace an API client with a stub on the dev or stage.
 | 
			
		||||
You can override any provider by another provider.
 | 
			
		||||
 | 
			
		||||
`More examples <https://github.com/ets-labs/python-dependency-injector/tree/master/examples>`_
 | 
			
		||||
It also helps you in configuring project for the different environments: replace an API client
 | 
			
		||||
with a stub on the dev or stage.
 | 
			
		||||
 | 
			
		||||
Installation
 | 
			
		||||
------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,137 +1,256 @@
 | 
			
		|||
Dependency injection and inversion of control in Python
 | 
			
		||||
-------------------------------------------------------
 | 
			
		||||
=======================================================
 | 
			
		||||
 | 
			
		||||
.. meta::
 | 
			
		||||
   :keywords: Python,DI,Dependency injection,IoC,Inversion of Control
 | 
			
		||||
   :description: This article describes benefits of dependency injection and
 | 
			
		||||
                 inversion of control for Python applications. Also it
 | 
			
		||||
                 contains some Python examples that show how dependency
 | 
			
		||||
                 injection and inversion could be implemented. In addition, it
 | 
			
		||||
                 demonstrates usage of dependency injection framework,
 | 
			
		||||
                 IoC container and such popular design pattern as Factory.
 | 
			
		||||
   :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Example
 | 
			
		||||
   :description: This page describes a usage of the dependency injection and inversion of control
 | 
			
		||||
                 in Python. It contains Python examples that show how to implement dependency
 | 
			
		||||
                 injection. It demonstrates a usage of the dependency injection framework
 | 
			
		||||
                 Dependency Injector, its container, Factory, Singleton and Configuration
 | 
			
		||||
                 providers. The example show how to use Dependency Injector providers overriding
 | 
			
		||||
                 feature for testing or configuring project in different environments and explains
 | 
			
		||||
                 why it's better then monkey-patching.
 | 
			
		||||
 | 
			
		||||
History
 | 
			
		||||
~~~~~~~
 | 
			
		||||
Originally dependency injection pattern got popular in the languages with a static typing,
 | 
			
		||||
like Java. Dependency injection is a principle that helps to achieve an inversion of control.
 | 
			
		||||
Dependency injection framework can significantly improve a flexibility of the language
 | 
			
		||||
with a static typing. Implementation of a dependency injection framework for a language
 | 
			
		||||
with a static typing is not something that one can do quickly. It will be a quite complex thing
 | 
			
		||||
to be done well. And will take time.
 | 
			
		||||
 | 
			
		||||
Originally, dependency injection pattern got popular in languages with static
 | 
			
		||||
typing, like Java. Dependency injection framework can
 | 
			
		||||
significantly improve flexibility of the language with static typing. Also,
 | 
			
		||||
implementation of dependency injection framework for language with static
 | 
			
		||||
typing is not something that one can do shortly, it could be quite complex
 | 
			
		||||
thing to be done well.
 | 
			
		||||
Python is an interpreted language with a dynamic typing. There is an opinion that dependency
 | 
			
		||||
injection doesn't work for it as well as it does for Java. A lot of the flexibility is already
 | 
			
		||||
built in. Also there is an opinion that a dependency injection framework is something that
 | 
			
		||||
Python developer rarely needs. Python developers say that dependency injection can be implemented
 | 
			
		||||
easily using language fundamentals.
 | 
			
		||||
 | 
			
		||||
While Python is very flexible interpreted language with dynamic typing, there
 | 
			
		||||
is a meaning that dependency injection doesn't work for it as well, as it does
 | 
			
		||||
for Java. Also there is a meaning that dependency injection framework is
 | 
			
		||||
something that Python developer would not ever need, cause dependency injection
 | 
			
		||||
could be implemented easily using language fundamentals.
 | 
			
		||||
This page describes the advantages of the dependency injection usage in Python. It
 | 
			
		||||
contains Python examples that show how to implement dependency injection. It demonstrates a usage
 | 
			
		||||
of the dependency injection framework ``Dependency Injector``, its container, ``Factory``,
 | 
			
		||||
``Singleton`` and ``Configuration`` providers. The example shows how to use ``Dependency Injector``
 | 
			
		||||
providers overriding feature for testing or configuring project in different environments and
 | 
			
		||||
explains why it's better then monkey-patching.
 | 
			
		||||
 | 
			
		||||
Discussion
 | 
			
		||||
~~~~~~~~~~
 | 
			
		||||
What is dependency injection?
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
It is true.
 | 
			
		||||
Let's see what the dependency injection is.
 | 
			
		||||
 | 
			
		||||
Partly.
 | 
			
		||||
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
 | 
			
		||||
 | 
			
		||||
Dependency injection, as a software design pattern, has number of
 | 
			
		||||
advantages that are common for each language (including Python):
 | 
			
		||||
.. image:: images/coupling-cohesion.png
 | 
			
		||||
 | 
			
		||||
+ Dependency Injection decreases coupling between a class and its dependency.
 | 
			
		||||
+ Because dependency injection doesn't require any change in code behavior it
 | 
			
		||||
  can be applied to legacy code as a refactoring. The result is clients that
 | 
			
		||||
  are more independent and that are easier to unit test in isolation using
 | 
			
		||||
  stubs or mock objects that simulate other objects not under test. This ease
 | 
			
		||||
  of testing is often the first benefit noticed when using dependency
 | 
			
		||||
  injection.
 | 
			
		||||
+ Dependency injection can be used to externalize a system's configuration
 | 
			
		||||
  details into configuration files allowing the system to be reconfigured
 | 
			
		||||
  without recompilation (rebuilding). Separate configurations can be written
 | 
			
		||||
  for different situations that require different implementations of
 | 
			
		||||
  components. This includes, but is not limited to, testing.
 | 
			
		||||
+ Reduction of boilerplate code in the application objects since all work to
 | 
			
		||||
  initialize or set up dependencies is handled by a provider component.
 | 
			
		||||
+ Dependency injection allows a client to remove all knowledge of a concrete
 | 
			
		||||
  implementation that it needs to use. This helps isolate the client from the
 | 
			
		||||
  impact of design changes and defects. It promotes reusability, testability
 | 
			
		||||
  and maintainability.
 | 
			
		||||
+ Dependency injection allows a client the flexibility of being configurable.
 | 
			
		||||
  Only the client's behavior is fixed. The client may act on anything that
 | 
			
		||||
  supports the intrinsic interface the client expects.
 | 
			
		||||
What is coupling and cohesion?
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
Coupling and cohesion are about how tough the components are tied.
 | 
			
		||||
 | 
			
		||||
    While improved testability is one the first benefits of using dependency
 | 
			
		||||
    injection, it could be easily overwhelmed by monkey-patching technique,
 | 
			
		||||
    that works absolutely great in Python (you can monkey-patch anything,
 | 
			
		||||
    anytime).  At the same time, monkey-patching has nothing similar with
 | 
			
		||||
    other advantages defined above. Also monkey-patching technique is
 | 
			
		||||
    something that could be considered like too dirty to be used in production.
 | 
			
		||||
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
 | 
			
		||||
  to disassemble.
 | 
			
		||||
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
 | 
			
		||||
  assemble back or assemble a different way. It is an opposite to high coupling.
 | 
			
		||||
 | 
			
		||||
The complexity of dependency injection pattern implementation in Python is
 | 
			
		||||
definitely quite lower than in other languages (even with dynamic typing).
 | 
			
		||||
When the cohesion is high the coupling is low.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
Low coupling brings a flexibility. Your code becomes easier to change and test.
 | 
			
		||||
 | 
			
		||||
    Low complexity of dependency injection pattern implementation in Python
 | 
			
		||||
    still means that some code should be written, reviewed, tested and
 | 
			
		||||
    supported.
 | 
			
		||||
How to implement the dependency injection?
 | 
			
		||||
 | 
			
		||||
Talking about inversion of control, it is a software design principle that
 | 
			
		||||
also works for each programming language, not depending on its typing type.
 | 
			
		||||
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
 | 
			
		||||
 | 
			
		||||
Inversion of control is used to increase modularity of the program and make
 | 
			
		||||
it extensible.
 | 
			
		||||
Before:
 | 
			
		||||
 | 
			
		||||
Main design purposes of using inversion of control are:
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
+ To decouple the execution of a task from implementation.
 | 
			
		||||
+ To focus a module on the task it is designed for.
 | 
			
		||||
+ To free modules from assumptions about how other systems do what they do and
 | 
			
		||||
  instead rely on contracts.
 | 
			
		||||
+ To prevent side effects when replacing a module.
 | 
			
		||||
   import os
 | 
			
		||||
 | 
			
		||||
Example
 | 
			
		||||
~~~~~~~
 | 
			
		||||
 | 
			
		||||
Let's go through next example:
 | 
			
		||||
   class ApiClient:
 | 
			
		||||
 | 
			
		||||
.. image:: /images/miniapps/engines_cars/diagram.png
 | 
			
		||||
    :width: 100%
 | 
			
		||||
    :align: center
 | 
			
		||||
       def __init__(self):
 | 
			
		||||
           self.api_key = os.getenv('API_KEY')  # <-- the dependency
 | 
			
		||||
           self.timeout = os.getenv('TIMEOUT')  # <-- the dependency
 | 
			
		||||
 | 
			
		||||
Listing of ``example.engines`` module:
 | 
			
		||||
 | 
			
		||||
.. literalinclude:: ../../examples/miniapps/engines_cars/example/engines.py
 | 
			
		||||
   :language: python
 | 
			
		||||
   class Service:
 | 
			
		||||
 | 
			
		||||
Listing of ``example.cars`` module:
 | 
			
		||||
       def __init__(self):
 | 
			
		||||
           self.api_client = ApiClient()  # <-- the dependency
 | 
			
		||||
 | 
			
		||||
.. literalinclude:: ../../examples/miniapps/engines_cars/example/cars.py
 | 
			
		||||
   :language: python
 | 
			
		||||
 | 
			
		||||
Next example demonstrates creation of several cars with different engines:
 | 
			
		||||
   if __name__ == '__main__':
 | 
			
		||||
       service = Service()
 | 
			
		||||
 | 
			
		||||
.. literalinclude:: ../../examples/miniapps/engines_cars/example_di.py
 | 
			
		||||
   :language: python
 | 
			
		||||
 | 
			
		||||
While previous example demonstrates advantages of dependency injection, there
 | 
			
		||||
is a disadvantage demonstration as well - creation of car requires additional
 | 
			
		||||
code for specification of dependencies. Nevertheless, this disadvantage could
 | 
			
		||||
be easily avoided by using a dependency injection framework for creation of
 | 
			
		||||
inversion of control container (IoC container).
 | 
			
		||||
After:
 | 
			
		||||
 | 
			
		||||
Example of creation of several inversion of control containers (IoC containers)
 | 
			
		||||
using :doc:`Dependency Injector <../index>`:
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
.. literalinclude:: ../../examples/miniapps/engines_cars/example_ioc_containers.py
 | 
			
		||||
   :language: python
 | 
			
		||||
   import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class ApiClient:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_key: str, timeout: int):
 | 
			
		||||
           self.api_key = api_key  # <-- the dependency is injected
 | 
			
		||||
           self.timeout = timeout  # <-- the dependency is injected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Service:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_client: ApiClient):
 | 
			
		||||
           self.api_client = api_client  # <-- the dependency is injected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   if __name__ == '__main__':
 | 
			
		||||
       service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
 | 
			
		||||
 | 
			
		||||
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
 | 
			
		||||
timeout from a configuration file or even get them from a database.
 | 
			
		||||
 | 
			
		||||
``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a
 | 
			
		||||
stub or other compatible object.
 | 
			
		||||
 | 
			
		||||
Flexibility comes with a price.
 | 
			
		||||
 | 
			
		||||
Now you need to assemble the objects like this::
 | 
			
		||||
 | 
			
		||||
    service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
 | 
			
		||||
 | 
			
		||||
The assembly code might get duplicated and it'll become harder to change the application structure.
 | 
			
		||||
 | 
			
		||||
Here comes the ``Dependency Injector``.
 | 
			
		||||
 | 
			
		||||
What does the Dependency Injector do?
 | 
			
		||||
-------------------------------------
 | 
			
		||||
 | 
			
		||||
With the dependency injection pattern objects lose the responsibility of assembling the
 | 
			
		||||
dependencies. The ``Dependency Injector`` absorbs that responsibility.
 | 
			
		||||
 | 
			
		||||
``Dependency Injector`` helps to assemble the objects.
 | 
			
		||||
 | 
			
		||||
It provides a container and providers that help you with the objects assembly. When you
 | 
			
		||||
need an object you get it from the container. The rest of the assembly work is done by the
 | 
			
		||||
framework:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
   from dependency_injector import containers, providers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Container(containers.DeclarativeContainer):
 | 
			
		||||
 | 
			
		||||
       config = providers.Configuration()
 | 
			
		||||
 | 
			
		||||
       api_client = providers.Singleton(
 | 
			
		||||
           ApiClient,
 | 
			
		||||
           api_key=config.api_key,
 | 
			
		||||
           timeout=config.timeout.as_int(),
 | 
			
		||||
       )
 | 
			
		||||
 | 
			
		||||
       service = providers.Factory(
 | 
			
		||||
           Service,
 | 
			
		||||
           api_client=api_client,
 | 
			
		||||
       )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   if __name__ == '__main__':
 | 
			
		||||
       container = Container()
 | 
			
		||||
       container.config.api_key.from_env('API_KEY')
 | 
			
		||||
       container.config.timeout.from_env('TIMEOUT')
 | 
			
		||||
 | 
			
		||||
       service = container.service()
 | 
			
		||||
 | 
			
		||||
Retrieving of the ``Service`` instance now is done like this::
 | 
			
		||||
 | 
			
		||||
    service = container.service()
 | 
			
		||||
 | 
			
		||||
Objects assembling is consolidated in the container. When you need to make a change you do it in
 | 
			
		||||
one place.
 | 
			
		||||
 | 
			
		||||
When doing a testing you call the ``container.api_client.override()`` to replace the real API
 | 
			
		||||
client with a mock:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
   from unittest import mock
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   with container.api_client.override(mock.Mock()):
 | 
			
		||||
       service = container.service()
 | 
			
		||||
 | 
			
		||||
You can override any provider by another provider.
 | 
			
		||||
 | 
			
		||||
It also helps you in configuring project for the different environments: replace an API client
 | 
			
		||||
with a stub on the dev or stage.
 | 
			
		||||
 | 
			
		||||
Testing, Monkey-patching and dependency injection
 | 
			
		||||
-------------------------------------------------
 | 
			
		||||
 | 
			
		||||
The testability benefit is opposed to a monkey-patching.
 | 
			
		||||
 | 
			
		||||
In Python you can monkey-patch
 | 
			
		||||
anything, anytime. The problem with a monkey-patching is that it's too fragile. The reason is that
 | 
			
		||||
when you monkey-patch you do something that wasn't intended to be done. You monkey-patch the
 | 
			
		||||
implementation details. When implementation changes the monkey-patching is broken.
 | 
			
		||||
 | 
			
		||||
With a dependency injection you patch the interface, not an implementation. This is a way more
 | 
			
		||||
stable approach.
 | 
			
		||||
 | 
			
		||||
Also monkey-patching is a way too dirty to be used outside of the testing code for
 | 
			
		||||
reconfiguring the project for the different environments.
 | 
			
		||||
 | 
			
		||||
Conclusion
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
Dependency injection brings you 3 advantages:
 | 
			
		||||
 | 
			
		||||
- **Flexibility**. The components are loosely coupled. You can easily extend or change a
 | 
			
		||||
  functionality of the system by combining the components different way. You even can do it on
 | 
			
		||||
  the fly.
 | 
			
		||||
- **Testability**. Testing is easy because you can easily inject mocks instead of real objects
 | 
			
		||||
  that use API or database, etc.
 | 
			
		||||
- **Clearness and maintainability**. Dependency injection helps you reveal the dependencies.
 | 
			
		||||
  Implicit becomes explicit. And "Explicit is better than implicit" (PEP20 - The Zen of Python).
 | 
			
		||||
  You have all the components and dependencies defined explicitly in the container. This
 | 
			
		||||
  provides an overview and control on the application structure. It is easy to understand and
 | 
			
		||||
  change it.
 | 
			
		||||
 | 
			
		||||
Is it worth to use a dependency injection in Python?
 | 
			
		||||
 | 
			
		||||
It depends on what you build. The advantages above are not too important if you use Python as a
 | 
			
		||||
scripting language. The picture is different when you use Python to create an application. The
 | 
			
		||||
larger the application the more significant is the benefit.
 | 
			
		||||
 | 
			
		||||
Is it worth to use a framework for the dependency injection?
 | 
			
		||||
 | 
			
		||||
The complexity of the dependency injection pattern implementation in Python is
 | 
			
		||||
lower than in the other languages but it's still in place. It doesn't mean you have to use a
 | 
			
		||||
framework but using a framework is beneficial because the framework is:
 | 
			
		||||
 | 
			
		||||
- Already implemented
 | 
			
		||||
- Tested on all platforms and versions of Python
 | 
			
		||||
- Documented
 | 
			
		||||
- Supported
 | 
			
		||||
- Known to the other engineers
 | 
			
		||||
 | 
			
		||||
Few advices at last:
 | 
			
		||||
 | 
			
		||||
- **Give it a try**. Dependency injection is counter-intuitive. Our nature is that
 | 
			
		||||
  when we need something the first thought that comes to our mind is to go and get it. Dependency
 | 
			
		||||
  injection is just like "Wait, I need to state a need instead of getting something right now".
 | 
			
		||||
  It's like a little investment that will pay-off later. The advice is to just give it a try for
 | 
			
		||||
  two weeks. This time will be enough for getting your own impression. If you don't like it you
 | 
			
		||||
  won't lose too much.
 | 
			
		||||
- **Common sense first**. Use a common sense when apply dependency injection. It is a good
 | 
			
		||||
  principle, but not a silver bullet. If you do it too much you will reveal too much of the
 | 
			
		||||
  implementation details. Experience comes with practice and time.
 | 
			
		||||
 | 
			
		||||
What's next?
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
Choose one of the following as a next step:
 | 
			
		||||
 | 
			
		||||
- Look at application examples:
 | 
			
		||||
- Look at the application examples:
 | 
			
		||||
    - :ref:`application-single-container`
 | 
			
		||||
    - :ref:`application-multiple-containers`
 | 
			
		||||
    - :ref:`decoupled-packages`
 | 
			
		||||
| 
						 | 
				
			
			@ -140,11 +259,12 @@ Choose one of the following as a next step:
 | 
			
		|||
    - :ref:`aiohttp-tutorial`
 | 
			
		||||
    - :ref:`asyncio-daemon-tutorial`
 | 
			
		||||
    - :ref:`cli-tutorial`
 | 
			
		||||
- Know more about the ``Dependency Injector`` :ref:`key-features`
 | 
			
		||||
- Know more about the :ref:`providers`
 | 
			
		||||
- Go to the :ref:`contents`
 | 
			
		||||
 | 
			
		||||
Useful links
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
There are some useful links related to dependency injection design pattern
 | 
			
		||||
that could be used for further reading:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,18 +3,16 @@ Introduction
 | 
			
		|||
 | 
			
		||||
.. meta::
 | 
			
		||||
   :keywords: Python,DI,Dependency injection,IoC,Inversion of Control
 | 
			
		||||
   :description: Current section of documentation is designed to give some 
 | 
			
		||||
                 overview about dependency injection pattern, inversion of 
 | 
			
		||||
                 control principle and "Dependency Injector" framework.
 | 
			
		||||
   :description: Current section of the documentation is provides an
 | 
			
		||||
                 overview of the dependency injection, inversion of
 | 
			
		||||
                 control and Dependency Injector framework.
 | 
			
		||||
 | 
			
		||||
Current section of documentation is designed to give some overview about 
 | 
			
		||||
dependency injection pattern, inversion of control principle and 
 | 
			
		||||
*Dependency Injector* framework.
 | 
			
		||||
Current section of the documentation provides an overview of the
 | 
			
		||||
dependency injection, inversion of control and the ``Dependency Injector`` framework.
 | 
			
		||||
 | 
			
		||||
..  toctree::
 | 
			
		||||
    :maxdepth: 2
 | 
			
		||||
 | 
			
		||||
    what_is_di
 | 
			
		||||
    di_in_python
 | 
			
		||||
    key_features
 | 
			
		||||
    installation
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
.. _key-features:
 | 
			
		||||
 | 
			
		||||
Key features
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,195 +0,0 @@
 | 
			
		|||
What is dependency injection?
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
.. meta::
 | 
			
		||||
   :keywords: Python,DI,Dependency injection,Low coupling,High cohesion
 | 
			
		||||
   :description: This page provides a Python example of what is dependency injection. It tells
 | 
			
		||||
                 about benefits of coupling and high cohesion.
 | 
			
		||||
 | 
			
		||||
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
 | 
			
		||||
 | 
			
		||||
.. image:: images/coupling-cohesion.png
 | 
			
		||||
 | 
			
		||||
What is coupling and cohesion?
 | 
			
		||||
 | 
			
		||||
Coupling and cohesion are about how tough the components are tied.
 | 
			
		||||
 | 
			
		||||
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
 | 
			
		||||
  to disassemble.
 | 
			
		||||
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
 | 
			
		||||
  assemble back or assemble a different way. It is an alternative to high coupling.
 | 
			
		||||
 | 
			
		||||
When the cohesion is high the coupling is low.
 | 
			
		||||
 | 
			
		||||
High cohesion brings the flexibility. Your code becomes easier to change and test.
 | 
			
		||||
 | 
			
		||||
The example
 | 
			
		||||
~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
How does dependency injection helps to achieve high cohesion?
 | 
			
		||||
 | 
			
		||||
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
 | 
			
		||||
 | 
			
		||||
Before:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
   import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class ApiClient:
 | 
			
		||||
 | 
			
		||||
       def __init__(self):
 | 
			
		||||
           self.api_key = os.getenv('API_KEY')  # <-- the dependency
 | 
			
		||||
           self.timeout = os.getenv('TIMEOUT')  # <-- the dependency
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Service:
 | 
			
		||||
 | 
			
		||||
       def __init__(self):
 | 
			
		||||
           self.api_client = ApiClient()  # <-- the dependency
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   if __name__ == '__main__':
 | 
			
		||||
       service = Service()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
After:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
   import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class ApiClient:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_key: str, timeout: int):
 | 
			
		||||
           self.api_key = api_key  # <-- the dependency is injected
 | 
			
		||||
           self.timeout = timeout  # <-- the dependency is injected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Service:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_client: ApiClient):
 | 
			
		||||
           self.api_client = api_client  # <-- the dependency is injected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   if __name__ == '__main__':
 | 
			
		||||
       service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
 | 
			
		||||
 | 
			
		||||
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
 | 
			
		||||
timeout from a configuration file or even get them from a database.
 | 
			
		||||
 | 
			
		||||
``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a
 | 
			
		||||
stub or other compatible object.
 | 
			
		||||
 | 
			
		||||
Flexibility comes with a price.
 | 
			
		||||
 | 
			
		||||
Now you need to assemble your objects like this
 | 
			
		||||
``Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))``. The assembly code might get
 | 
			
		||||
duplicated and it'll become harder to change the application structure.
 | 
			
		||||
 | 
			
		||||
Here comes the ``Dependency Injector``.
 | 
			
		||||
 | 
			
		||||
``Dependency Injector`` helps to assemble the objects.
 | 
			
		||||
 | 
			
		||||
It provides you the container and the providers that help you describe objects assembly. When you
 | 
			
		||||
need an object you get it from the container. The rest of the assembly work is done by the
 | 
			
		||||
framework:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
   from dependency_injector import containers, providers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class ApiClient:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_key: str, timeout: int):
 | 
			
		||||
           self.api_key = api_key
 | 
			
		||||
           self.timeout = timeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Service:
 | 
			
		||||
 | 
			
		||||
       def __init__(self, api_client: ApiClient):
 | 
			
		||||
           self.api_client = api_client
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class Container(containers.DeclarativeContainer):
 | 
			
		||||
 | 
			
		||||
       config = providers.Configuration()
 | 
			
		||||
 | 
			
		||||
       api_client = providers.Singleton(
 | 
			
		||||
           ApiClient,
 | 
			
		||||
           api_key=config.api_key,
 | 
			
		||||
           timeout=config.timeout.as_int(),
 | 
			
		||||
       )
 | 
			
		||||
 | 
			
		||||
       service = providers.Factory(
 | 
			
		||||
           Service,
 | 
			
		||||
           api_client=api_client,
 | 
			
		||||
       )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   if __name__ == '__main__':
 | 
			
		||||
       container = Container()
 | 
			
		||||
       container.config.api_key.from_env('API_KEY')
 | 
			
		||||
       container.config.timeout.from_env('TIMEOUT')
 | 
			
		||||
 | 
			
		||||
       service = container.service()
 | 
			
		||||
 | 
			
		||||
Retrieving of the ``Service`` instance now is done like this ``container.service()``.
 | 
			
		||||
 | 
			
		||||
Objects assembling is consolidated in the container. When you need to make a change you do it in
 | 
			
		||||
one place.
 | 
			
		||||
 | 
			
		||||
When doing the testing you call the ``container.api_client.override()`` to replace the real API
 | 
			
		||||
client with a mock:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
   from unittest import mock
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   with container.api_client.override(mock.Mock()):
 | 
			
		||||
       service = container.service()
 | 
			
		||||
 | 
			
		||||
How to explain dependency injection to a 5-year-old?
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Some time ago `user198313`_ posted this `question`_ on the `StackOverflow`_.
 | 
			
		||||
 | 
			
		||||
`John Munsch`_ provided a great answer:
 | 
			
		||||
 | 
			
		||||
    *When you go and get things out of the refrigerator for yourself, you can
 | 
			
		||||
    cause problems. You might leave the door open, you might get something 
 | 
			
		||||
    Mommy or Daddy doesn't want you to have. You might even be looking for 
 | 
			
		||||
    something we don't even have or which has expired.*
 | 
			
		||||
 | 
			
		||||
    *What you should be doing is stating a need, "I need something to drink
 | 
			
		||||
    with lunch," and then we will make sure you have something when you sit 
 | 
			
		||||
    down to eat.*
 | 
			
		||||
 | 
			
		||||
What's next?
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Choose one of the following as a next step:
 | 
			
		||||
 | 
			
		||||
- Look at application examples:
 | 
			
		||||
    - :ref:`application-single-container`
 | 
			
		||||
    - :ref:`application-multiple-containers`
 | 
			
		||||
    - :ref:`decoupled-packages`
 | 
			
		||||
- Pass the tutorials:
 | 
			
		||||
    - :ref:`flask-tutorial`
 | 
			
		||||
    - :ref:`aiohttp-tutorial`
 | 
			
		||||
    - :ref:`asyncio-daemon-tutorial`
 | 
			
		||||
    - :ref:`cli-tutorial`
 | 
			
		||||
- Know more about the :ref:`providers`
 | 
			
		||||
- Go to the :ref:`contents`
 | 
			
		||||
 | 
			
		||||
.. disqus::
 | 
			
		||||
 | 
			
		||||
.. _StackOverflow: http://stackoverflow.com/
 | 
			
		||||
.. _question: http://stackoverflow.com/questions/1638919/how-to-explain-dependency-injection-to-a-5-year-old/1639186
 | 
			
		||||
.. _user198313: http://stackoverflow.com/users/198313/user198313
 | 
			
		||||
.. _John Munsch: http://stackoverflow.com/users/31899/john-munsch
 | 
			
		||||
| 
						 | 
				
			
			@ -7,6 +7,13 @@ that were made in every particular version.
 | 
			
		|||
From version 0.7.6 *Dependency Injector* framework strictly 
 | 
			
		||||
follows `Semantic versioning`_
 | 
			
		||||
 | 
			
		||||
3.42.0
 | 
			
		||||
------
 | 
			
		||||
- Update "DI in Python" documentation page.
 | 
			
		||||
- Delete "What is DI?" documentation page.
 | 
			
		||||
- Delete "engines cars" example mini app.
 | 
			
		||||
- Update README.
 | 
			
		||||
 | 
			
		||||
3.41.0
 | 
			
		||||
------
 | 
			
		||||
- Refactor "use cases" example.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
Engines & Cars Dependency Injection Example
 | 
			
		||||
===========================================
 | 
			
		||||
 | 
			
		||||
Instructions for running:
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
    python example_di.py
 | 
			
		||||
    python example_ioc_containers.py
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
"""Example top-level package."""
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
"""Dependency injection example, cars module."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Car:
 | 
			
		||||
    """Example car."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, engine):
 | 
			
		||||
        """Initialize instance."""
 | 
			
		||||
        self._engine = engine  # Engine is injected
 | 
			
		||||
| 
						 | 
				
			
			@ -1,21 +0,0 @@
 | 
			
		|||
"""Dependency injection example, engines module."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Engine:
 | 
			
		||||
    """Example engine base class.
 | 
			
		||||
 | 
			
		||||
    Engine is a heart of every car. Engine is a very common term and
 | 
			
		||||
    could be implemented in very different ways.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GasolineEngine(Engine):
 | 
			
		||||
    """Gasoline engine."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DieselEngine(Engine):
 | 
			
		||||
    """Diesel engine."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ElectricEngine(Engine):
 | 
			
		||||
    """Electric engine."""
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
"""Dependency injection example, Cars & Engines."""
 | 
			
		||||
 | 
			
		||||
import example.cars
 | 
			
		||||
import example.engines
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    gasoline_car = example.cars.Car(example.engines.GasolineEngine())
 | 
			
		||||
    diesel_car = example.cars.Car(example.engines.DieselEngine())
 | 
			
		||||
    electric_car = example.cars.Car(example.engines.ElectricEngine())
 | 
			
		||||
| 
						 | 
				
			
			@ -1,36 +0,0 @@
 | 
			
		|||
"""Dependency injection example, Cars & Engines IoC containers."""
 | 
			
		||||
 | 
			
		||||
import example.cars
 | 
			
		||||
import example.engines
 | 
			
		||||
 | 
			
		||||
import dependency_injector.containers as containers
 | 
			
		||||
import dependency_injector.providers as providers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Engines(containers.DeclarativeContainer):
 | 
			
		||||
    """IoC container of engine providers."""
 | 
			
		||||
 | 
			
		||||
    gasoline = providers.Factory(example.engines.GasolineEngine)
 | 
			
		||||
 | 
			
		||||
    diesel = providers.Factory(example.engines.DieselEngine)
 | 
			
		||||
 | 
			
		||||
    electric = providers.Factory(example.engines.ElectricEngine)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Cars(containers.DeclarativeContainer):
 | 
			
		||||
    """IoC container of car providers."""
 | 
			
		||||
 | 
			
		||||
    gasoline = providers.Factory(example.cars.Car,
 | 
			
		||||
                                 engine=Engines.gasoline)
 | 
			
		||||
 | 
			
		||||
    diesel = providers.Factory(example.cars.Car,
 | 
			
		||||
                               engine=Engines.diesel)
 | 
			
		||||
 | 
			
		||||
    electric = providers.Factory(example.cars.Car,
 | 
			
		||||
                                 engine=Engines.electric)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    gasoline_car = Cars.gasoline()
 | 
			
		||||
    diesel_car = Cars.diesel()
 | 
			
		||||
    electric_car = Cars.electric()
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
"""Dependency injector top-level package."""
 | 
			
		||||
 | 
			
		||||
__version__ = '3.41.0'
 | 
			
		||||
__version__ = '3.42.0'
 | 
			
		||||
"""Version number that follows semantic versioning.
 | 
			
		||||
 | 
			
		||||
:type: str
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user