2

I have a class that has an attribute that holds a Redis connection as seen below:

import redis

class RedisService:
    db = redis.Redis(host=RedisConfig.REDIS_HOST, port=RedisConfig.REDIS_PORT)

    @staticmethod
    def exists(key):
        return RedisService.db.exists(key)

This works well. But now I want to replace this with async alternatives:

import aioredis

class RedisService:
    db = await aioredis.create_connection((RedisConfig.REDIS_HOST, RedisConfig.REDIS_PORT))

    @staticmethod
    async def exists(key):
        value = await RedisService.db.execute('GET', key)
        return value

But await is not allowed to assign a value to a class attribute. If I remove await, when I call RedisService.exists() I get:

File "./src/service/redis.py", line 12, in exists
     value = await RedisService.db.execute('GET', key)
 AttributeError: 'coroutine' object has no attribute 'execute'

So how can I assign a value to a class attribute by awaiting a coroutine?

Note that, I directly call await RedisService.exists() without creating an object instance. It should be a static method and should've been called without creating an instance for some BL reason.

iedmrc
  • 662
  • 2
  • 8
  • 18

1 Answers1

1

You can create an init function in the module that initializes the needed class attributes:

# let's assume module name redis_service.py

class RedisService:
    ...

async def init():
    RedisService.db = await aioredis.create_connection(...)

You would invoke this function from a main entry point:

import redis_service
...

async def main():
    await redis_service.init()
    ...

if __name__ == '__main__':
    asyncio.run(main())

This has the advantage of being able to re-initialize the "global" async data, in case you run asyncio.run() more than once. Also, the object returned by aioredis.create_connection() is most likely tied to the current event loop, so executing it at class definition time would preclude the use of asyncio.run to begin with.

user4815162342
  • 104,573
  • 13
  • 179
  • 246
  • Not a bad idea but I don't want to init (or create an object) on class. I need to call static methods of the class immediately and that static method uses its attributes. Like `RedisService.get_key()`. If there is an initialization then there are some methods on the web that uses metaclasses and `__init__` constructor to achieve this purpose. So I would prefer them. – iedmrc Aug 21 '20 at 07:32
  • @iedmrc Why do you need to invoke static methods immediately? How can you even do that if you are at top-level and not in an `async def`? Forcing initialization at load-time using metaclass sounds like it will make the approach incompatible with `asyncio.run`, which is IMHO a bad idea. – user4815162342 Aug 21 '20 at 07:53
  • It's much like how we use classes here for some BL reasons. I think this is not what we should think of. IMO, there must be a neat way to set a class attribute by calling an async function as simple as calling a sync function. Just like: `an_attr = await a_func()`. It is really interesting that having such an assignment with sync functions but not async functions. If we cannot, why we have `await` there :). Okay, I understand we have not such thing thought not good to not have, but I'd expect a much more similar way to achieve this. But here you propose a more complicated way. BTW, so thanks! – iedmrc Aug 21 '20 at 08:21
  • @iedmrc The difference between sync and async code is that async must be driven by an event loop. This is why awaits must be in async functions and you cannot just await stuff at top-level. If you insist on awaiting in class definitions, perhaps you can put the class definitions inside an `async def`, and export them once done. But that will again require an `init`-style function. I don't think I can really help you here beyond what I already wrote in the answer, but perhaps someone else can - good luck! – user4815162342 Aug 21 '20 at 08:43
  • I've also tried getting the event loop outside (and the top) of the class and `class_attr = loop.run_until.complete(my_async_func())`. And also tried such things: `class_attr = asyncio.gather(my_async_func())` but got: `RuntimeError: This event loop is already running` and some other errors I don't remember now :) – iedmrc Aug 21 '20 at 08:50
  • 1
    @iedmrc Some of those things should work locally, but will fail when `async.run` creates a different event loop. What you're attempting to do is certainly not good practice and is a bad idea unless you really know what you're doing, which (no offense) doesn't appear to be the case here. This answer provides a robust solution that will work regardless of how you start your event loop. (It still won't work for simultaneous concurrent event loops, but that's a limitation of the class-attribute-based design, and not an important consideration for asyncio to begin with.) – user4815162342 Aug 21 '20 at 09:09