Merge branch 'release/4.39.0' into master

This commit is contained in:
Roman Mogylatov 2022-03-27 22:22:02 -04:00
commit 8b0745d43e
12 changed files with 19915 additions and 9953 deletions

View File

@ -7,6 +7,13 @@ 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`_
4.39.0
------
- Optimize injections and wiring from x1.5 to x7 times depending on the use case.
- Fix bug `#569 <https://github.com/ets-labs/python-dependency-injector/issues/569>`_:
"numpy.typing.NDArray breaks wiring". Thanks to
`@VKFisher (Vlad Fisher) <https://github.com/VKFisher>`_ for reporting the issue and providing a fix.
4.38.0 4.38.0
------ ------
- Add new provider ``Aggregate``. It is a generalized version of ``FactoryAggregate`` that - Add new provider ``Aggregate``. It is a generalized version of ``FactoryAggregate`` that

View File

@ -58,6 +58,10 @@ setup(name="dependency-injector",
["src/dependency_injector/providers.c"], ["src/dependency_injector/providers.c"],
define_macros=list(defined_macros.items()), define_macros=list(defined_macros.items()),
extra_compile_args=["-O2"]), extra_compile_args=["-O2"]),
Extension("dependency_injector._cwiring",
["src/dependency_injector/_cwiring.c"],
define_macros=list(defined_macros.items()),
extra_compile_args=["-O2"]),
], ],
install_requires=requirements, install_requires=requirements,
extras_require={ extras_require={

View File

@ -1,6 +1,6 @@
"""Top-level package.""" """Top-level package."""
__version__ = "4.38.0" __version__ = "4.39.0"
"""Version number. """Version number.
:type: str :type: str

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
"""Wiring optimizations module."""
import asyncio
import collections.abc
import functools
import inspect
import types
from . import providers
from .wiring import _Marker
def _get_sync_patched(fn):
@functools.wraps(fn)
def _patched(*args, **kwargs):
cdef object result
cdef dict to_inject
to_inject = kwargs.copy()
for injection, provider in _patched.__injections__.items():
if injection not in kwargs or isinstance(kwargs[injection], _Marker):
to_inject[injection] = provider()
result = fn(*args, **to_inject)
if _patched.__closing__:
for injection, provider in _patched.__closing__.items():
if injection in kwargs and not isinstance(kwargs[injection], _Marker):
continue
if not isinstance(provider, providers.Resource):
continue
provider.shutdown()
return result
return _patched
def _get_async_patched(fn):
@functools.wraps(fn)
async def _patched(*args, **kwargs):
cdef object result
cdef dict to_inject
cdef list to_inject_await = []
cdef list to_close_await = []
to_inject = kwargs.copy()
for injection, provider in _patched.__injections__.items():
if injection not in kwargs or isinstance(kwargs[injection], _Marker):
provide = provider()
if _isawaitable(provide):
to_inject_await.append((injection, provide))
else:
to_inject[injection] = provide
if to_inject_await:
async_to_inject = await asyncio.gather(*(provide for _, provide in to_inject_await))
for provide, (injection, _) in zip(async_to_inject, to_inject_await):
to_inject[injection] = provide
result = await fn(*args, **to_inject)
if _patched.__closing__:
for injection, provider in _patched.__closing__.items():
if injection in kwargs \
and isinstance(kwargs[injection], _Marker):
continue
if not isinstance(provider, providers.Resource):
continue
shutdown = provider.shutdown()
if _isawaitable(shutdown):
to_close_await.append(shutdown)
await asyncio.gather(*to_close_await)
return result
# Hotfix for iscoroutinefunction() for Cython < 3.0.0; can be removed after migration to Cython 3.0.0+
_patched._is_coroutine = asyncio.coroutines._is_coroutine
return _patched
cdef bint _isawaitable(object instance):
"""Return true if object can be passed to an ``await`` expression."""
return (isinstance(instance, types.CoroutineType) or
isinstance(instance, types.GeneratorType) and
bool(instance.gi_code.co_flags & inspect.CO_ITERABLE_COROUTINE) or
isinstance(instance, collections.abc.Awaitable))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,14 @@ import functools
cimport cython cimport cython
cdef int ASYNC_MODE_UNDEFINED
cdef int ASYNC_MODE_ENABLED
cdef int ASYNC_MODE_DISABLED
cdef set __iscoroutine_typecache
cdef tuple __COROUTINE_TYPES
# Base providers # Base providers
cdef class Provider(object): cdef class Provider(object):
cdef tuple __overridden cdef tuple __overridden
@ -383,11 +391,13 @@ cdef inline object __provide_positional_args(
tuple args, tuple args,
tuple inj_args, tuple inj_args,
int inj_args_len, int inj_args_len,
int async_mode,
): ):
cdef int index cdef int index
cdef list positional_args = [] cdef list positional_args = []
cdef list future_args = [] cdef list future_args = []
cdef PositionalInjection injection cdef PositionalInjection injection
cdef object value
if inj_args_len == 0: if inj_args_len == 0:
return args return args
@ -397,7 +407,7 @@ cdef inline object __provide_positional_args(
value = __get_value(injection) value = __get_value(injection)
positional_args.append(value) positional_args.append(value)
if __is_future_or_coroutine(value): if async_mode != ASYNC_MODE_DISABLED and __is_future_or_coroutine(value):
future_args.append((index, value)) future_args.append((index, value))
positional_args.extend(args) positional_args.extend(args)
@ -414,6 +424,7 @@ cdef inline object __provide_keyword_args(
dict kwargs, dict kwargs,
tuple inj_kwargs, tuple inj_kwargs,
int inj_kwargs_len, int inj_kwargs_len,
int async_mode,
): ):
cdef int index cdef int index
cdef object name cdef object name
@ -428,7 +439,7 @@ cdef inline object __provide_keyword_args(
name = __get_name(kw_injection) name = __get_name(kw_injection)
value = __get_value(kw_injection) value = __get_value(kw_injection)
kwargs[name] = value kwargs[name] = value
if __is_future_or_coroutine(value): if async_mode != ASYNC_MODE_DISABLED and __is_future_or_coroutine(value):
future_kwargs.append((name, value)) future_kwargs.append((name, value))
else: else:
kwargs, prefixed = __separate_prefixed_kwargs(kwargs) kwargs, prefixed = __separate_prefixed_kwargs(kwargs)
@ -447,7 +458,7 @@ cdef inline object __provide_keyword_args(
value = __get_value(kw_injection) value = __get_value(kw_injection)
kwargs[name] = value kwargs[name] = value
if __is_future_or_coroutine(value): if async_mode != ASYNC_MODE_DISABLED and __is_future_or_coroutine(value):
future_kwargs.append((name, value)) future_kwargs.append((name, value))
if future_kwargs: if future_kwargs:
@ -550,20 +561,26 @@ cdef inline object __call(
dict context_kwargs, dict context_kwargs,
tuple injection_kwargs, tuple injection_kwargs,
int injection_kwargs_len, int injection_kwargs_len,
int async_mode,
): ):
args = __provide_positional_args( cdef object args = __provide_positional_args(
context_args, context_args,
injection_args, injection_args,
injection_args_len, injection_args_len,
async_mode,
) )
kwargs = __provide_keyword_args( cdef object kwargs = __provide_keyword_args(
context_kwargs, context_kwargs,
injection_kwargs, injection_kwargs,
injection_kwargs_len, injection_kwargs_len,
async_mode,
) )
is_future_args = __is_future_or_coroutine(args) if async_mode == ASYNC_MODE_DISABLED:
is_future_kwargs = __is_future_or_coroutine(kwargs) return call(*args, **kwargs)
cdef bint is_future_args = __is_future_or_coroutine(args)
cdef bint is_future_kwargs = __is_future_or_coroutine(kwargs)
if is_future_args or is_future_kwargs: if is_future_args or is_future_kwargs:
future_args = args if is_future_args else __future_result(args) future_args = args if is_future_args else __future_result(args)
@ -609,7 +626,7 @@ cdef inline object __async_result_callback(object future_result, object future):
future_result.set_result(result) future_result.set_result(result)
cdef inline object __callable_call(Callable self, tuple args, dict kwargs): cdef inline object __callable_call(Callable self, tuple args, dict kwargs, ):
return __call( return __call(
self.__provides, self.__provides,
args, args,
@ -618,13 +635,23 @@ cdef inline object __callable_call(Callable self, tuple args, dict kwargs):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
cdef inline object __factory_call(Factory self, tuple args, dict kwargs): cdef inline object __factory_call(Factory self, tuple args, dict kwargs):
cdef object instance cdef object instance
instance = __callable_call(self.__instantiator, args, kwargs) instance = __call(
self.__instantiator.__provides,
args,
self.__instantiator.__args,
self.__instantiator.__args_len,
kwargs,
self.__instantiator.__kwargs,
self.__instantiator.__kwargs_len,
self.__async_mode,
)
if self.__attributes_len > 0: if self.__attributes_len > 0:
attributes = __provide_attributes(self.__attributes, self.__attributes_len) attributes = __provide_attributes(self.__attributes, self.__attributes_len)
@ -643,9 +670,26 @@ cdef inline object __factory_call(Factory self, tuple args, dict kwargs):
cdef inline bint __is_future_or_coroutine(object instance): cdef inline bint __is_future_or_coroutine(object instance):
if asyncio is None: return __isfuture(instance) or __iscoroutine(instance)
cdef inline bint __isfuture(object obj):
return hasattr(obj.__class__, "_asyncio_future_blocking") and obj._asyncio_future_blocking is not None
cdef inline bint __iscoroutine(object obj):
if type(obj) in __iscoroutine_typecache:
return True
if isinstance(obj, __COROUTINE_TYPES):
# Just in case we don't want to cache more than 100
# positive types. That shouldn't ever happen, unless
# someone stressing the system on purpose.
if len(__iscoroutine_typecache) < 100:
__iscoroutine_typecache.add(type(obj))
return True
else:
return False return False
return asyncio.isfuture(instance) or asyncio.iscoroutine(instance)
cdef inline object __future_result(object instance): cdef inline object __future_result(object instance):

View File

@ -145,6 +145,9 @@ cdef int ASYNC_MODE_UNDEFINED = 0
cdef int ASYNC_MODE_ENABLED = 1 cdef int ASYNC_MODE_ENABLED = 1
cdef int ASYNC_MODE_DISABLED = 2 cdef int ASYNC_MODE_DISABLED = 2
cdef set __iscoroutine_typecache = set()
cdef tuple __COROUTINE_TYPES = asyncio.coroutines._COROUTINE_TYPES if asyncio else tuple()
cdef class Provider(object): cdef class Provider(object):
"""Base provider class. """Base provider class.
@ -220,13 +223,13 @@ cdef class Provider(object):
else: else:
result = self._provide(args, kwargs) result = self._provide(args, kwargs)
if self.is_async_mode_disabled(): if self.__async_mode == ASYNC_MODE_DISABLED:
return result return result
elif self.is_async_mode_enabled(): elif self.__async_mode == ASYNC_MODE_ENABLED:
if __is_future_or_coroutine(result): if __is_future_or_coroutine(result):
return result return result
return __future_result(result) return __future_result(result)
elif self.is_async_mode_undefined(): elif self.__async_mode == ASYNC_MODE_UNDEFINED:
if __is_future_or_coroutine(result): if __is_future_or_coroutine(result):
self.enable_async_mode() self.enable_async_mode()
else: else:
@ -810,10 +813,10 @@ cdef class Dependency(Provider):
else: else:
self._raise_undefined_error() self._raise_undefined_error()
if self.is_async_mode_disabled(): if self.__async_mode == ASYNC_MODE_DISABLED:
self._check_instance_type(result) self._check_instance_type(result)
return result return result
elif self.is_async_mode_enabled(): elif self.__async_mode == ASYNC_MODE_ENABLED:
if __is_future_or_coroutine(result): if __is_future_or_coroutine(result):
future_result = asyncio.Future() future_result = asyncio.Future()
result = asyncio.ensure_future(result) result = asyncio.ensure_future(result)
@ -822,7 +825,7 @@ cdef class Dependency(Provider):
else: else:
self._check_instance_type(result) self._check_instance_type(result)
return __future_result(result) return __future_result(result)
elif self.is_async_mode_undefined(): elif self.__async_mode == ASYNC_MODE_UNDEFINED:
if __is_future_or_coroutine(result): if __is_future_or_coroutine(result):
self.enable_async_mode() self.enable_async_mode()
@ -3407,7 +3410,7 @@ cdef class List(Provider):
cpdef object _provide(self, tuple args, dict kwargs): cpdef object _provide(self, tuple args, dict kwargs):
"""Return result of provided callable call.""" """Return result of provided callable call."""
return __provide_positional_args(args, self.__args, self.__args_len) return __provide_positional_args(args, self.__args, self.__args_len, self.__async_mode)
cdef class Dict(Provider): cdef class Dict(Provider):
@ -3533,7 +3536,7 @@ cdef class Dict(Provider):
cpdef object _provide(self, tuple args, dict kwargs): cpdef object _provide(self, tuple args, dict kwargs):
"""Return result of provided callable call.""" """Return result of provided callable call."""
return __provide_keyword_args(kwargs, self.__kwargs, self.__kwargs_len) return __provide_keyword_args(kwargs, self.__kwargs, self.__kwargs_len, self.__async_mode)
@ -3690,7 +3693,7 @@ cdef class Resource(Provider):
def shutdown(self): def shutdown(self):
"""Shutdown resource.""" """Shutdown resource."""
if not self.__initialized: if not self.__initialized:
if self.is_async_mode_enabled(): if self.__async_mode == ASYNC_MODE_ENABLED:
result = asyncio.Future() result = asyncio.Future()
result.set_result(None) result.set_result(None)
return result return result
@ -3709,7 +3712,7 @@ cdef class Resource(Provider):
self.__initialized = False self.__initialized = False
self.__shutdowner = None self.__shutdowner = None
if self.is_async_mode_enabled(): if self.__async_mode == ASYNC_MODE_ENABLED:
result = asyncio.Future() result = asyncio.Future()
result.set_result(None) result.set_result(None)
return result return result
@ -3736,6 +3739,7 @@ cdef class Resource(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
self.__shutdowner = initializer.shutdown self.__shutdowner = initializer.shutdown
elif self._is_async_resource_subclass(self.__provides): elif self._is_async_resource_subclass(self.__provides):
@ -3748,6 +3752,7 @@ cdef class Resource(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
self.__initialized = True self.__initialized = True
return self._create_init_future(async_init, initializer.shutdown) return self._create_init_future(async_init, initializer.shutdown)
@ -3760,6 +3765,7 @@ cdef class Resource(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
self.__resource = next(initializer) self.__resource = next(initializer)
self.__shutdowner = initializer.send self.__shutdowner = initializer.send
@ -3772,6 +3778,7 @@ cdef class Resource(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
self.__initialized = True self.__initialized = True
return self._create_init_future(initializer) return self._create_init_future(initializer)
@ -3784,6 +3791,7 @@ cdef class Resource(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
self.__initialized = True self.__initialized = True
return self._create_async_gen_init_future(initializer) return self._create_async_gen_init_future(initializer)
@ -3796,6 +3804,7 @@ cdef class Resource(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
else: else:
raise Error("Unknown type of resource initializer") raise Error("Unknown type of resource initializer")
@ -4510,6 +4519,7 @@ cdef class MethodCaller(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
def _async_provide(self, future_result, args, kwargs, future): def _async_provide(self, future_result, args, kwargs, future):
@ -4523,6 +4533,7 @@ cdef class MethodCaller(Provider):
kwargs, kwargs,
self.__kwargs, self.__kwargs,
self.__kwargs_len, self.__kwargs_len,
self.__async_mode,
) )
except Exception as exception: except Exception as exception:
future_result.set_exception(exception) future_result.set_exception(exception)

View File

@ -1,7 +1,5 @@
"""Wiring module.""" """Wiring module."""
import asyncio
import functools
import inspect import inspect
import importlib import importlib
import importlib.machinery import importlib.machinery
@ -58,7 +56,6 @@ except ImportError:
from . import providers from . import providers
if sys.version_info[:2] == (3, 5): if sys.version_info[:2] == (3, 5):
warnings.warn( warnings.warn(
"Dependency Injector will drop support of Python 3.5 after Jan 1st of 2022. " "Dependency Injector will drop support of Python 3.5 after Jan 1st of 2022. "
@ -321,7 +318,7 @@ class InspectFilter:
def _is_starlette_request_cls(self, instance: object) -> bool: def _is_starlette_request_cls(self, instance: object) -> bool:
return starlette \ return starlette \
and isinstance(instance, type) \ and isinstance(instance, type) \
and issubclass(instance, starlette.requests.Request) and _safe_is_subclass(instance, starlette.requests.Request)
def _is_builtin(self, instance: object) -> bool: def _is_builtin(self, instance: object) -> bool:
return inspect.isbuiltin(instance) return inspect.isbuiltin(instance)
@ -600,71 +597,6 @@ def _get_patched(fn, reference_injections, reference_closing):
return patched return patched
def _get_sync_patched(fn):
@functools.wraps(fn)
def _patched(*args, **kwargs):
to_inject = kwargs.copy()
for injection, provider in _patched.__injections__.items():
if injection not in kwargs \
or _is_fastapi_default_arg_injection(injection, kwargs):
to_inject[injection] = provider()
result = fn(*args, **to_inject)
for injection, provider in _patched.__closing__.items():
if injection in kwargs \
and not _is_fastapi_default_arg_injection(injection, kwargs):
continue
if not isinstance(provider, providers.Resource):
continue
provider.shutdown()
return result
return _patched
def _get_async_patched(fn):
@functools.wraps(fn)
async def _patched(*args, **kwargs):
to_inject = kwargs.copy()
to_inject_await = []
to_close_await = []
for injection, provider in _patched.__injections__.items():
if injection not in kwargs \
or _is_fastapi_default_arg_injection(injection, kwargs):
provide = provider()
if inspect.isawaitable(provide):
to_inject_await.append((injection, provide))
else:
to_inject[injection] = provide
async_to_inject = await asyncio.gather(*[provide for _, provide in to_inject_await])
for provide, (injection, _) in zip(async_to_inject, to_inject_await):
to_inject[injection] = provide
result = await fn(*args, **to_inject)
for injection, provider in _patched.__closing__.items():
if injection in kwargs \
and not _is_fastapi_default_arg_injection(injection, kwargs):
continue
if not isinstance(provider, providers.Resource):
continue
shutdown = provider.shutdown()
if inspect.isawaitable(shutdown):
to_close_await.append(shutdown)
await asyncio.gather(*to_close_await)
return result
return _patched
def _is_fastapi_default_arg_injection(injection, kwargs):
"""Check if injection is FastAPI injection of the default argument."""
return injection in kwargs and isinstance(kwargs[injection], _Marker)
def _is_fastapi_depends(param: Any) -> bool: def _is_fastapi_depends(param: Any) -> bool:
return fastapi and isinstance(param, fastapi.params.Depends) return fastapi and isinstance(param, fastapi.params.Depends)
@ -679,6 +611,13 @@ def _is_declarative_container(instance: Any) -> bool:
and getattr(instance, "declarative_parent", None) is None) and getattr(instance, "declarative_parent", None) is None)
def _safe_is_subclass(instance: Any, cls: Type) -> bool:
try:
return issubclass(instance, cls)
except TypeError:
return False
class Modifier: class Modifier:
def modify( def modify(
@ -821,6 +760,8 @@ class ClassGetItemMeta(GenericMeta):
class _Marker(Generic[T], metaclass=ClassGetItemMeta): class _Marker(Generic[T], metaclass=ClassGetItemMeta):
__IS_MARKER__ = True
def __init__( def __init__(
self, self,
provider: Union[providers.Provider, Container, str], provider: Union[providers.Provider, Container, str],
@ -951,3 +892,7 @@ def is_loader_installed() -> bool:
_patched_registry = PatchedRegistry() _patched_registry = PatchedRegistry()
_inspect_filter = InspectFilter() _inspect_filter = InspectFilter()
_loader = AutoLoader() _loader = AutoLoader()
# Optimizations
from ._cwiring import _get_sync_patched # noqa
from ._cwiring import _get_async_patched # noqa

View File

@ -5,6 +5,8 @@ import sys
if "pypy" not in sys.version.lower(): if "pypy" not in sys.version.lower():
import numpy # noqa import numpy # noqa
from numpy import * # noqa from numpy import * # noqa
if sys.version_info >= (3, 7):
from numpy.typing import * # noqa
import scipy # noqa import scipy # noqa
from scipy import * # noqa from scipy import * # noqa

View File

@ -1,4 +1,4 @@
"""Auto loader tests.""" """Autoloader tests."""
import contextlib import contextlib
import importlib import importlib