Await an async function in Python debugger
Asked Answered
H

2

33

Is it possible to await arbitrary calls to an async function when inside a Python debugger?

Say I have the following code in some main.py file:

import asyncio

async def bar(x):
    return x + 1

async def foo():
    import ipdb; ipdb.set_trace()

asyncio.run(foo())

Now I want to test calling bar() with some argument inside the debugger to test the results. The following happens:

$ python3 main.py
> /Users/user/test/main.py(8)foo()
      7     import ipdb; ipdb.set_trace()
----> 8     return None
      9

ipdb> bar(1)
<coroutine object bar at 0x10404ae60>
main.py:1: RuntimeWarning: coroutine 'bar' was never awaited
  import asyncio
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
ipdb> await bar(1)
*** SyntaxError: 'await' outside function

Of course, I can get around this by having x = await bar(1) above my ipdb.set_trace(), and then inspecting the results, but then I can't try calling my functions in real time while the debugger is active.

Homerhomere answered 17/8, 2019 at 1:34 Comment(3)
The trouble with this is that when you reach your breakpoint, the event loop is already in the middle of processing an event. In order to get the result of bar(1), you need to allow control to return to the event loop, pick up your task from the queue, and run it. The debugger can't do that while leaving your current call stack in place. It seems like you should be able to create a new, private event loop and use that, but asyncio won't let you invoke another event loop while one is already runningEmber
Yeah, I had a similar idea to start a new event loop to run async functions on that one, but had no idea how to do it. I do see the problem you mentioned, though, where the main event loop is suspended because of the ipdb interpreter waiting for our input, and it makes sense. I wish ipdb had a utility function to do this somehow.Homerhomere
There is an open feature request for it, but there has been no activity: bugs.python.org/issue42045Fourlegged
W
6

Here a modified version of what I posted at https://mcmap.net/q/398587/-how-to-await-a-coroutine-in-pdb since it also solves this problem.

I found a solution using nest_asyncio. If one has the following async example script:

import asyncio
import nest_asyncio


async def bar(x):
    return x + 1

async def foo():
    import ipdb; ipdb.set_trace()


if __name__=="__main__":
    loop = asyncio.get_event_loop()
    nest_asyncio.apply(loop)
    loop.run_until_complete(foo())

One can then do:

      8 async def foo():
----> 9     import ipdb; ipdb.set_trace()
     10 

ipdb> loop = asyncio.get_event_loop()
ipdb> loop.run_until_complete(bar(1))
2

Admittedly it is a bit more tedious then await bar(1) but it gets the job done. Hopefully a more elegant solution will come up in the future.

Winglet answered 5/6, 2021 at 7:25 Comment(2)
For me this is the most convenient solution. Especially in recent versions where nest_asyncio.apply() doesn't even need the loop parameter, which defaults to asyncio.get_event_loop().Adobe
Also, one now can call asyncio.run() directly instead of needing to get the loop and calling loop.run_until_complete().Adobe
H
4

Seems like there's starting to be more support for this feature since Python 3.8. In particular, look at this issue bpo-37028

If you're still on Python 3.7, maybe aiomonitor could have something that supports this feature to a certain extent.

Homerhomere answered 30/10, 2019 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.