3

So I'm trying to port a code to work with Asyncio.

My problem are the @propertys, which - if not yet cached - need a network request to complete.

While I can await them without a problem in most contexts using that property in the __str__ method fails which is kinda expected.

What is the most pythonic way to solve this, other than caching the value on instance creation or just not using Asyncio.


Here is a example code illustrating the issue:

import requests
from typing import Union


class Example(object):
  _username: Union[str, None]

  @property
  def username(self):
    if not _username:
      r = request.get('https://api.example.com/getMe')
      self._username = r.json()['username']
    return self._username

  def __str__(self):
    return f"Example(username={self.username!r})"

e = Example()
str(e)

So far I could come up with the following.

I could substitute @property with @async_property ( PyPi, Github, Docs), and requests with httpx (PyPi, Github, Docs).

However it is still not working.

My attempt at porting it to Asyncio:

import httpx
from typing import Union

from async_property import async_property


class Example(object):
  _username: Union[str, None]

  @async_property
  async def username(self):
    if not _username:
      async with httpx.AsyncClient() as client:
        r = await client.get('https://www.example.org/')
        self._username = r.json()['username']
    return self._username

  async def __str__(self):
    return f"Example(username={await self.username!r})"

e = Example()
str(e)

Fails with TypeError: __str__ returned non-string (type coroutine).

luckydonald
  • 5,976
  • 4
  • 38
  • 58
  • There is no way to call asyncio code from within the main thread - you either have to call it using `asyncio.run(asyncio_function())`, or from within another `asyncio` function. I suggest you read the very first part of the introduction to asyncio...? [link](https://docs.python.org/3/library/asyncio.html) – Nearoo Mar 14 '20 at 18:02
  • Is it really necessary for `str(e)` to work as in the threaded code? It seems like a convenient thing to have when possible, but it's simply not possible with async. – user4815162342 Mar 14 '20 at 18:19
  • 1
    Consider that `str(obj)` breaking synchronicity may be a major surprise and even a potential source of bugs! Code may depend on no other code executing for certain blocks to guarantee consistency; if a seemingly harmless `str()` call would as a side effect cause other scheduled async code to run which may potentially change some state unexpectedly, that'd be a hard to track bug. – deceze Mar 15 '20 at 18:35
  • Does this answer your question? [python async special class methods \_\_delete\_\_](https://stackoverflow.com/questions/43268390/python-async-special-class-methods-delete) – Nearoo Mar 16 '20 at 10:17
  • @Nearoo I'm well aware of _why_ it isn't working, that's not why I came to SO. I asked what would be a good way around a problem like this, because all magic methods are sync by nature, which that example illustrates. The code is obviously omitting the starting of the loop, but if you use e.g. IPython it [supports async](https://blog.jupyter.org/ipython-7-0-async-repl-a35ce050f7f7) that just fine. I'm well aware of the limitations of Asyncio and that a sync method can't call async out of the box, but still it would be easy to miss a feature like `str(await e)`, `async class` or similar. – luckydonald Mar 16 '20 at 23:23
  • Ok I see what you mean. What if you defined sync and async function separately? You could make a function `async def a__str__(self)`, and define `astr(obj)` that returns the value of that. `__str__()` could also spin up an event loop on its own and then call `a__str__`, so you have a string method for both sync and async callls. Build a whole super class implementing all magic methods this way and you've got a package that might benefit others. – Nearoo Mar 17 '20 at 10:44
  • It's not even that inefficient - `get_event_loop` will create a new loop the first time it's called, but after that, it will always return the same one it did before. – Nearoo Mar 17 '20 at 10:49

0 Answers0