python-dependency-injector/src/dependency_injector/providers.pxd
Roman Mogylatov 14d8ed909b
FactoryAggregate - non string keys (#496)
* Improve FactoryAggregate typing stub

* Add implementation, typing stubs, and tests

* Update changelog

* Fix deepcopying

* Add example

* Update docs

* Fix errors formatting for pypy3
2021-08-25 10:20:45 -04:00

648 lines
15 KiB
Cython

"""Providers module."""
try:
import asyncio
except ImportError:
asyncio = None
import functools
cimport cython
# Base providers
cdef class Provider(object):
cdef tuple __overridden
cdef Provider __last_overriding
cdef tuple __overrides
cdef int __async_mode
cpdef object _provide(self, tuple args, dict kwargs)
cpdef void _copy_overridings(self, Provider copied, dict memo)
cdef class Object(Provider):
cdef object __provides
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Self(Provider):
cdef object __container
cdef tuple __alt_names
cdef class Delegate(Provider):
cdef object __provides
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Dependency(Provider):
cdef object __instance_of
cdef object __default
cdef object __parent
cdef class ExternalDependency(Dependency):
pass
cdef class DependenciesContainer(Object):
cdef dict __providers
cdef object __parent
cpdef object _override_providers(self, object container)
# Callable providers
cdef class Callable(Provider):
cdef object __provides
cdef tuple __args
cdef int __args_len
cdef tuple __kwargs
cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class DelegatedCallable(Callable):
pass
cdef class AbstractCallable(Callable):
cpdef object _provide(self, tuple args, dict kwargs)
cdef class CallableDelegate(Delegate):
pass
# Coroutine providers
cdef class Coroutine(Callable):
pass
cdef class DelegatedCoroutine(Coroutine):
pass
cdef class AbstractCoroutine(Coroutine):
cpdef object _provide(self, tuple args, dict kwargs)
cdef class CoroutineDelegate(Delegate):
pass
# Configuration providers
cdef class ConfigurationOption(Provider):
cdef tuple __name
cdef Configuration __root
cdef dict __children
cdef bint __required
cdef object __cache
cdef class TypedConfigurationOption(Callable):
pass
cdef class Configuration(Object):
cdef str __name
cdef bint __strict
cdef dict __children
cdef object __weakref__
# Factory providers
cdef class Factory(Provider):
cdef Callable __instantiator
cdef tuple __attributes
cdef int __attributes_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class DelegatedFactory(Factory):
pass
cdef class AbstractFactory(Factory):
cpdef object _provide(self, tuple args, dict kwargs)
cdef class FactoryDelegate(Delegate):
pass
cdef class FactoryAggregate(Provider):
cdef dict __factories
cdef Factory __get_factory(self, object factory_name)
# Singleton providers
cdef class BaseSingleton(Provider):
cdef Factory __instantiator
cdef object __storage
cdef class Singleton(BaseSingleton):
cpdef object _provide(self, tuple args, dict kwargs)
cdef class DelegatedSingleton(Singleton):
pass
cdef class ThreadSafeSingleton(BaseSingleton):
cdef object __storage_lock
cpdef object _provide(self, tuple args, dict kwargs)
cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
pass
cdef class ThreadLocalSingleton(BaseSingleton):
cpdef object _provide(self, tuple args, dict kwargs)
cdef class ContextLocalSingleton(BaseSingleton):
cpdef object _provide(self, tuple args, dict kwargs)
cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
pass
cdef class AbstractSingleton(BaseSingleton):
pass
cdef class SingletonDelegate(Delegate):
pass
# Miscellaneous providers
cdef class List(Provider):
cdef tuple __args
cdef int __args_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Dict(Provider):
cdef tuple __kwargs
cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Resource(Provider):
cdef object __provides
cdef bint __initialized
cdef object __shutdowner
cdef object __resource
cdef tuple __args
cdef int __args_len
cdef tuple __kwargs
cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Container(Provider):
cdef object __container_cls
cdef dict __overriding_providers
cdef object __container
cdef object __parent
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Selector(Provider):
cdef object __selector
cdef dict __providers
cpdef object _provide(self, tuple args, dict kwargs)
# Provided instance
cdef class ProvidedInstance(Provider):
cdef object __provides
cpdef object _provide(self, tuple args, dict kwargs)
cdef class AttributeGetter(Provider):
cdef object __provides
cdef object __name
cpdef object _provide(self, tuple args, dict kwargs)
cdef class ItemGetter(Provider):
cdef object __provides
cdef object __name
cpdef object _provide(self, tuple args, dict kwargs)
cdef class MethodCaller(Provider):
cdef object __provides
cdef tuple __args
cdef int __args_len
cdef tuple __kwargs
cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
# Injections
cdef class Injection(object):
cdef object __value
cdef int __is_provider
cdef int __is_delegated
cdef int __call
cdef class PositionalInjection(Injection):
pass
cdef class NamedInjection(Injection):
cdef object __name
cpdef tuple parse_positional_injections(tuple args)
cpdef tuple parse_named_injections(dict kwargs)
# Utils
cdef class OverridingContext(object):
cdef Provider __overridden
cdef Provider __overriding
cdef class BaseSingletonResetContext(object):
cdef object __singleton
cdef class SingletonResetContext(BaseSingletonResetContext):
pass
cdef class SingletonFullResetContext(BaseSingletonResetContext):
pass
cdef object CLASS_TYPES
cpdef bint is_provider(object instance)
cpdef object ensure_is_provider(object instance)
cpdef bint is_delegated(object instance)
cpdef str represent_provider(object provider, object provides)
cpdef bint is_container_instance(object instance)
cpdef bint is_container_class(object instance)
cpdef object deepcopy(object instance, dict memo=*)
# Inline helper functions
cdef inline object __get_name(NamedInjection self):
return self.__name
cdef inline object __get_value(Injection self):
if self.__call == 0:
return self.__value
return self.__value()
cdef inline object __get_value_kwargs(Injection self, dict kwargs):
if self.__call == 0:
return self.__value
return self.__value(**kwargs)
cdef inline tuple __separate_prefixed_kwargs(dict kwargs):
cdef dict plain_kwargs = {}
cdef dict prefixed_kwargs = {}
for key, value in kwargs.items():
if '__' not in key:
plain_kwargs[key] = value
continue
index = key.index('__')
prefix, name = key[:index], key[index+2:]
if prefix not in prefixed_kwargs:
prefixed_kwargs[prefix] = {}
prefixed_kwargs[prefix][name] = value
return plain_kwargs, prefixed_kwargs
@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline object __provide_positional_args(
tuple args,
tuple inj_args,
int inj_args_len,
):
cdef int index
cdef list positional_args = []
cdef list future_args = []
cdef PositionalInjection injection
if inj_args_len == 0:
return args
for index in range(inj_args_len):
injection = <PositionalInjection>inj_args[index]
value = __get_value(injection)
positional_args.append(value)
if __is_future_or_coroutine(value):
future_args.append((index, value))
positional_args.extend(args)
if future_args:
return __combine_future_injections(positional_args, future_args)
return positional_args
@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline object __provide_keyword_args(
dict kwargs,
tuple inj_kwargs,
int inj_kwargs_len,
):
cdef int index
cdef object name
cdef object value
cdef dict prefixed = {}
cdef list future_kwargs = []
cdef NamedInjection kw_injection
if len(kwargs) == 0:
for index in range(inj_kwargs_len):
kw_injection = <NamedInjection>inj_kwargs[index]
name = __get_name(kw_injection)
value = __get_value(kw_injection)
kwargs[name] = value
if __is_future_or_coroutine(value):
future_kwargs.append((name, value))
else:
kwargs, prefixed = __separate_prefixed_kwargs(kwargs)
for index in range(inj_kwargs_len):
kw_injection = <NamedInjection>inj_kwargs[index]
name = __get_name(kw_injection)
if name in kwargs:
continue
if name in prefixed:
value = __get_value_kwargs(kw_injection, prefixed[name])
else:
value = __get_value(kw_injection)
kwargs[name] = value
if __is_future_or_coroutine(value):
future_kwargs.append((name, value))
if future_kwargs:
return __combine_future_injections(kwargs, future_kwargs)
return kwargs
cdef inline object __combine_future_injections(object injections, list future_injections):
future_result = asyncio.Future()
injections_ready = asyncio.gather(*[value for _, value in future_injections])
injections_ready.add_done_callback(
functools.partial(
__async_prepare_args_kwargs_callback,
future_result,
injections,
future_injections,
),
)
asyncio.ensure_future(injections_ready)
return future_result
cdef inline void __async_prepare_args_kwargs_callback(
object future_result,
object args,
object future_args_kwargs,
object future,
):
try:
result = future.result()
for value, (key, _) in zip(result, future_args_kwargs):
args[key] = value
except Exception as exception:
future_result.set_exception(exception)
else:
future_result.set_result(args)
@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline object __provide_attributes(tuple attributes, int attributes_len):
cdef NamedInjection attr_injection
cdef dict attribute_injections = {}
cdef list future_attributes = []
for index in range(attributes_len):
attr_injection = <NamedInjection>attributes[index]
name = __get_name(attr_injection)
value = __get_value(attr_injection)
attribute_injections[name] = value
if __is_future_or_coroutine(value):
future_attributes.append((name, value))
if future_attributes:
return __combine_future_injections(attribute_injections, future_attributes)
return attribute_injections
cdef inline object __async_inject_attributes(future_instance, future_attributes):
future_result = asyncio.Future()
attributes_ready = asyncio.gather(future_instance, future_attributes)
attributes_ready.add_done_callback(
functools.partial(
__async_inject_attributes_callback,
future_result,
),
)
asyncio.ensure_future(attributes_ready)
return future_result
cdef inline void __async_inject_attributes_callback(object future_result, object future):
try:
instance, attributes = future.result()
for name, value in attributes.items():
setattr(instance, name, value)
except Exception as exception:
future_result.set_exception(exception)
else:
future_result.set_result(instance)
cdef inline void __inject_attributes(object instance, dict attributes):
for name, value in attributes.items():
setattr(instance, name, value)
cdef inline object __call(
object call,
tuple context_args,
tuple injection_args,
int injection_args_len,
dict context_kwargs,
tuple injection_kwargs,
int injection_kwargs_len,
):
args = __provide_positional_args(
context_args,
injection_args,
injection_args_len,
)
kwargs = __provide_keyword_args(
context_kwargs,
injection_kwargs,
injection_kwargs_len,
)
is_future_args = __is_future_or_coroutine(args)
is_future_kwargs = __is_future_or_coroutine(kwargs)
if is_future_args or is_future_kwargs:
future_args = args if is_future_args else __future_result(args)
future_kwargs = kwargs if is_future_kwargs else __future_result(kwargs)
future_result = asyncio.Future()
args_kwargs_ready = asyncio.gather(future_args, future_kwargs)
args_kwargs_ready.add_done_callback(
functools.partial(
__async_call_callback,
future_result,
call,
),
)
asyncio.ensure_future(args_kwargs_ready)
return future_result
return call(*args, **kwargs)
cdef inline void __async_call_callback(object future_result, object call, object future):
try:
args, kwargs = future.result()
result = call(*args, **kwargs)
except Exception as exception:
future_result.set_exception(exception)
else:
if __is_future_or_coroutine(result):
result = asyncio.ensure_future(result)
result.add_done_callback(functools.partial(__async_result_callback, future_result))
return
future_result.set_result(result)
cdef inline object __async_result_callback(object future_result, object future):
try:
result = future.result()
except Exception as exception:
future_result.set_exception(exception)
else:
future_result.set_result(result)
cdef inline object __callable_call(Callable self, tuple args, dict kwargs):
return __call(
self.__provides,
args,
self.__args,
self.__args_len,
kwargs,
self.__kwargs,
self.__kwargs_len,
)
cdef inline object __factory_call(Factory self, tuple args, dict kwargs):
cdef object instance
instance = __callable_call(self.__instantiator, args, kwargs)
if self.__attributes_len > 0:
attributes = __provide_attributes(self.__attributes, self.__attributes_len)
is_future_instance = __is_future_or_coroutine(instance)
is_future_attributes = __is_future_or_coroutine(attributes)
if is_future_instance or is_future_attributes:
future_instance = instance if is_future_instance else __future_result(instance)
future_attributes = attributes if is_future_attributes else __future_result(attributes)
return __async_inject_attributes(future_instance, future_attributes)
__inject_attributes(instance, attributes)
return instance
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 object __future_result(object instance):
future_result = asyncio.Future()
future_result.set_result(instance)
return future_result