0

I'm trying to create a wrapper around an asyncio coroutine that allows the user to use it as a "normal" function.

To give a bit of context, this is a function inside a package that war originally not-async. For a series of reasons, I now need to have an async version of it. To avoid duplicating the whole code, I'm trying to create a wrapper to allow existing code (that doesn't use asyncio) to keep running without breaking back compatibility. To make things more complicated, the majority of the users (it's a company code) use this code inside Spyder IDE.

To sort it, I did something like this

import asyncio

async def an_async_subfunction(t, tag):
    print(f"I'm inside an_async_subfunction named {tag}")
    await asyncio.sleep(t)
    print(f"Leaving an_async_subfunction named {tag}")


async def an_async_function(n):
    print(f"I'm inside an_async_function")
    tasks = [an_async_subfunction(t, t) for t in range(n)]
    await asyncio.gather(*tasks)
    print(f"Leaving an_async_function")

async def main_async(n):
    # the old main function, now become a corouting
    await an_async_function(n)

    return 'a result'

def main(*args):
    # the wrapper exposed to the users
    return asyncio.run(main_async(*args))

if __name__ == '__main__':

    print('Normal version')
    # The user can call the main function without bothering with asyncio
    result = main(3)

    print('Async version')
    # ...or can use the async version of main if he wants
    async def a_user_defined_async_function():
        return await main_async(3)

    result = asyncio.run(a_user_defined_async_function())

This works as expected, allowing the basic user to call main without bothering that it is a coroutine, while if a user wants to use main inside a custom-made async function, he can use main_async.

However, if you try to run this code in Spyder, you get the error:

RuntimeError: asyncio.run() cannot be called from a running event loop

This is caused by the fact that Spyder has its own event loop running as explained here.

I tried to fix it doing something like:

def main(*args):
    if asyncio.get_event_loop().is_running():
        return asyncio.create_task(main_async(*args)).result()
    else:
        return asyncio.run(main_async(*args))

This is now "Spyder-friendly" an it works inside Spyder without problems. The problem is that .result() is called before the Task created by asyncio.create_task is finished and an InvalidStateError exception is returned.

I can't put an await in front of create_task as main is not a coroutine, and I can't make main a coroutine, otherwise the whole thing would have been pointless.

Is there a solution to this mess?

Luca
  • 1,299
  • 1
  • 13
  • 27

0 Answers0