Merge branch 'release/4.23.3' into master

This commit is contained in:
Roman Mogylatov 2021-02-17 10:02:41 -05:00
commit 70bebf9075
6 changed files with 7098 additions and 7267 deletions

View File

@ -7,6 +7,14 @@ 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.23.3
------
- Fix mistakenly processed awaitable objects in async mode. This bug has corrupted
``fastapi-redis`` example causing pool exhaustion.
Thanks to Ilya Miroshnichenko and Valery Komarov for finding and reporting
the issue.
- Refactor async mode.
4.23.2 4.23.2
------ ------
- Improve async mode exceptions handling. - Improve async mode exceptions handling.

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@ except ImportError:
asyncio = None asyncio = None
import functools import functools
import inspect
cimport cython cimport cython
@ -362,7 +361,7 @@ cdef inline object __provide_positional_args(
): ):
cdef int index cdef int index
cdef list positional_args = [] cdef list positional_args = []
cdef list awaitables = [] cdef list future_args = []
cdef PositionalInjection injection cdef PositionalInjection injection
if inj_args_len == 0: if inj_args_len == 0:
@ -373,13 +372,13 @@ cdef inline object __provide_positional_args(
value = __get_value(injection) value = __get_value(injection)
positional_args.append(value) positional_args.append(value)
if __isawaitable(value): if __is_future_or_coroutine(value):
awaitables.append((index, value)) future_args.append((index, value))
positional_args.extend(args) positional_args.extend(args)
if awaitables: if future_args:
return __awaitable_args_kwargs_future(positional_args, awaitables) return __combine_future_injections(positional_args, future_args)
return positional_args return positional_args
@ -395,7 +394,7 @@ cdef inline object __provide_keyword_args(
cdef object name cdef object name
cdef object value cdef object value
cdef dict prefixed = {} cdef dict prefixed = {}
cdef list awaitables = [] cdef list future_kwargs = []
cdef NamedInjection kw_injection cdef NamedInjection kw_injection
if len(kwargs) == 0: if len(kwargs) == 0:
@ -404,8 +403,8 @@ 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 __isawaitable(value): if __is_future_or_coroutine(value):
awaitables.append((name, value)) future_kwargs.append((name, value))
else: else:
kwargs, prefixed = __separate_prefixed_kwargs(kwargs) kwargs, prefixed = __separate_prefixed_kwargs(kwargs)
@ -423,28 +422,28 @@ cdef inline object __provide_keyword_args(
value = __get_value(kw_injection) value = __get_value(kw_injection)
kwargs[name] = value kwargs[name] = value
if __isawaitable(value): if __is_future_or_coroutine(value):
awaitables.append((name, value)) future_kwargs.append((name, value))
if awaitables: if future_kwargs:
return __awaitable_args_kwargs_future(kwargs, awaitables) return __combine_future_injections(kwargs, future_kwargs)
return kwargs return kwargs
cdef inline object __awaitable_args_kwargs_future(object args, list awaitables): cdef inline object __combine_future_injections(object injections, list future_injections):
future_result = asyncio.Future() future_result = asyncio.Future()
args_ready = asyncio.gather(*[value for _, value in awaitables]) injections_ready = asyncio.gather(*[value for _, value in future_injections])
args_ready.add_done_callback( injections_ready.add_done_callback(
functools.partial( functools.partial(
__async_prepare_args_kwargs_callback, __async_prepare_args_kwargs_callback,
future_result, future_result,
args, injections,
awaitables, future_injections,
), ),
) )
asyncio.ensure_future(args_ready) asyncio.ensure_future(injections_ready)
return future_result return future_result
@ -452,13 +451,12 @@ cdef inline object __awaitable_args_kwargs_future(object args, list awaitables):
cdef inline void __async_prepare_args_kwargs_callback( cdef inline void __async_prepare_args_kwargs_callback(
object future_result, object future_result,
object args, object args,
object awaitables, object future_args_kwargs,
object future, object future,
): ):
try: try:
awaited = future.result() result = future.result()
for value, (key, _) in zip(result, future_args_kwargs):
for value, (key, _) in zip(awaited, awaitables):
args[key] = value args[key] = value
except Exception as exception: except Exception as exception:
future_result.set_exception(exception) future_result.set_exception(exception)
@ -471,18 +469,18 @@ cdef inline void __async_prepare_args_kwargs_callback(
cdef inline object __provide_attributes(tuple attributes, int attributes_len): cdef inline object __provide_attributes(tuple attributes, int attributes_len):
cdef NamedInjection attr_injection cdef NamedInjection attr_injection
cdef dict attribute_injections = {} cdef dict attribute_injections = {}
cdef list awaitables = [] cdef list future_attributes = []
for index in range(attributes_len): for index in range(attributes_len):
attr_injection = <NamedInjection>attributes[index] attr_injection = <NamedInjection>attributes[index]
name = __get_name(attr_injection) name = __get_name(attr_injection)
value = __get_value(attr_injection) value = __get_value(attr_injection)
attribute_injections[name] = value attribute_injections[name] = value
if __isawaitable(value): if __is_future_or_coroutine(value):
awaitables.append((name, value)) future_attributes.append((name, value))
if awaitables: if future_attributes:
return __awaitable_args_kwargs_future(attribute_injections, awaitables) return __combine_future_injections(attribute_injections, future_attributes)
return attribute_injections return attribute_injections
@ -539,23 +537,16 @@ cdef inline object __call(
injection_kwargs_len, injection_kwargs_len,
) )
args_awaitable = __isawaitable(args) is_future_args = __is_future_or_coroutine(args)
kwargs_awaitable = __isawaitable(kwargs) is_future_kwargs = __is_future_or_coroutine(kwargs)
if args_awaitable or kwargs_awaitable: if is_future_args or is_future_kwargs:
if not args_awaitable: future_args = args if is_future_args else __future_result(args)
future = asyncio.Future() future_kwargs = kwargs if is_future_kwargs else __future_result(kwargs)
future.set_result(args)
args = future
if not kwargs_awaitable:
future = asyncio.Future()
future.set_result(kwargs)
kwargs = future
future_result = asyncio.Future() future_result = asyncio.Future()
args_kwargs_ready = asyncio.gather(args, kwargs) args_kwargs_ready = asyncio.gather(future_args, future_kwargs)
args_kwargs_ready.add_done_callback( args_kwargs_ready.add_done_callback(
functools.partial( functools.partial(
__async_call_callback, __async_call_callback,
@ -577,7 +568,7 @@ cdef inline void __async_call_callback(object future_result, object call, object
except Exception as exception: except Exception as exception:
future_result.set_exception(exception) future_result.set_exception(exception)
else: else:
if __isawaitable(result): if __is_future_or_coroutine(result):
result = asyncio.ensure_future(result) result = asyncio.ensure_future(result)
result.add_done_callback(functools.partial(__async_result_callback, future_result)) result.add_done_callback(functools.partial(__async_result_callback, future_result))
return return
@ -613,38 +604,26 @@ cdef inline object __factory_call(Factory self, tuple args, dict kwargs):
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)
instance_awaitable = __isawaitable(instance) is_future_instance = __is_future_or_coroutine(instance)
attributes_awaitable = __isawaitable(attributes) is_future_attributes = __is_future_or_coroutine(attributes)
if instance_awaitable or attributes_awaitable: if is_future_instance or is_future_attributes:
if not instance_awaitable: future_instance = instance if is_future_instance else __future_result(instance)
future = asyncio.Future() future_attributes = attributes if is_future_attributes else __future_result(attributes)
future.set_result(instance) return __async_inject_attributes(future_instance, future_attributes)
instance = future
if not attributes_awaitable:
future = asyncio.Future()
future.set_result(attributes)
attributes = future
return __async_inject_attributes(instance, attributes)
__inject_attributes(instance, attributes) __inject_attributes(instance, attributes)
return instance return instance
cdef bint __has_isawaitable = False cdef inline bint __is_future_or_coroutine(object instance):
if asyncio is None:
return False
return asyncio.isfuture(instance) or asyncio.iscoroutine(instance)
cdef inline bint __isawaitable(object instance): cdef inline object __future_result(object instance):
global __has_isawaitable future_result = asyncio.Future()
future_result.set_result(instance)
if __has_isawaitable is True: return future_result
return inspect.isawaitable(instance)
if hasattr(inspect, 'isawaitable'):
__has_isawaitable = True
return inspect.isawaitable(instance)
return False

View File

@ -192,13 +192,11 @@ cdef class Provider(object):
if self.is_async_mode_disabled(): if self.is_async_mode_disabled():
return result return result
elif self.is_async_mode_enabled(): elif self.is_async_mode_enabled():
if not __isawaitable(result): if __is_future_or_coroutine(result):
future_result = asyncio.Future() return result
future_result.set_result(result) return __future_result(result)
return future_result
return result
elif self.is_async_mode_undefined(): elif self.is_async_mode_undefined():
if __isawaitable(result): if __is_future_or_coroutine(result):
self.enable_async_mode() self.enable_async_mode()
else: else:
self.disable_async_mode() self.disable_async_mode()
@ -661,18 +659,16 @@ cdef class Dependency(Provider):
self._check_instance_type(result) self._check_instance_type(result)
return result return result
elif self.is_async_mode_enabled(): elif self.is_async_mode_enabled():
if __isawaitable(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)
result.add_done_callback(functools.partial(self._async_provide, future_result)) result.add_done_callback(functools.partial(self._async_provide, future_result))
return future_result return future_result
else: else:
self._check_instance_type(result) self._check_instance_type(result)
future_result = asyncio.Future() return __future_result(result)
future_result.set_result(result)
return future_result
elif self.is_async_mode_undefined(): elif self.is_async_mode_undefined():
if __isawaitable(result): if __is_future_or_coroutine(result):
self.enable_async_mode() self.enable_async_mode()
future_result = asyncio.Future() future_result = asyncio.Future()
@ -2701,7 +2697,7 @@ cdef class Singleton(BaseSingleton):
:rtype: None :rtype: None
""" """
if __isawaitable(self.__storage): if __is_future_or_coroutine(self.__storage):
asyncio.ensure_future(self.__storage).cancel() asyncio.ensure_future(self.__storage).cancel()
self.__storage = None self.__storage = None
@ -2710,7 +2706,7 @@ cdef class Singleton(BaseSingleton):
if self.__storage is None: if self.__storage is None:
instance = __factory_call(self.__instantiator, args, kwargs) instance = __factory_call(self.__instantiator, args, kwargs)
if __isawaitable(instance): if __is_future_or_coroutine(instance):
future_result = asyncio.Future() future_result = asyncio.Future()
instance = asyncio.ensure_future(instance) instance = asyncio.ensure_future(instance)
instance.add_done_callback(functools.partial(self._async_init_instance, future_result)) instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
@ -2769,7 +2765,7 @@ cdef class ThreadSafeSingleton(BaseSingleton):
:rtype: None :rtype: None
""" """
with self.__storage_lock: with self.__storage_lock:
if __isawaitable(self.__storage): if __is_future_or_coroutine(self.__storage):
asyncio.ensure_future(self.__storage).cancel() asyncio.ensure_future(self.__storage).cancel()
self.__storage = None self.__storage = None
@ -2783,7 +2779,7 @@ cdef class ThreadSafeSingleton(BaseSingleton):
if self.__storage is None: if self.__storage is None:
instance = __factory_call(self.__instantiator, args, kwargs) instance = __factory_call(self.__instantiator, args, kwargs)
if __isawaitable(instance): if __is_future_or_coroutine(instance):
future_result = asyncio.Future() future_result = asyncio.Future()
instance = asyncio.ensure_future(instance) instance = asyncio.ensure_future(instance)
instance.add_done_callback(functools.partial(self._async_init_instance, future_result)) instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
@ -2850,7 +2846,7 @@ cdef class ThreadLocalSingleton(BaseSingleton):
:rtype: None :rtype: None
""" """
if __isawaitable(self.__storage.instance): if __is_future_or_coroutine(self.__storage.instance):
asyncio.ensure_future(self.__storage.instance).cancel() asyncio.ensure_future(self.__storage.instance).cancel()
del self.__storage.instance del self.__storage.instance
@ -2863,7 +2859,7 @@ cdef class ThreadLocalSingleton(BaseSingleton):
except AttributeError: except AttributeError:
instance = __factory_call(self.__instantiator, args, kwargs) instance = __factory_call(self.__instantiator, args, kwargs)
if __isawaitable(instance): if __is_future_or_coroutine(instance):
future_result = asyncio.Future() future_result = asyncio.Future()
instance = asyncio.ensure_future(instance) instance = asyncio.ensure_future(instance)
instance.add_done_callback(functools.partial(self._async_init_instance, future_result)) instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
@ -3884,7 +3880,7 @@ cdef class AttributeGetter(Provider):
cpdef object _provide(self, tuple args, dict kwargs): cpdef object _provide(self, tuple args, dict kwargs):
provided = self.__provider(*args, **kwargs) provided = self.__provider(*args, **kwargs)
if __isawaitable(provided): if __is_future_or_coroutine(provided):
future_result = asyncio.Future() future_result = asyncio.Future()
provided = asyncio.ensure_future(provided) provided = asyncio.ensure_future(provided)
provided.add_done_callback(functools.partial(self._async_provide, future_result)) provided.add_done_callback(functools.partial(self._async_provide, future_result))
@ -3892,9 +3888,13 @@ cdef class AttributeGetter(Provider):
return getattr(provided, self.__attribute) return getattr(provided, self.__attribute)
def _async_provide(self, future_result, future): def _async_provide(self, future_result, future):
provided = future.result() try:
result = getattr(provided, self.__attribute) provided = future.result()
future_result.set_result(result) result = getattr(provided, self.__attribute)
except Exception:
pass
else:
future_result.set_result(result)
cdef class ItemGetter(Provider): cdef class ItemGetter(Provider):
@ -3950,7 +3950,7 @@ cdef class ItemGetter(Provider):
cpdef object _provide(self, tuple args, dict kwargs): cpdef object _provide(self, tuple args, dict kwargs):
provided = self.__provider(*args, **kwargs) provided = self.__provider(*args, **kwargs)
if __isawaitable(provided): if __is_future_or_coroutine(provided):
future_result = asyncio.Future() future_result = asyncio.Future()
provided = asyncio.ensure_future(provided) provided = asyncio.ensure_future(provided)
provided.add_done_callback(functools.partial(self._async_provide, future_result)) provided.add_done_callback(functools.partial(self._async_provide, future_result))
@ -4050,7 +4050,7 @@ cdef class MethodCaller(Provider):
cpdef object _provide(self, tuple args, dict kwargs): cpdef object _provide(self, tuple args, dict kwargs):
call = self.__provider() call = self.__provider()
if __isawaitable(call): if __is_future_or_coroutine(call):
future_result = asyncio.Future() future_result = asyncio.Future()
call = asyncio.ensure_future(call) call = asyncio.ensure_future(call)
call.add_done_callback(functools.partial(self._async_provide, future_result, args, kwargs)) call.add_done_callback(functools.partial(self._async_provide, future_result, args, kwargs))

View File

@ -987,3 +987,65 @@ class AsyncProvidersWithAsyncDependenciesTests(AsyncTestCase):
service = self._run(container.service()) service = self._run(container.service())
self.assertEquals(service, {'service': 'ok', 'db': {'db': 'ok'}}) self.assertEquals(service, {'service': 'ok', 'db': {'db': 'ok'}})
class AsyncProviderWithAwaitableObjectTests(AsyncTestCase):
def test(self):
class SomeResource:
def __await__(self):
raise RuntimeError('Should never happen')
async def init_resource():
pool = SomeResource()
yield pool
class Service:
def __init__(self, resource) -> None:
self.resource = resource
class Container(containers.DeclarativeContainer):
resource = providers.Resource(init_resource)
service = providers.Singleton(Service, resource=resource)
container = Container()
self._run(container.init_resources())
self.assertIsInstance(container.service(), asyncio.Future)
self.assertIsInstance(container.resource(), asyncio.Future)
resource = self._run(container.resource())
service = self._run(container.service())
self.assertIsInstance(resource, SomeResource)
self.assertIsInstance(service.resource, SomeResource)
self.assertIs(service.resource, resource)
def test_without_init_resources(self):
class SomeResource:
def __await__(self):
raise RuntimeError('Should never happen')
async def init_resource():
pool = SomeResource()
yield pool
class Service:
def __init__(self, resource) -> None:
self.resource = resource
class Container(containers.DeclarativeContainer):
resource = providers.Resource(init_resource)
service = providers.Singleton(Service, resource=resource)
container = Container()
self.assertIsInstance(container.service(), asyncio.Future)
self.assertIsInstance(container.resource(), asyncio.Future)
resource = self._run(container.resource())
service = self._run(container.service())
self.assertIsInstance(resource, SomeResource)
self.assertIsInstance(service.resource, SomeResource)
self.assertIs(service.resource, resource)