We have used Apache Zeppelin notebooks for several years, and although their interactive widgets are limited (just textboxes, pull-downs, and checkboxes), they have the desirable property of recalling user-entered values, even after restarting a kernel. We find this especially helpful for creating reproducible results.
Zeppelin defines and stores “forms” via its ZeppelinContext (‘z’) object, and once the user enters or selects a value in a Zeppelin form, that value is preserved and restored, even after you terminate the kernel, then close, reopen, and rerun the notebook. This behavior is essential for ensuring repeatable notebook results and is convenient for users who need to pause and resume their work later. Further,
if you share a ‘configured’ notebook with another user, they clearly see the choices you made, and if they choose to rerun it without alteration, the notebook will produce the same results, without demanding that user re-select the widget values once again.
JupyterLab’s ipywidgets do not share this desirable behavior. Instead, when you restart a kernel in JupyterLab, any values entered in widgets are lost. Each time the user runs a cell defining the widget, a new runtime widget is created, using the value dictated by its programmatic default, and discarding any value previously chosen by the notebook user. As a result, the user must re-enter the same values each time they run the notebook.
To solve this issue, we created a helper class, ipwstore, to store widget values in a separate sidecar file. When the notebook is re-run, the prior widget values are recovered from the sidecar file, and then used to update the live widget objects, creating a reproducible notebook result. Here are the details of our solution:
Sidecar File
The sidecar file is a plain text JSON document holding a unique key for every registered widget and each widget’s value. This file is created (or opened) when the class object is initialized. Widget values are persisted to the file if the user invokes the save() function, or automatically on destruction of the ipwstore object (e.g., when the user terminates the kernel).
Helper Class
Our helper class has a constructor and two main methods – add and save.
Constructor
The constructor that creates the ipwstore object requires the name of a sidecar (JSON) file. If that file already exists, the object populates its registry of widget keys and values from the JSON. If the file does not exist, a new file is opened for writing. We expect that each notebook will use its own sidecar file.
Add Method
The add method requires a unique key and an ipywidgets object. The notebook author should call the add method for each new widget created (except for any widget that needn’t persist its state). This method binds each live widget object to a unique key in its registry, enabling the widget’s value to be persisted to the JSON file and recovered later. The same method also assigns a live widget’s value from the values previously cached in the JSON (provided a matching key was present in the JSON file), creating a “replay from memory” of its prior state.
Save Method
The save method writes the widget keys and values to the sidecar file and is automatically invoked on destruction of the ipwstore object. As a result, the JSON file is automatically updated as the notebook kernel is normally terminated. (Aside from exploring the functionality, or to ensure persistence prior to an abnormal termination, there is no need to explicitly call the save method.)
Usage Summary
To use our helper class, the notebook author should instantiate a single ipwstore object near the beginning of the notebook, pointing to a unique sidecar file, and should register each new widget under a unique key using the add method.
Shortcomings of this Class
The three key drawbacks of this are: (1) the need to explicitly declare a sidecar file, which essentially divides the notebook into two parts: a “template” JupyterLab notebook containing all the code, and a collection of user-selected configurations in the JSON file, (2) the burden of invoking the add method for each widget, and (3) the burden of carefully providing a unique key for each widget.
To aid with (3), if you wish to be warned about any risk of accidentally duplicated keys, you can construct your ipwstore object in “developer mode” by setting strict_add=True. When your notebook has all the widgets it needs, you can reset the constructor to its more forgiving default (strict_add=False).