Add isfuture() and iscoroutine() optimization

This commit is contained in:
Roman Mogylatov 2022-03-27 18:15:54 -04:00
parent f0c55cda22
commit ae1d00d107
9 changed files with 15566 additions and 9166 deletions

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={

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
"""Wiring optimizations module."""

View File

@ -0,0 +1,51 @@
"""Wiring optimizations module."""
import copy
import functools
import sys
import types
from . import providers
if sys.version_info[0] == 3: # pragma: no cover
CLASS_TYPES = (type,)
else: # pragma: no cover
CLASS_TYPES = (type, types.ClassType)
copy._deepcopy_dispatch[types.MethodType] = \
lambda obj, memo: type(obj)(obj.im_func,
copy.deepcopy(obj.im_self, memo),
obj.im_class)
def _get_sync_patched(fn):
@functools.wraps(fn)
def _patched(*args, **kwargs):
cdef dict 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
cdef bint _is_fastapi_default_arg_injection(object injection, dict kwargs):
"""Check if injection is FastAPI injection of the default argument."""
return injection in kwargs and _is_marker(kwargs[injection])
cdef bint _is_marker(object instance):
return getattr(instance, "__IS_MARKER__", False) is True

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,10 @@ import functools
cimport cython cimport cython
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
@ -551,19 +555,19 @@ cdef inline object __call(
tuple injection_kwargs, tuple injection_kwargs,
int injection_kwargs_len, int injection_kwargs_len,
): ):
args = __provide_positional_args( cdef object args = __provide_positional_args(
context_args, context_args,
injection_args, injection_args,
injection_args_len, injection_args_len,
) )
kwargs = __provide_keyword_args( cdef object kwargs = __provide_keyword_args(
context_kwargs, context_kwargs,
injection_kwargs, injection_kwargs,
injection_kwargs_len, injection_kwargs_len,
) )
is_future_args = __is_future_or_coroutine(args) cdef bint is_future_args = __is_future_or_coroutine(args)
is_future_kwargs = __is_future_or_coroutine(kwargs) 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)
@ -643,9 +647,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:

View File

@ -600,27 +600,29 @@ def _get_patched(fn, reference_injections, reference_closing):
return patched return patched
def _get_sync_patched(fn): from ._cwiring import _get_sync_patched
@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) # def _get_sync_patched(fn):
# @functools.wraps(fn)
for injection, provider in _patched.__closing__.items(): # def _patched(*args, **kwargs):
if injection in kwargs \ # to_inject = kwargs.copy()
and not _is_fastapi_default_arg_injection(injection, kwargs): # for injection, provider in _patched.__injections__.items():
continue # if injection not in kwargs \
if not isinstance(provider, providers.Resource): # or _is_fastapi_default_arg_injection(injection, kwargs):
continue # to_inject[injection] = provider()
provider.shutdown() #
# result = fn(*args, **to_inject)
return result #
return _patched # 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): def _get_async_patched(fn):
@ -828,6 +830,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],