Programmatically Creating Styled Cells In Jupyter Notebooks
I've been playing around with the idea of using Jupyter notebooks as a narrative game interface for a while, and finally got around to doing some prototyping this week. One thing I really wanted to do was require users to run code cells in Jupyter to unlock story beats: Jupyter notebooks are usually presented as a step-by-step tutorial, which is perfectly fine if you're trying to teach someone how to run a linear regression in sklearn
but less so if you're trying to build suspense over the course of a story.
It's pretty easy to get output from just running a Python function, obviously:
But I wanted to be able to do two things: programmatically create code cells, and programmatically create styled text cells.
Both of these are relatively easy to do if you don't get caught in the weeds like I did (we'll get to that):
You can use get_ipython().set_next_input("your code here")
to programmatically create a cell that the reader of your notebook then has to run. Easy enough to build on top of this by obfuscating some code that outputs your story beats in a separate file.
Unfortunately, set_next_input
can't set a cell type other than code (and is limited to one cell at a time, as I understand it). If you want to overcomplicate things, you can also use the JupyterFrontend
app:commands
API to inject code as well and it can get more granular than set_next_input
(e.g. you can run it in a loop to create N cells), but it still couldn't set a cell type or render styled text:
(It's possible that if I understood the notebook JSON structure better I might be able to use it for other purposes but I couldn't find any helpful documentation)
I kept digging until a JupyterLab forum thread gave me the answer I didn't want:
There are plenty of ways to create styled output from a code cell, but I got sucked into a bit of a rabbit hole: I wanted to create a styled input cell programmatically. I am not sure exactly why I wanted to do this other than it was annoying to me that I could only create a code cell with IPython's built-in methods.
I'll include those styled output methods here just in case they're useful to someone:
run_cell_magic
and display_markdown
both work perfectly fine for displaying formatted text, but again, they don't display as inputs (you can tell by the lack of a little number next to them that indicates they were run as code).
At this point I had dug into StackOverflow, the official Jupyter forum, GitHub issues, and the API documentation for IPython, which is about where I tap out when doing research. This seemed like a problem somebody should have solved, but my Google-fu may have been lacking. I finally stumbled across the answer I was looking for when I found an issue recommending someone make a widget for this problem - it turned out there was already one that met my needs!
I finally found my answer after a lot of digging in the iPyWidgets documentation: a simple HTML widget.
I could go even further down the rabbit hole here and get upset that it's not numbered as its own execution block, rather sharing the parent function's index (14), but I think that's a reasonable stopping point for the day. Next up: animating Jupyter cells!
Sources/The Weird Search Path I followed:
Member discussion