Add first dirty async injections implementation

This commit is contained in:
Roman Mogylatov 2020-12-10 23:05:48 -05:00
parent c51eb52053
commit caee7f6b41
5 changed files with 5686 additions and 2933 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
"""Providers module.""" """Providers module."""
import asyncio
import inspect
cimport cython cimport cython
@ -353,6 +356,7 @@ cdef inline tuple __provide_positional_args(
return tuple(positional_args) return tuple(positional_args)
# TODO: refactor
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.wraparound(False) @cython.wraparound(False)
cdef inline dict __provide_keyword_args( cdef inline dict __provide_keyword_args(
@ -363,14 +367,19 @@ cdef inline dict __provide_keyword_args(
cdef int index cdef int index
cdef object name cdef object name
cdef object value cdef object value
cdef dict prefixed cdef dict prefixed = {}
cdef list awaitables = []
cdef NamedInjection kw_injection cdef NamedInjection kw_injection
if len(kwargs) == 0: if len(kwargs) == 0:
for index in range(inj_kwargs_len): for index in range(inj_kwargs_len):
kw_injection = <NamedInjection>inj_kwargs[index] kw_injection = <NamedInjection>inj_kwargs[index]
name = __get_name(kw_injection) name = __get_name(kw_injection)
kwargs[name] = __get_value(kw_injection) value = __get_value(kw_injection)
if inspect.isawaitable(value):
awaitables.append((name, value))
else:
kwargs[name] = value
else: else:
kwargs, prefixed = __separate_prefixed_kwargs(kwargs) kwargs, prefixed = __separate_prefixed_kwargs(kwargs)
@ -387,9 +396,12 @@ cdef inline dict __provide_keyword_args(
else: else:
value = __get_value(kw_injection) value = __get_value(kw_injection)
if inspect.isawaitable(value):
awaitables.append((name, value))
else:
kwargs[name] = value kwargs[name] = value
return kwargs return {'kwargs': kwargs, 'awaitables': awaitables}
@cython.boundscheck(False) @cython.boundscheck(False)
@ -424,15 +436,49 @@ cdef inline object __call(
injection_args, injection_args,
injection_args_len, injection_args_len,
) )
keyword_args = __provide_keyword_args( kw_return = __provide_keyword_args(
kwargs, kwargs,
injection_kwargs, injection_kwargs,
injection_kwargs_len, injection_kwargs_len,
) )
keyword_args, awaitable_keyword_args = kw_return['kwargs'], kw_return['awaitables']
# TODO: Refactor
if awaitable_keyword_args:
call_future = asyncio.Future()
future = asyncio.Future()
future.set_result(
(
call_future,
call,
positional_args,
keyword_args,
awaitable_keyword_args,
),
)
kwargs_ready = asyncio.gather(future, *[value for _, value in awaitable_keyword_args])
kwargs_ready.add_done_callback(__async_call_callback)
asyncio.ensure_future(kwargs_ready)
return call_future
return call(*positional_args, **keyword_args) return call(*positional_args, **keyword_args)
# TODO: refactor
cdef inline object __async_call_callback(object injections):
(call_future, call, positional_args, keyword_args, awaitable_keyword_args), *awaited_keyword_args = injections.result()
for value, (name, _) in zip(awaited_keyword_args, awaitable_keyword_args):
keyword_args[name] = value
result = call(*positional_args, **keyword_args)
call_future.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,

View File

@ -2564,7 +2564,9 @@ 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's call.""" """Return result of provided callable's call."""
return __provide_keyword_args(kwargs, self.__kwargs, self.__kwargs_len) # TODO: hotfix, remove
kwargs = __provide_keyword_args(kwargs, self.__kwargs, self.__kwargs_len)
return kwargs['kwargs']
cdef class Resource(Provider): cdef class Resource(Provider):

View File

@ -0,0 +1,89 @@
import asyncio
import random
from dependency_injector import containers, providers
# Runtime import to get asyncutils module
import os
_TOP_DIR = os.path.abspath(
os.path.sep.join((
os.path.dirname(__file__),
'../',
)),
)
import sys
sys.path.append(_TOP_DIR)
from asyncutils import AsyncTestCase
RESOURCE1 = object()
RESOURCE2 = object()
async def init_resource(resource):
await asyncio.sleep(random.randint(1, 10) / 1000)
yield resource
await asyncio.sleep(random.randint(1, 10) / 1000)
class Client:
def __init__(self, resource1: object, resource2: object) -> None:
self.resource1 = resource1
self.resource2 = resource2
class Service:
def __init__(self, client: Client) -> None:
self.client = client
class Container(containers.DeclarativeContainer):
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
resource2 = providers.Resource(init_resource, providers.Object(RESOURCE2))
client = providers.Factory(
Client,
resource1=resource1,
resource2=resource2,
)
service = providers.Factory(
Service,
client=client,
)
class FactoryTests(AsyncTestCase):
def test_direct_injection(self):
container = Container()
client1 = self._run(container.client())
client2 = self._run(container.client())
self.assertIsInstance(client1, Client)
self.assertIs(client1.resource1, RESOURCE1)
self.assertIs(client1.resource2, RESOURCE2)
self.assertIsInstance(client2, Client)
self.assertIs(client2.resource1, RESOURCE1)
self.assertIs(client2.resource2, RESOURCE2)
def test_children_injection(self):
container = Container()
service1 = self._run(container.service())
service2 = self._run(container.service())
self.assertIsInstance(service1, Service)
self.assertIsInstance(service1.client, Client)
self.assertIs(service1.client.resource1, RESOURCE1)
self.assertIs(service1.client.resource2, RESOURCE2)
self.assertIsInstance(service2, Service)
self.assertIsInstance(service2.client, Client)
self.assertIs(service2.client.resource1, RESOURCE1)
self.assertIs(service2.client.resource2, RESOURCE2)
self.assertIsNot(service1.client, service2.client)