Merge branch 'release/3.38.0' into master

This commit is contained in:
Roman Mogylatov 2020-09-03 23:22:32 -04:00
commit 4ec3cce1d0
11 changed files with 209 additions and 152 deletions

View File

@ -57,14 +57,25 @@ It helps implementing the dependency injection principle.
What is dependency injection?
-----------------------------
Dependency injection is a principle that helps to decrease coupling and increase cohesion. Your
code becomes more flexible, clear and it is easier to test it.
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
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.
How to implement dependency injection?
--------------------------------------
Objects do not create each other anymore. They provide a way to inject the needed dependencies
instead.
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
Before:
@ -76,14 +87,14 @@ Before:
class ApiClient:
def __init__(self):
self.api_key = os.getenv('API_KEY')
self.timeout = os.getenv('TIMEOUT')
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()
self.api_client = ApiClient() # <-- the dependency
if __name__ == '__main__':
@ -100,28 +111,36 @@ After:
class ApiClient:
def __init__(self, api_key: str, timeout: int):
self.api_key = api_key
self.timeout = timeout
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
self.api_client = api_client # <-- the dependency is injected
if __name__ == '__main__':
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
Flexibility comes with a price: now you need to assemble your objects like this
``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.
What does Dependency Injector do?
---------------------------------
``Dependency Injector`` helps you assemble the objects.
``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
@ -170,8 +189,11 @@ framework:
Retrieving of the ``Service`` instance now is done like this ``container.service()``.
Also ``Dependency Injector`` provides a bonus in overriding any of the providers with the
``.override()`` method:
The responsibility of assembling the object 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
@ -180,7 +202,6 @@ Also ``Dependency Injector`` provides a bonus in overriding any of the providers
with container.api_client.override(mock.Mock()):
service = container.service()
assert isinstance(service.api_client, mock.Mock)
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.
@ -217,7 +238,7 @@ Concept
- Explicit is better than implicit (PEP20).
- Do no magic to your code.
How does it different from the other frameworks?
How is it different from the other frameworks?
- **No autowiring.** The framework does NOT do any autowiring / autoresolving of the dependencies. You need to specify everything explicitly. Because *"Explicit is better than implicit" (PEP20)*.
- **Does not pollute your code.** Your application does NOT know and does NOT depend on the framework. No ``@inject`` decorators, annotations, patching or any other magic tricks.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -1,125 +1,190 @@
What is dependency injection and inversion of control?
------------------------------------------------------
What is dependency injection?
-----------------------------
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article provides definition of dependency injection,
inversion of control and dependency inversion. It contains
example code in Python that is refactored to be following
inversion of control principle.
: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.
Definition
~~~~~~~~~~
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
Wikipedia provides quite good definitions of dependency injection pattern
and related principles:
.. image:: images/coupling-cohesion.png
.. glossary::
What is coupling and cohesion?
`Dependency injection`_
In software engineering, dependency injection is a software design
pattern that implements inversion of control for resolving
dependencies. A dependency is an object that can be used (a service).
An injection is the passing of a dependency to a dependent object (a
client) that would use it. The service is made part of the client's
state. Passing the service to the client, rather than allowing a
client to build or find the service, is the fundamental requirement of
the pattern.
Coupling and cohesion are about how tough the components are tied.
Dependency injection allows a program design to follow the dependency
inversion principle. The client delegates to external code (the
injector) the responsibility of providing its dependencies. The client
is not allowed to call the injector code. It is the injecting code
that constructs the services and calls the client to inject them. This
means the client code does not need to know about the injecting code.
The client does not need to know how to construct the services. The
client does not need to know which actual services it is using. The
client only needs to know about the intrinsic interfaces of the
services because these define how the client may use the services.
This separates the responsibilities of use and construction.
- **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.
`Inversion of control`_
In software engineering, inversion of control (IoC) describes a design
in which custom-written portions of a computer program receive the
flow of control from a generic, reusable library. A software
architecture with this design inverts control as compared to
traditional procedural programming: in traditional programming, the
custom code that expresses the purpose of the program calls into
reusable libraries to take care of generic tasks, but with inversion
of control, it is the reusable code that calls into the custom, or
task-specific, code.
When the cohesion is high the coupling is low.
Inversion of control is used to increase modularity of the program and
make it extensible, and has applications in object-oriented
programming and other programming paradigms. The term was popularized
by Robert C. Martin and Martin Fowler.
High cohesion brings the flexibility. Your code becomes easier to change and test.
The term is related to, but different from, the dependency inversion
principle, which concerns itself with decoupling dependencies between
high-level and low-level layers through shared abstractions.
The example
~~~~~~~~~~~
`Dependency inversion`_
In object-oriented programming, the dependency inversion principle
refers to a specific form of decoupling software modules. When
following this principle, the conventional dependency relationships
established from high-level, policy-setting modules to low-level,
dependency modules are reversed, thus rendering high-level modules
independent of the low-level module implementation details. The
principle states:
How does dependency injection helps to achieve high cohesion?
+ High-level modules should not depend on low-level modules.
Both should depend on abstractions.
+ Abstractions should not depend on details.
Details should depend on abstractions.
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
The principle inverts the way some people may think about
object-oriented design, dictating that both high- and low-level
objects must depend on the same abstraction.
Before:
Example
~~~~~~~
.. code-block:: python
Let's go through the code of ``example.py``:
import os
.. literalinclude:: ../../examples/di_demo/example.py
:language: python
At some point, things defined above mean, that the code from ``example.py``,
could look different, like in ``example_di.py``:
class ApiClient:
.. literalinclude:: ../../examples/di_demo/example_di.py
:language: python
def __init__(self):
self.api_key = os.getenv('API_KEY') # <-- the dependency
self.timeout = os.getenv('TIMEOUT') # <-- the dependency
Best explanation, ever
~~~~~~~~~~~~~~~~~~~~~~
Some times ago `user198313`_ posted awesome `question`_ about dependency
injection on `StackOverflow`_:
class Service:
.. note::
def __init__(self):
self.api_client = ApiClient() # <-- the dependency
How to explain dependency injection to a 5-year-old?
And `John Munsch`_ provided absolutely Great answer:
if __name__ == '__main__':
service = Service()
.. note::
When you go and get things out of the refrigerator for yourself, you can
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.
something we don't even have or which has expired.*
What you should be doing is stating a need, "I need something to drink
*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.
down to eat.*
What's next?
~~~~~~~~~~~~
Choose one of the following as a next step:
+ Pass one of the tutorials:
+ :ref:`cli-tutorial`
+ :ref:`flask-tutorial`
+ :ref:`aiohttp-tutorial`
+ :ref:`asyncio-daemon-tutorial`
+ Know more about the :ref:`providers`
+ Go to the :ref:`contents`
.. disqus::
.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection
.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
.. _Dependency inversion: https://en.wikipedia.org/wiki/Dependency_inversion_principle
.. _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

View File

@ -7,6 +7,12 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
3.38.0
------
- Update "What is What is dependency injection?" documentation page.
- Update README.
- Fix a bunch of typos.
3.37.0
------
- Update index documentation page.

View File

@ -1,17 +0,0 @@
"""The Code."""
class Service:
"""The Service."""
class Client:
"""The Client that uses the Service."""
def __init__(self):
"""Initialize the Client."""
self.service = Service() # The Service is created by the Client
if __name__ == '__main__':
client = Client() # Application creates the Client

View File

@ -1,18 +1,18 @@
"""The Code that demonstrates dependency injection pattern."""
import os
class ApiClient:
def __init__(self, api_key: str, timeout: int):
self.api_key = api_key
self.timeout = timeout
class Service:
"""The Service."""
class Client:
"""The Client that uses the Service."""
def __init__(self, service): # The Service is injected into the Client
"""Initialize the Client."""
self.service = service
def __init__(self, api_client: ApiClient):
self.api_client = api_client
if __name__ == '__main__':
service = Service() # Application creates the Service
client = Client(service) # and inject the Service into the Client
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))

View File

@ -1,18 +0,0 @@
import os
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
if __name__ == '__main__':
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))

View File

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