jake kara, software engineer ‣ Margo: Margin Notes for Jupyter Notebooks ░

Margo: Margin Notes for Jupyter Notebooks

For my master’s thesis in Software Engineering from Harvard Extension School, I developed a suite of software for enhancing Jupyter Notebooks to be used as Python modules, and in other unintended contexts.

Margo Loader extends Python’s import statement so you can import Jupyter Notebooks as if they were .py files.

Since you might not want to execute every cell in a notebook when you import it, I developed a syntax called “Margo” for annotating cells with “margin notes” — specially formatted comments that provide extra information to interpreters. If you use syntax like # :: ignore-cell :: you can instruct Margo Loader to skip that cell during import.

For a live tutorial on how to write modular Jupyter Notebooks, and on Margo in general, check out this notebook on binder:

A screenshot of my modular notebooks tutorial running in Binder

I designed Margo syntax to be flexible, so that ignore-cell keyword isn’t actually part of Margo’s, it’s just a directives that the Margo Loader software considers meaningful. In addition to directives, Margo syntax supports arbitrary data serialization in several formats. This allows you to effectively extend the notebook format in a backwards compatible manner. I’ve demonstrated in my thesis that Margin notes can be used to encode requirements.txt inside a notebook in order to integrate a .ipynb file with pip, and they can be used to encode a list of input and output files to integrate a notebook with a tool like GNU Make.

Taking this concept even further, I desmontrated that developers can build new graphical interface features for Notebooks and serialize associated data in margin notes. To demonstrate this I built a proof of concept editor UI that supports hierarchical cell relationships.

For example, imagine this notebook with two cells, one which tests another cell:

The Margo Editor UI prototype defining a cell that tests another cell

When serialized as a .ipynb file, the relationships are stored in Margo margin notes:

# :: cell.id: 'e973c97f' ::

def say_hello(to="World"):
    return f"Hello, {to}!"
# :: cell.id: 'dd974423' ::
# :: rel.tests: "e973c97f" ::
def test_say_hello():
    assert say_hello() == "Hello, World!"