Async support
askerka opened this issue · 6 comments
Hi! Thank you for the great library.
I just wanted to ask if there any plans for support async version of it.
Thanks!
Hey, thank you for the kind words. I have no plans to implement this as I don't need it but I'm happy to accept a pull request (if someone desires to create one there's some design discussion to have beforehand).
Hi, I've been using injector in most of my projects for a couple of months now, and found 2 different approaches to define providers of awaitable types, which I think you may find helpful. A most basic example would be aioredis, as its create_connection
and create_redis_pool
are both coroutines.
First, most obvious approach:
Using injector.binder.bind(Redis, to=redis)
in an async context:
async def create_app():
"""Create and return aiohttp application."""
module = MyModule()
injector = Injector(module)
# Use async context to bind awaitables
await injector.call_with_injection(module.setup_aio_providers)
return injector.get(App)
And in the module:
Class MyModule(Module):
...
@inject
async def setup_aio_providers(self, injector: Injector, config: Dynaconf):
redis = await create_redis_pool(
config.redis.address,
db=config.redis.db_index,
encoding=config.redis.encoding,
)
injector.binder.bind(Redis, to=redis)
The downside is, we're losing the lazy-loaded nature of providers themselves, meaning redis is always created, event if we don't use it (important if you reuse the code for multiple apps / microservices).
Second approach
Use thread pool to set-up redis, while passing your loop explicitly to the constructor. I think it's a cleaner solution, which regains the lazy nature and keeps the module more vanilla.
Class MyModule(Module):
...
@provider
def provide_event_loop(self) -> Loop:
return asyncio.get_event_loop()
@provider
@singleton
def provide_redis(self, config: Dynaconf, loop: Loop, pool: ThreadPoolExecutor) -> Redis:
coroutine = create_redis_pool(
config.redis.address,
db=config.redis.db_index,
loop=loop,
encoding=config.redis.encoding,
)
return pool.submit(asyncio.run, coroutine).result()
The second approach I haven't field tested anywhere, please let me know if It's not safe / a bad practice.
Hope this helps.
The second approach should work and is a good way to achieve on demand object creation, yeah.
Thank you, @Litery!
We went with the first option because with the second one we have a problem like "Task attached to a different loop" because asyncio.run
creates a new loop and close it after. We've tried to fix it but unsuccessfully.
I believe I can close the issue. Thank you!
Hi, I've been using injector in most of my projects for a couple of months now, and found 2 different approaches to define providers of awaitable types, which I think you may find helpful. A most basic example would be aioredis, as its
create_connection
andcreate_redis_pool
are both coroutines.First, most obvious approach:
Using
injector.binder.bind(Redis, to=redis)
in an async context:async def create_app(): """Create and return aiohttp application.""" module = MyModule() injector = Injector(module) # Use async context to bind awaitables await injector.call_with_injection(module.setup_aio_providers) return injector.get(App)And in the module:
Class MyModule(Module): ... @inject async def setup_aio_providers(self, injector: Injector, config: Dynaconf): redis = await create_redis_pool( config.redis.address, db=config.redis.db_index, encoding=config.redis.encoding, ) injector.binder.bind(Redis, to=redis)The downside is, we're losing the lazy-loaded nature of providers themselves, meaning redis is always created, event if we don't use it (important if you reuse the code for multiple apps / microservices).
Second approach
Use thread pool to set-up redis, while passing your loop explicitly to the constructor. I think it's a cleaner solution, which regains the lazy nature and keeps the module more vanilla.
Class MyModule(Module): ... @provider def provide_event_loop(self) -> Loop: return asyncio.get_event_loop() @provider @singleton def provide_redis(self, config: Dynaconf, loop: Loop, pool: ThreadPoolExecutor) -> Redis: coroutine = create_redis_pool( config.redis.address, db=config.redis.db_index, loop=loop, encoding=config.redis.encoding, ) return pool.submit(asyncio.run, coroutine).result()The second approach I haven't field tested anywhere, please let me know if It's not safe / a bad practice. Hope this helps.
In tests, how would you replace Redis
with some mock e.g.?