Make provider generic type (#293)

* Add __class_getitem__ for Provider to null the typing in runtime

* Make Provider stub generic and remove types module

* Update types module tests

* Return types module with deprecation warning

* Return types module with deprecation warning

* Update changelog

* Add docs page
This commit is contained in:
Roman Mogylatov 2020-09-13 20:32:21 -04:00 committed by GitHub
parent f56b5398ef
commit d8439a28b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 4167 additions and 3961 deletions

View File

@ -78,7 +78,7 @@ Key features of the ``Dependency Injector``:
and dictionaries. See :ref:`configuration-provider`. and dictionaries. See :ref:`configuration-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Performance**. Fast. Written in ``Cython``. - **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. - **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python .. code-block:: python

View File

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

View File

@ -7,6 +7,12 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_ follows `Semantic versioning`_
Develop
-------
- 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.
3.43.1 3.43.1
------ ------
- Fix a typo in README. - Fix a typo in README.

View File

@ -49,3 +49,4 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
overriding overriding
provided_instance provided_instance
custom 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,4 +1,4 @@
/* Generated by Cython 0.29.20 */ /* Generated by Cython 0.29.21 */
#define PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
@ -7,8 +7,8 @@
#elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000) #elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)
#error Cython requires Python 2.6+ or Python 3.3+. #error Cython requires Python 2.6+ or Python 3.3+.
#else #else
#define CYTHON_ABI "0_29_20" #define CYTHON_ABI "0_29_21"
#define CYTHON_HEX_VERSION 0x001D14F0 #define CYTHON_HEX_VERSION 0x001D15F0
#define CYTHON_FUTURE_DIVISION 0 #define CYTHON_FUTURE_DIVISION 0
#include <stddef.h> #include <stddef.h>
#ifndef offsetof #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_DATA(u) PyUnicode_DATA(u)
#define __Pyx_PyUnicode_READ(k, d, i) PyUnicode_READ(k, d, i) #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) #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))) #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 #else
#define CYTHON_PEP393_ENABLED 0 #define CYTHON_PEP393_ENABLED 0
#define PyUnicode_1BYTE_KIND 1 #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 #define __Pyx_PyInt_AsHash_t PyInt_AsSsize_t
#endif #endif
#if PY_MAJOR_VERSION >= 3 #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 #else
#define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass) #define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass)
#endif #endif
@ -12437,8 +12441,9 @@ static int __Pyx_modinit_variable_import_code(void) {
__Pyx_RefNannySetupContext("__Pyx_modinit_variable_import_code", 0); __Pyx_RefNannySetupContext("__Pyx_modinit_variable_import_code", 0);
/*--- Variable import code ---*/ /*--- Variable import code ---*/
__pyx_t_1 = PyImport_ImportModule("dependency_injector.providers"); if (!__pyx_t_1) __PYX_ERR(0, 1, __pyx_L1_error) __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) 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(); __Pyx_RefNannyFinishContext();
return 0; return 0;
__pyx_L1_error:; __pyx_L1_error:;
@ -12456,8 +12461,9 @@ static int __Pyx_modinit_function_import_code(void) {
__Pyx_RefNannySetupContext("__Pyx_modinit_function_import_code", 0); __Pyx_RefNannySetupContext("__Pyx_modinit_function_import_code", 0);
/*--- Function import code ---*/ /*--- Function import code ---*/
__pyx_t_1 = PyImport_ImportModule("dependency_injector.providers"); if (!__pyx_t_1) __PYX_ERR(0, 1, __pyx_L1_error) __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) 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(); __Pyx_RefNannyFinishContext();
return 0; return 0;
__pyx_L1_error:; __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) static PyObject *__Pyx_CyFunction_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{ {
#if PY_MAJOR_VERSION < 3
__pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func; __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
if (m->flags & __Pyx_CYFUNCTION_STATICMETHOD) { if (m->flags & __Pyx_CYFUNCTION_STATICMETHOD) {
Py_INCREF(func); Py_INCREF(func);
@ -14679,6 +14686,7 @@ static PyObject *__Pyx_CyFunction_descr_get(PyObject *func, PyObject *obj, PyObj
} }
if (obj == Py_None) if (obj == Py_None)
obj = NULL; obj = NULL;
#endif
return __Pyx_PyMethod_New(func, obj, type); return __Pyx_PyMethod_New(func, obj, type);
} }
static PyObject* static PyObject*

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,6 @@ from typing import (
Coroutine as _Coroutine, Coroutine as _Coroutine,
) )
from .types import Provider as _Provider
Injection = Any Injection = Any
T = TypeVar('T') T = TypeVar('T')
@ -27,10 +25,10 @@ class OverridingContext:
def __exit__(self, *_: Any) -> None: ... def __exit__(self, *_: Any) -> None: ...
class Provider(_Provider): class Provider(Generic[T]):
def __init__(self) -> None: ... def __init__(self) -> None: ...
def __call__(self, *args: Injection, **kwargs: Injection) -> Any: ... def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> Provider: ... def __deepcopy__(self, memo: Optional[Dict[Any, Any]]) -> Provider: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
@property @property
@ -43,7 +41,7 @@ class Provider(_Provider):
def delegate(self) -> Provider: ... def delegate(self) -> Provider: ...
@property @property
def provider(self) -> Provider: ... 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]): class Object(Provider, Generic[T]):
@ -316,7 +314,7 @@ def is_delegated(instance: Any) -> bool: ...
def represent_provider(provider: Provider, provides: Any) -> str: ... 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]: ... 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 return copied
@classmethod
def __class_getitem__(cls, item):
return cls
def __str__(self): def __str__(self):
"""Return string representation of provider. """Return string representation of provider.

View File

@ -1,4 +1,12 @@
from typing import TypeVar, Generic, Any 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 Injection = Any

View File

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

View File

@ -139,6 +139,11 @@ class ProviderTests(unittest.TestCase):
self.assertIsNot(overriding_provider, overriding_provider_copy) self.assertIsNot(overriding_provider, overriding_provider_copy)
self.assertIsInstance(overriding_provider_copy, providers.Provider) self.assertIsInstance(overriding_provider_copy, providers.Provider)
def test_generic_type(self):
provider: providers.Provider[object] = providers.Factory(object)
some_object = provider()
self.assertIsInstance(some_object, object)
def test_repr(self): def test_repr(self):
self.assertEqual(repr(self.provider), self.assertEqual(repr(self.provider),
'<dependency_injector.providers.' '<dependency_injector.providers.'

View File

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