I use IPython in the terminal for most things, but I’d love to see it supported in Jupyter notebooks. I’d probably use notebooks a lot more with more Trio support.
As an alternative to a global nursery
object, there could be magic to run notebook cells concurrently as tasks. You could open a nursery block which runs in the background as you edit other notebook cells:
In [1]: %task async with trio.open_nursery() as nursery:
nursery.start_soon(ws_reverse_server, 8888)
I also saw a feature request in ipython to “Create a %with magic”, which would expose the context manager variable to the interactive shell. Extending that idea, I could see a cell with a magic background task, running a context manager with an interactive prompt:
In [2]: %task async with open_websocket_url("ws://localhost:8888") as ws:
>>> await ws.send_message(b"Hello, world.")
>>> await ws.get_message()
b'.dlrow ,olleH'
>>>
Not really sure how viable these ideas are, but it’s interesting to consider the possibilities…
I tried some approaches to getting your example code to work with the current IPython terminal and came up with a few results (one of them actually works).
The first one was:
import atexit
from functools import partial
import IPython
import trio
def ipython_embed(nursery):
class NurseryWrapper:
def __init__(self, nursery):
self._nursery = nursery
def start_soon(self, fn, *args):
trio.from_thread.run_sync(self._nursery.start_soon, fn, *args)
nursery = NurseryWrapper(nursery)
IPython.embed()
# Avoid ipython-history-sqlite3-threading error
atexit._run_exitfuncs()
async def main():
async with trio.open_nursery() as nursery:
await trio.to_thread.run_sync(ipython_embed, nursery)
nursery.cancel_scope.cancel()
trio.run(main)
This one works for some really basic stuff (i.e. run a background task that prints and sleeps in a loop), but the nursery can’t be used with %autoawait, and it’s probably broken in many other ways.
Next, I started looking at the ipython code and got confused. I decided to write my own REPL to get a feel for what kind of patterns I’d expect to recognize in the ipython code. In particular, I was curious about integration with Python Prompt Toolkit. I made a gist with two examples that sort of work (one for prompt-toolkit 2 and one for version 3). The script for version 2 seems more stable. I think they both end up losing the ability to print to stdout after a while, so not really usable…
Anyway, the most successful attempt was just forking prompt-toolkit and making it work with Trio natively. Version 3 of prompt-toolkit is asyncio native, so I just went in with brute force and put Trio code where I saw asyncio code. The Trio REPL example script can do this (with syntax hilighting!):
>>> import trio_websocket
>>> async def tock(n=5):
... for i in range(n):
... print(i + 1)
... await trio.sleep(i + 1)
...
>>> await tock(3)
1
2
3
>>> conn = await trio_websocket.connect_websocket_url(nursery, "ws://localhost:8888")
>>> nursery.start_soon(tock, 42)
1
2
3
4
>>> await conn.send_message("Hello, world.")
5
6
>>> await conn.get_message()
.dlrow ,olleH
>>>
I think I’ll continue working on this idea in the context of the Trio monitor, the maintenance of which is an open issue. I’m hoping that this work could eventually be a path towards Trio support in Jupyter notebooks.
Thanks for the inspiration to look into all this stuff! I’ve been curious, but didn’t take a serious look until you made this post.