Deprecation of asyncio.coroutine

I have recently learned of the deprecation and pending removal of asyncio.coroutine (deprecated since 3.8, to be removed in 3.10), and this will greatly affect a pattern of using it to safely wrap regular functions when executing them in an async context.

This pattern is very helpful when writing libraries and frameworks, where end users can submit their own callables to be executed by the library code (e.g. callbacks and similar); allowing the users to submit both coroutines and regular functions greatly helps with their learning curve.

Of course, this can be replaced by a simple asyncio.iscoroutine check, but it is practical to have a simple decorator that can be used inline (including on lambdas).

Is there any chance that the decision to remove asyncio.coroutine could be reversed?

OK, I have since discovered types.coroutine, which is not set to be removed in Python 3.10. However, it does not work exactly the same as asyncio.coroutine; in fact, both of them work differently than “native” coroutines (i.e. functions defined with async def). I’ve done some research, and here are the results:

  1. Native coroutines (created with async def)

    • iscoroutinefunction: True
    • iscoroutine: False
    • isawaitable: False
  2. Result of calling a native coroutine

    • iscoroutinefunction: False
    • iscoroutine: True
    • isawaitable: True
  3. Function converted using asyncio.coroutine

    • iscoroutinefunction: False
    • iscoroutine: False
    • isawaitable: False
  4. Result of calling a result of asyncio.coroutine

    • iscoroutinefunction: False
    • iscoroutine: False
    • isawaitable: True
  5. Function converted using types.coroutine

    • iscoroutinefunction: False
    • iscoroutine: False
    • isawaitable: False
  6. Result of calling a result of types.coroutine

    • iscoroutinefunction: False
    • iscoroutine: False
    • isawaitable: False

So the best approach seems to be writing a custom decorator; something like:

def coroutine(fn: typing.Callable) -> typing.Callable:
    if inspect.iscoroutinefunction(fn):
        return fn

    @functools.wraps(fn)
    async def _wrapper(*args, **kwargs):
        return fn(*args, **kwargs)

    return _wrapper