python-dependency-injector/README.rst

400 lines
13 KiB
ReStructuredText
Raw Normal View History

.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/logo.svg
|
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: License
.. image:: https://pepy.tech/badge/dependency-injector
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Supported Python versions
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
:target: https://pypi.org/project/dependency-injector/
:alt: Supported Python implementations
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
:target: https://travis-ci.org/ets-labs/python-dependency-injector
:alt: Build Status
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
:target: http://python-dependency-injector.ets-labs.org/
:alt: Docs Status
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
:alt: Coverage Status
What is ``Dependency Injector``?
================================
``Dependency Injector`` is a dependency injection microframework for Python.
2019-08-18 00:57:10 +00:00
It was designed to be a unified and developer-friendly tool that helps
implement a dependency injection design pattern in a formal, pretty, and
Pythonic way.
2019-08-18 00:57:10 +00:00
The key features of the *Dependency Injector* framework are:
2019-08-18 00:57:10 +00:00
+ Easy, smart, and pythonic style.
+ Obvious and clear structure.
+ Extensibility and flexibility.
2016-11-13 09:52:09 +00:00
+ High performance.
+ Memory efficiency.
+ Thread safety.
2019-08-18 00:57:10 +00:00
+ Documented.
+ Semantically versioned.
2017-03-26 19:45:05 +00:00
*Dependency Injector* containers and providers are implemented as C extension
types using Cython.
2016-11-11 15:05:25 +00:00
2015-08-31 13:31:38 +00:00
2020-01-27 00:04:23 +00:00
Installation
------------
The *Dependency Injector* library is available on `PyPi`_::
2020-01-27 00:33:28 +00:00
pip install dependency-injector
2020-01-27 00:04:23 +00:00
Documentation
-------------
The *Dependency Injector* documentation is hosted on ReadTheDocs:
- `User's guide`_
- `API docs`_
2016-10-07 14:33:51 +00:00
Dependency injection
2016-10-06 20:15:41 +00:00
--------------------
`Dependency injection`_ is a software design pattern that implements
2019-08-18 00:57:10 +00:00
`Inversion of control`_ to resolve dependencies. Formally, if object **A**
depends on object **B**, object **A** must not create or import object **B**
2019-08-18 00:57:10 +00:00
directly. Instead of this object **A** must provide a way to *inject*
object **B**. The responsibilities of objects creation and dependency
injection are delegated to external code - the *dependency injector*.
2019-08-18 00:57:10 +00:00
Popular terminology of the dependency injection pattern:
2019-08-18 00:57:10 +00:00
+ Object **A**, which depends on object **B**, is often called -
the *client*.
2019-08-18 00:57:10 +00:00
+ Object **B**, which is depended on, is often called - the *service*.
+ External code that is responsible for creation of objects and injection
of dependencies is often called - the *dependency injector*.
2019-08-18 00:57:10 +00:00
There are several ways to inject a *service* into a *client*:
2019-08-18 00:57:10 +00:00
+ by passing it as an ``__init__`` argument (constructor / initializer
injection)
+ by setting it as an attribute's value (attribute injection)
+ by passing it as a method's argument (method injection)
2019-08-18 00:57:10 +00:00
The dependency injection pattern has few strict rules that should be followed:
+ The *client* delegates to the *dependency injector* the responsibility
of injecting its dependencies - the *service(s)*.
+ The *client* doesn't know how to create the *service*, it knows only
2019-08-18 00:57:10 +00:00
the interface of the *service*. The *service* doesn't know that it is used by
2016-10-11 20:52:40 +00:00
the *client*.
+ The *dependency injector* knows how to create the *client* and
2019-08-18 00:57:10 +00:00
the *service*. It also knows that the *client* depends on the *service*,
and knows how to inject the *service* into the *client*.
+ The *client* and the *service* know nothing about the *dependency injector*.
2016-10-07 14:32:24 +00:00
2019-08-18 00:57:10 +00:00
The dependency injection pattern provides the following advantages:
2016-10-07 14:32:24 +00:00
2019-08-18 00:57:10 +00:00
+ Control of application structure.
+ Decreased coupling of application components.
2016-10-07 14:32:24 +00:00
+ Increased code reusability.
+ Increased testability.
+ Increased maintainability.
2019-08-18 00:57:10 +00:00
+ Reconfiguration of a system without rebuilding.
2016-10-06 20:15:41 +00:00
Example of dependency injection
-------------------------------
2015-04-02 21:35:22 +00:00
2016-12-27 21:06:40 +00:00
Let's go through next example:
.. image:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/engines_cars/diagram.png
:width: 100%
:align: center
Listing of ``example.engines`` module:
.. code-block:: python
"""Dependency injection example, engines module."""
2020-01-27 00:04:23 +00:00
class Engine:
2016-12-27 21:06:40 +00:00
"""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."""
2020-01-27 00:04:23 +00:00
class ElectricEngine(Engine):
"""Electric engine."""
2016-12-27 21:06:40 +00:00
Listing of ``example.cars`` module:
.. code-block:: python
"""Dependency injection example, cars module."""
2020-01-27 00:04:23 +00:00
class Car:
2016-12-27 21:06:40 +00:00
"""Example car."""
def __init__(self, engine):
"""Initializer."""
self._engine = engine # Engine is injected
2019-08-18 00:57:10 +00:00
The next example demonstrates the creation of several cars with different engines:
2016-12-27 21:06:40 +00:00
.. code-block:: python
"""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())
2020-01-27 00:04:23 +00:00
electric_car = example.cars.Car(example.engines.ElectricEngine())
2016-12-27 21:06:40 +00:00
2019-08-18 00:57:10 +00:00
While the previous example demonstrates the advantages of dependency injection,
there is a disadvantage demonstrated as well - the creation of a car requires
2020-01-27 00:04:23 +00:00
additional code to specify its dependencies. However, this disadvantage
2019-08-18 00:57:10 +00:00
could be avoided by using a dependency injection framework for the creation of
an inversion of control container (IoC container).
2016-12-27 21:06:40 +00:00
2019-08-18 00:57:10 +00:00
Here's an example of the creation of several inversion of control containers
(IoC containers) using *Dependency Injector*:
2016-12-27 21:06:40 +00:00
.. code-block:: python
"""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)
2020-01-27 00:04:23 +00:00
electric = providers.Factory(example.engines.ElectricEngine)
2016-12-27 21:06:40 +00:00
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)
2020-01-27 00:04:23 +00:00
electric = providers.Factory(example.cars.Car,
engine=Engines.electric)
2016-12-27 21:06:40 +00:00
if __name__ == '__main__':
gasoline_car = Cars.gasoline()
diesel_car = Cars.diesel()
2020-01-27 00:04:23 +00:00
electric_car = Cars.electric()
2016-12-27 21:06:40 +00:00
2017-01-29 21:30:38 +00:00
Dependency Injector structure
-----------------------------
2019-08-18 00:57:10 +00:00
*Dependency Injector* is a microframework and has a simple structure.
2017-01-29 21:30:38 +00:00
2019-08-18 00:57:10 +00:00
There are two main entities: providers and containers.
2017-01-29 21:30:38 +00:00
.. image:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/internals.png
:width: 100%
:align: center
Providers
~~~~~~~~~
2019-08-18 00:57:10 +00:00
Providers describe strategies of accessing objects. They define how particular
2017-01-29 21:30:38 +00:00
objects are provided.
- **Provider** - base provider class.
2019-08-18 00:57:10 +00:00
- **Callable** - provider that calls a wrapped callable on every call. Supports
positional and keyword argument injections.
2017-01-29 21:30:38 +00:00
- **Factory** - provider that creates new instance of specified class on every
2019-08-18 00:57:10 +00:00
call. Supports positional and keyword argument injections, as well as
2017-01-29 21:30:38 +00:00
attribute injections.
2019-08-18 00:57:10 +00:00
- **Singleton** - provider that creates new instance of specified class on its
first call and returns the same instance on every next call. Supports
position and keyword argument injections, as well as attribute injections.
2017-01-29 21:30:38 +00:00
- **Object** - provider that returns provided instance "as is".
- **ExternalDependency** - provider that can be useful for development of
2019-08-18 00:57:10 +00:00
self-sufficient libraries, modules, and applications that require external
dependencies.
2017-01-29 21:30:38 +00:00
- **Configuration** - provider that helps with implementing late static binding
of configuration options - use first, define later.
Containers
~~~~~~~~~~
2019-08-18 00:57:10 +00:00
Containers are collections of providers. The main purpose of containers is to
2017-01-29 21:30:38 +00:00
group providers.
2019-08-18 00:57:10 +00:00
- **DeclarativeContainer** - is an inversion of control container that can be
defined in a declarative manner. It covers most of the cases where a list of
providers that is be included in a container is deterministic
(that means the container will not change its structure in runtime).
- **DynamicContainer** - is an inversion of control container with a dynamic
structure. It covers most of the cases where a list of providers that
would be included in container is non-deterministic and depends on the
2017-01-29 21:30:38 +00:00
application's flow or its configuration (container's structure could be
2019-08-18 00:57:10 +00:00
determined just after the application starts and might perform some initial
work, like parsing a list of container providers from a configuration).
2017-01-29 21:30:38 +00:00
Dependency Injector in action
-----------------------------
2016-12-27 21:06:40 +00:00
2019-08-18 00:57:10 +00:00
The brief example below is a simplified version of inversion of control
containers from a real-life application. The example demonstrates the usage
of *Dependency Injector* inversion of control container and providers for
specifying application components and their dependencies on each other in one
module. Besides other previously mentioned advantages, it shows a great
opportunity to control and manage application's structure in one place.
2016-05-18 20:18:29 +00:00
2016-03-13 22:04:55 +00:00
.. code-block:: python
2016-10-06 19:48:43 +00:00
"""Example of dependency injection in Python."""
2016-10-06 19:48:43 +00:00
import logging
import sqlite3
2016-10-06 19:48:43 +00:00
2017-03-15 15:26:59 +00:00
import boto3
2016-09-18 20:00:10 +00:00
from dependency_injector import containers, providers
from example import services, main
2016-04-20 11:25:40 +00:00
class IocContainer(containers.DeclarativeContainer):
"""Application IoC container."""
2017-03-15 15:26:59 +00:00
config = providers.Configuration('config')
2016-10-06 19:48:43 +00:00
logger = providers.Singleton(logging.Logger, name='example')
# Gateways
database_client = providers.Singleton(sqlite3.connect, config.database.dsn)
s3_client = providers.Singleton(
2017-03-15 15:26:59 +00:00
boto3.client, 's3',
aws_access_key_id=config.aws.access_key_id,
aws_secret_access_key=config.aws.secret_access_key,
)
# Services
users_service = providers.Factory(
services.UsersService,
db=database_client,
logger=logger,
)
auth_service = providers.Factory(
services.AuthService,
token_ttl=config.auth.token_ttl,
db=database_client,
logger=logger,
)
2016-06-01 16:52:10 +00:00
photos_service = providers.Factory(
services.PhotosService,
db=database_client,
s3=s3_client,
logger=logger,
)
2016-05-18 20:18:29 +00:00
# Misc
2016-05-18 20:18:29 +00:00
main = providers.Callable(
main.main,
users_service=users_service,
auth_service=auth_service,
photos_service=photos_service,
)
2016-05-18 20:18:29 +00:00
2019-08-18 00:57:10 +00:00
The next example demonstrates a run of the example application defined above:
2016-09-18 20:00:10 +00:00
.. code-block:: python
"""Run example of dependency injection in Python."""
2016-09-18 20:00:10 +00:00
2016-10-06 19:48:43 +00:00
import sys
import logging
from container import IocContainer
2016-04-20 11:19:54 +00:00
if __name__ == '__main__':
# Configure container:
container = IocContainer(
config={
'database': {
'dsn': ':memory:',
},
'aws': {
'access_key_id': 'KEY',
'secret_access_key': 'SECRET',
},
'auth': {
'token_ttl': 3600,
},
}
)
container.logger().addHandler(logging.StreamHandler(sys.stdout))
2016-10-06 19:48:43 +00:00
# Run application:
container.main(*sys.argv[1:])
2016-09-18 20:00:10 +00:00
2019-08-18 00:57:10 +00:00
You can find more *Dependency Injector* examples in the ``/examples`` directory
on our GitHub:
2015-04-02 21:40:03 +00:00
2016-04-25 08:07:47 +00:00
https://github.com/ets-labs/python-dependency-injector
2015-04-02 21:35:22 +00:00
2016-10-07 14:33:51 +00:00
Feedback & Support
------------------
2015-04-02 21:33:28 +00:00
2019-08-18 00:57:10 +00:00
Feel free to post questions, bugs, feature requests, proposals, etc. on
the *Dependency Injector* GitHub issues page:
2015-04-02 21:33:28 +00:00
2016-04-25 08:07:47 +00:00
https://github.com/ets-labs/python-dependency-injector/issues
2015-04-02 21:33:28 +00:00
Your feedback is quite important!
2015-04-02 21:29:00 +00:00
.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection
.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
2020-01-27 01:37:13 +00:00
.. _PyPi: https://pypi.org/project/dependency-injector/
2017-01-10 22:09:43 +00:00
.. _User's guide: http://python-dependency-injector.ets-labs.org/
.. _API docs: http://python-dependency-injector.ets-labs.org/api/