Merge branch 'release/3.44.0' into master

This commit is contained in:
Roman Mogylatov 2020-09-13 20:56:13 -04:00
commit 53b7ad0275
14 changed files with 4200 additions and 4064 deletions

View File

@ -54,103 +54,23 @@ What is ``Dependency Injector``?
It helps implementing the dependency injection principle.
What is dependency injection?
-----------------------------
Key features of the ``Dependency Injector``:
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 opposite to high coupling.
When the cohesion is high the coupling is low.
Low coupling brings a 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 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 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 loose 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:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your
objects. See `Providers <http://python-dependency-injector.ets-labs.org/providers/index.html>`_.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev / stage environment to replace API clients with stubs etc. See
`Provider overriding <http://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries.
See `Configuration provider <http://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
- **Containers**. Provides declarative and dynamic containers.
See `Containers <http://python-dependency-injector.ets-labs.org/containers/index.html>`_.
- **Performance**. Fast. Written in ``Cython``.
- **Typing**. Provides typing stubs, ``mypy``-friendly.
See `Typing and mypy <http://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python
@ -180,28 +100,18 @@ framework:
service = container.service()
Retrieving of the ``Service`` instance now is done like this::
With the ``Dependency Injector`` you keep **application structure in one place**.
This place is called **the container**. You use the container to manage all the components of the
application. All the component dependencies are defined explicitly. This provides the control on
the application structure. It is **easy to understand and change** it.
service = container.service()
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-map.svg
:target: https://github.com/ets-labs/python-dependency-injector
Objects assembling is consolidated in the container. When you need to make a change you do it in
one place.
*The container is like a map of your application. You always know what depends on what.*
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 with 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.
Visit the docs to know more about the
`Dependency injection and inversion of control in Python <http://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
Installation
------------
@ -215,6 +125,15 @@ Documentation
The documentation is available on the `Read The Docs <http://python-dependency-injector.ets-labs.org/>`_
Examples
--------
Choose one of the following:
- `Application example (single container) <http://python-dependency-injector.ets-labs.org/examples/application-single-container.html>`_
- `Application example (multiple containers) <http://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html>`_
- `Decoupled packages example (multiple containers) <http://python-dependency-injector.ets-labs.org/examples/decoupled-packages.html>`_
Tutorials
---------

View File

@ -78,7 +78,7 @@ Key features of the ``Dependency Injector``:
and dictionaries. See :ref:`configuration-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Performance**. Fast. Written in ``Cython``.
- **Typing**. Provides typing stubs, ``mypy``-friendly.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python

View File

@ -20,7 +20,7 @@ Key features of the ``Dependency Injector``:
and dictionaries. See :ref:`configuration-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Performance**. Fast. Written in ``Cython``.
- **Typing**. Provides typing stubs, ``mypy``-friendly.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
The framework stands on two principles:

View File

@ -7,6 +7,13 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
3.44.0
------
- Add native support of the generics to the providers: ``some_provider = providers.Provider[SomeClass]``.
- Deprecate module ``types``.
- Add documentation page on providers typing and ``mypy`` support.
- Update README.
3.43.1
------
- Fix a typo in README.

View File

@ -49,3 +49,4 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
overriding
provided_instance
custom
typing_mypy

View File

@ -0,0 +1,58 @@
.. _provider-typing:
Typing and mypy
===============
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Providers,Typing,Mypy,
Pattern,Example
:description: Dependency Injector providers are mypy-friendly. Providers module goes with the
typing stubs to provide the typing information to ``mypy``, IDEs and editors.
Providers are ``mypy``-friendly.
Providers module goes with the typing stubs. It provides typing information to ``mypy`` and your
IDE.
.. code-block:: python
from dependency_injector import providers
class Animal:
...
class Cat(Animal)
...
provider = providers.Factory(Cat)
if __name__ == '__main__':
animal = provider() # mypy knows that animal is of type "Cat"
You can use ``Provider`` as a generic type. This helps when a provider is an argument of a
function or method.
.. code-block:: python
:emphasize-lines: 12
from dependency_injector import providers
class Animal:
...
class Cat(Animal)
...
provider: providers.Provider[Animal] = providers.Factory(Cat)
if __name__ == '__main__':
animal = provider() # mypy knows that animal is of type "Animal"

View File

@ -1,6 +1,6 @@
"""Top-level package."""
__version__ = '3.43.1'
__version__ = '3.44.0'
"""Version number.
:type: str

View File

@ -1,4 +1,4 @@
/* Generated by Cython 0.29.20 */
/* Generated by Cython 0.29.21 */
#define PY_SSIZE_T_CLEAN
#include "Python.h"
@ -7,8 +7,8 @@
#elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)
#error Cython requires Python 2.6+ or Python 3.3+.
#else
#define CYTHON_ABI "0_29_20"
#define CYTHON_HEX_VERSION 0x001D14F0
#define CYTHON_ABI "0_29_21"
#define CYTHON_HEX_VERSION 0x001D15F0
#define CYTHON_FUTURE_DIVISION 0
#include <stddef.h>
#ifndef offsetof
@ -435,7 +435,11 @@ static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) {
#define __Pyx_PyUnicode_DATA(u) PyUnicode_DATA(u)
#define __Pyx_PyUnicode_READ(k, d, i) PyUnicode_READ(k, d, i)
#define __Pyx_PyUnicode_WRITE(k, d, i, ch) PyUnicode_WRITE(k, d, i, ch)
#if defined(PyUnicode_IS_READY) && defined(PyUnicode_GET_SIZE)
#define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u)))
#else
#define __Pyx_PyUnicode_IS_TRUE(u) (0 != PyUnicode_GET_LENGTH(u))
#endif
#else
#define CYTHON_PEP393_ENABLED 0
#define PyUnicode_1BYTE_KIND 1
@ -544,7 +548,7 @@ static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) {
#define __Pyx_PyInt_AsHash_t PyInt_AsSsize_t
#endif
#if PY_MAJOR_VERSION >= 3
#define __Pyx_PyMethod_New(func, self, klass) ((self) ? PyMethod_New(func, self) : (Py_INCREF(func), func))
#define __Pyx_PyMethod_New(func, self, klass) ((self) ? ((void)(klass), PyMethod_New(func, self)) : __Pyx_NewRef(func))
#else
#define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass)
#endif
@ -12437,8 +12441,9 @@ static int __Pyx_modinit_variable_import_code(void) {
__Pyx_RefNannySetupContext("__Pyx_modinit_variable_import_code", 0);
/*--- Variable import code ---*/
__pyx_t_1 = PyImport_ImportModule("dependency_injector.providers"); if (!__pyx_t_1) __PYX_ERR(0, 1, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
if (__Pyx_ImportVoidPtr(__pyx_t_1, "CLASS_TYPES", (void **)&__pyx_vp_19dependency_injector_9providers_CLASS_TYPES, "PyObject *") < 0) __PYX_ERR(0, 1, __pyx_L1_error)
Py_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__Pyx_RefNannyFinishContext();
return 0;
__pyx_L1_error:;
@ -12456,8 +12461,9 @@ static int __Pyx_modinit_function_import_code(void) {
__Pyx_RefNannySetupContext("__Pyx_modinit_function_import_code", 0);
/*--- Function import code ---*/
__pyx_t_1 = PyImport_ImportModule("dependency_injector.providers"); if (!__pyx_t_1) __PYX_ERR(0, 1, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
if (__Pyx_ImportFunction(__pyx_t_1, "deepcopy", (void (**)(void))&__pyx_f_19dependency_injector_9providers_deepcopy, "PyObject *(PyObject *, int __pyx_skip_dispatch, struct __pyx_opt_args_19dependency_injector_9providers_deepcopy *__pyx_optional_args)") < 0) __PYX_ERR(0, 1, __pyx_L1_error)
Py_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__Pyx_RefNannyFinishContext();
return 0;
__pyx_L1_error:;
@ -14667,6 +14673,7 @@ static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit,
}
static PyObject *__Pyx_CyFunction_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
#if PY_MAJOR_VERSION < 3
__pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
if (m->flags & __Pyx_CYFUNCTION_STATICMETHOD) {
Py_INCREF(func);
@ -14679,6 +14686,7 @@ static PyObject *__Pyx_CyFunction_descr_get(PyObject *func, PyObject *obj, PyObj
}
if (obj == Py_None)
obj = NULL;
#endif
return __Pyx_PyMethod_New(func, obj, type);
}
static PyObject*

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,6 @@ from typing import (
Coroutine as _Coroutine,
)
from .types import Provider as _Provider
Injection = Any
T = TypeVar('T')
@ -27,10 +25,10 @@ class OverridingContext:
def __exit__(self, *_: Any) -> None: ...
class Provider(_Provider):
class Provider(Generic[T]):
def __init__(self) -> None: ...
def __call__(self, *args: Injection, **kwargs: Injection) -> Any: ...
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> Provider: ...
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
def __deepcopy__(self, memo: Optional[Dict[Any, Any]]) -> Provider: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
@property
@ -43,7 +41,7 @@ class Provider(_Provider):
def delegate(self) -> Provider: ...
@property
def provider(self) -> Provider: ...
def _copy_overridings(self, copied: Provider, memo: Optional[Dict[str, Any]]) -> None: ...
def _copy_overridings(self, copied: Provider, memo: Optional[Dict[Any, Any]]) -> None: ...
class Object(Provider, Generic[T]):
@ -316,7 +314,7 @@ def is_delegated(instance: Any) -> bool: ...
def represent_provider(provider: Provider, provides: Any) -> str: ...
def deepcopy(instance: Any, memo: Optional[Dict[str, Any]]): Any: ...
def deepcopy(instance: Any, memo: Optional[Dict[Any, Any]]): Any: ...
def merge_dicts(dict1: Dict[Any, Any], dict2: Dict[Any, Any]) -> Dict[Any, Any]: ...

View File

@ -169,6 +169,10 @@ cdef class Provider(object):
return copied
@classmethod
def __class_getitem__(cls, item):
return cls
def __str__(self):
"""Return string representation of provider.

View File

@ -1,4 +1,12 @@
from typing import TypeVar, Generic, Any
import warnings
warnings.warn(
'Types module is deprecated since version 3.44.0. Use "providers" module instead: '
'providers.Provider[SomeClass]',
category=DeprecationWarning,
)
Injection = Any

View File

@ -1,6 +1,6 @@
from typing import Tuple, Any, Dict
from dependency_injector import providers, types
from dependency_injector import providers
class Animal:
@ -64,5 +64,5 @@ factory_b_9: providers.Factory = provider9.b
val9: Any = provider9('a')
# Test 10: to check the explicit typing
factory10: types.Provider[Animal] = providers.Factory(Cat)
factory10: providers.Provider[Animal] = providers.Factory(Cat)
animal10: Animal = factory10()

View File

@ -1,6 +1,6 @@
import unittest
from dependency_injector import providers, types
from dependency_injector import providers
class SomeClass:
@ -10,6 +10,6 @@ class SomeClass:
class TypesTest(unittest.TestCase):
def test_provider(self):
provider: types.Provider[SomeClass] = providers.Factory(SomeClass)
provider: providers.Provider[SomeClass] = providers.Factory(SomeClass)
some_object = provider()
self.assertIsInstance(some_object, SomeClass)