Skip to content

asyncio.iscoroutinefunction returns false for Cython async function objects #2273

@danigosa

Description

@danigosa

Referring to #2092

@scoder the implementation in asyncio is ridiculous

def iscoroutinefunction(func):
    """Return True if func is a decorated coroutine function."""
    return (inspect.iscoroutinefunction(func) or
            getattr(func, '_is_coroutine', None) is _is_coroutine)

So once we agreed inspect returns False, it happens to be that CPython just add a simple attribute to the function _is_coroutine, I think if Cython does this adding up this attribute it will make everything work

Here: https://github.com/python/cpython/blob/789e359f51d2b27bea01b8c6c3bf090aaedf8839/Lib/asyncio/coroutines.py#L152

And here:
https://github.com/python/cpython/blob/789e359f51d2b27bea01b8c6c3bf090aaedf8839/Lib/asyncio/coroutines.py#L160

So the solution as it can't be another it results in this, from my test in iPython:

In [46]: fut = init_connection()
In [47]: asyncio.iscoroutine(fut)
Out[47]: True

In [49]: init_connection.__class__
Out[49]: cython_function_or_method

In [50]: asyncio.iscoroutinefunction(init_connection)
Out[50]: False

In [56]: from asyncio.coroutines import _is_coroutine

In [57]: init_connection._is_coroutine = _is_coroutine

In [58]: asyncio.iscoroutinefunction(init_connection)
Out[58]: True

Dirty but easy. Don't know if this is a 5 seconds issue or a hara-kiri, theoretically it should be easy to plug the attribute runtime from from asyncio.coroutines import _is_coroutine, or not.

This is a super annoying problem (I don't blame anyone with the word bug) that make cythonized code to be very dramatic in several asyncio frameworks. asyncio.iscoroutine work well with cythonized funcs for the silliest reason I think.

Following the cyfunctions above, create_token is NOT a coroutine, while init_connection is an async function, this workaround works in both so it's ok if you can schedule the functions whether are coros or not, no way to easily fix this with asyncio.iscoroutinefunction or at least easier than that:

In [59]: async def test():
    ...:     await create_token({})
    ...:
In [61]: asyncio.get_event_loop().run_until_complete(test())
TypeError: object str can't be used in 'await' expression

In [73]: async def test():
    ...:     return await init_connection()
Out[74]: <asyncpg.pool.Pool at 0x7f78e04752c8>

Here comes the magic:

In [69]: async def test():
    ...:     return await asyncio.coroutine(create_token)({})
In [70]: asyncio.get_event_loop().run_until_complete(test())
Out[70]: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1MjU1NTk0NTB9.v8wRZGIke6RYizIpJhl4oaypKyvuVARKaiq0KdpJg6XJ0qTB0o76BuTLera6kSQ_5qXnAb7_9DQSadwdRqgPmw'

In [71]: async def test():
    ...:     return await asyncio.coroutine(init_connection)()
In [72]: asyncio.get_event_loop().run_until_complete(test())
Out[72]: <asyncpg.pool.Pool at 0x7f78df48af48>

With that the workaround:

def nasty_iscoroutinefunction(func):
      is_coro = asyncio. iscoroutinefunction(func)
      if func.__class__.__name__ == 'cython_function_or_method':
            # It's cythonized, danger!
            is_coro = True  # We'll make it possible
            func = asyncio.coroutine(func)
      return is_coro

This works!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions