mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 17:47:02 +03:00
Merge branch 'release/3.38.0' into master
This commit is contained in:
commit
4ec3cce1d0
53
README.rst
53
README.rst
|
@ -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.
|
||||
|
|
BIN
docs/introduction/images/coupling-cohesion.png
Normal file
BIN
docs/introduction/images/coupling-cohesion.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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')))
|
||||
|
|
|
@ -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')))
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user