`IPython` integration 😎
Like it sounds, with more refinements to come!
This is a POC and seems to do about what I'd like; would appreciate lurker feedback 😉
ping @mikenerone, original author of the base gists for this:
- https://gist.github.com/mikenerone/786ce75cf8d906ae4ad1e0b57933c23f
- https://gist.github.com/mikenerone/3640fdd450b4ca55ee8df4d4da5a7165
TODO:
- [ ] link to @mikenerone's github in credits
- [ ] adjust credit to point to @mikenerone's GH
- [ ] get colors and user's config workin
- see config docs: https://ipython.readthedocs.io/en/stable/config/details.html#editor-configuration
- [ ] toy with a default set of actor mgmt / respawn APIs?
Turns out there might be a better solution for what we actually want (await <blah> inside any REPL, but mostly our debugger UX).
from @smurfix on gitter:
import trio
import greenback as gb
AW=gb.await_
async def wat():
await trio.sleep(0.1)
return "yes"
async def run():
await gb.ensure_portal()
breakpoint()
pass
trio.run(run)
which can be called from repl like:
(Pdb) AW(wat())
'yes'
with follow up from @oremanj actually using pdb++ 💥 :
$ python3.8 -c "import trio, pdb, greenback; trio.run(greenback.with_portal_run_sync, lambda: breakpoint())"
(Pdb++) greenback.await_(trio.sleep(1))
<1sec delay>
also a further extended example 🏄🏼
$ python3.8 -c "import trio, greenback, code; trio.run(greenback.with_portal_run_sync, code.interact)"
>>> import trio
>>> from greenback import await_ as aw, async_context as acm
>>> async def task(interval, msg):
... while True:
... await trio.sleep(interval)
... print(msg)
...
>>> with acm(trio.open_nursery()) as nursery:
... nursery.start_soon(task, 2, "every two seconds")
... nursery.start_soon(task, 0.75, "every 3/4 second")
... aw(task(1.7, "every 1.7 seconds"))
...
every 3/4 second
every 3/4 second
every 1.7 seconds
every two seconds
every 3/4 second
every 3/4 second
every 1.7 seconds
every 3/4 second
every two seconds
^CTraceback (most recent call last):
File "/usr/lib/python3.8/code.py", line 90, in runcode
exec(code, self.locals)
File "<console>", line 4, in <module>
File "/usr/lib/python3.8/site-packages/greenback/_impl.py", line 653, in await_
raise exception_from_greenbacked_function
File "<console>", line 3, in task
File "/usr/lib/python3.8/site-packages/trio/_timeouts.py", line 75, in sleep
await sleep_until(trio.current_time() + seconds)
File "/usr/lib/python3.8/site-packages/trio/_timeouts.py", line 56, in sleep_until
await sleep_forever()
File "/usr/lib/python3.8/site-packages/trio/_timeouts.py", line 40, in sleep_forever
await trio.lowlevel.wait_task_rescheduled(lambda _: trio.lowlevel.Abort.SUCCEEDED)
File "/usr/lib/python3.8/site-packages/trio/_core/_traps.py", line 166, in wait_task_rescheduled
return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
File "/usr/lib/python3.8/site-packages/outcome/_sync.py", line 111, in unwrap
raise captured_error
File "/usr/lib/python3.8/site-packages/trio/_core/_run.py", line 1178, in raise_cancel
raise KeyboardInterrupt
KeyboardInterrupt
>>>
To make this work anywhere we should teach Trio's task creation code to unconditionally create a Greenback portal in every new task; otherwise debugging at random breakpoints won't work.
@smurfix agreed, this is already an issue with debugging single task crashes within a nursery as well.
Currently on a task crash, you can't breakpoint() at the (specific task's) error stack frame and instead always end up at the nursery exit; so, an extended nursery is needed for both of these cases:
- handling task-level crashes with a
breakpoint()and engaging the REPL in that task's frame, -
overriding the global hook to always allow using
awaitin the REPL (and obviously making the particular REPL plug-gable in the longer run)
Further, this probably plays best with the idea of a OCONursery (one-cancels-one) style nursery where every task has an associated individual cancel scope that can be managed independently of other tasks such that respawns can happen on a per-task-failure scenario.