class AsyncDI(type):
def __new__(cls, name, bases, attrs):
# Decorate any items that are 'async def' methods
registry = {}
new_attrs = {}
for key, value in attrs.items():
if inspect.iscoroutinefunction(value):
new_attrs[key] = make_method(value, registry)
registry[key] = new_attrs[key]
else:
new_attrs[key] = value
return super().__new__(cls, name, bases, new_attrs)
def make_method(method, registry):
@wraps(method)
async def inner(self, **kwargs):
parameters = inspect.signature(method).parameters.keys()
# Any parameters not provided by kwargs are resolved from registry
to_resolve = [p for p in parameters if p not in kwargs and p != "self"]
missing = [p for p in to_resolve if p not in registry]
assert (
not missing
), "The following DI parameters could not be found in the registry: {}".format(
missing
)
awaitables = [registry[name](self) for name in to_resolve]
# Assert that all missing params are awaitable
assert all(asyncio.iscoroutine(p) for p in awaitables)
results = {}
results.update(kwargs)
awaitable_results = await asyncio.gather(*awaitables)
results.update(
(p[0].__name__, p[1]) for p in zip(awaitables, awaitable_results)
)
return await method(self, **results)
return inner
Example usage:
class Foo(metaclass=AsyncDI):
async def other(self):
return 5
async def async_blah(self, other):
return 1 + other
f = Foo()
await f.async_blah()
# Outputs 6
New version:
Use like this: