For some years now, Jupyter has been using an event loop as the execution model for all Jupyter components: usually
tornado for the web (as opposed to e.g.
qt for the desktop). In this case, as both
tornado apps, everything’s fine.
how the ipython can do this on the fly?
What happens at the tail end of the
initialize is a call to
listen, which schedules
tornado to route requests whenever it is contacted on its assigned OS port, but otherwise not do much.
Both applications run in the same OS thread, politely scheduling their next activity after the next expensive thing (like waiting for a user to make a request, reading a file, querying a database).
How does the app instance know there is a new handler added to it?
By interacting directly with the live
NotebookApp object, we can change what the next routed response will do. The downside is that one “bad read” can deadlock everything. In the above example, you really don’t want to make a blocking call (e.g.
localhost:9999… python will be waiting on itself for a response that will never come.
This meant we could run other
tornado apps, but that was pretty much it. The present is much more exciting. Once Python 3.6 rolled around,
tornado transparently dropped its “custom” loop and use the built-in
asyncio loop. Now a great many loop-agnostic (or at least, aware) applications can be used together:
home-assistant … the list will grow.
Most incumbent Python web environments, (e.g.
django) won’t be able to be used this way for a time, if ever. Historically, they did not use an event loop…
django is starting, though it’s going to take a long time to get the database story squared, as the unit-of-work pattern of the Django ORM (or most any ORM) is not generally not compatible with asynchronous execution, without some pretty dark hackery (e.g.
There is a bit of a dark shadow, though, at least for windows users:
asyncio loops aren’t going to play very nicely together anytime soon, so a lot of cool features just aren’t going to work there.