How to roundtrip RPC from python kernel to jupyterlab frontend

I am trying to get jupyterlite running in google sheets sidebar (using google’s App Script APIs), allowing for reading, modifying, and writing sheet data with your favorite python tools such as polars. The user experience in python would be to call eg jupysheets.readRange(“A1:C2”) and to get back eg [[1,2,3], [4,5,6]]. I have some parts of this working:

I have been prototyping this in GitHub - NickCrews/jupysheets . I have a lot of the different plumbing bits figured out, but am still stuck on the fundamental mismatch that jupyterlab’s comms are built around “command” semantics, not “request/response” semantics. Eg I have successfully sent a “read_sheet” message from python, over the "network” boundary to the jupyterlab frontend, and then forwarded that over the iframe boundary to the javascript on the host html page. But, that is a one way stream, since commands have no notion of a return value. I could theoretically build a request/response abstraction on top of the message-sending semantics, but first wanted to ask a few questions here:

1. I couldn’t find prior work that did this. Any tips? Is there some other mechanism I should be looking for instead of the commands that I am currently using?
2. If I did build the request/response pattern on top of the messaging, I was imagining it would be something like this, in pseudocode

def read_sheet(range_string):
    result = None
    def handle_response(request_id, resp):
         result = resp
    request_id = uuid.uuidv4()
    jupysheets.register_response_handler(request_id, handle_response)
    jupysheets.send_request(request_id, func="read_sheet", args={"range_string": range_string})
    while until_timeout(<10 seconds>):
        if result is not None:
            return result
    # throw timeout error

But I don’t know if that will actually work, eg does the kernel process events while a python function is running? Do I need to do something with threads?? I’m not sure if those even work in pyodide??

I think that once I get the python-jupyterlab frontent interface working, then I can get the interface over the iframe boundary (which also uses messaging semantics) without too much difficulty, since both sides are running js with good async support.

Thanks for any thoughts or help!

1 Like

There is an open pull request to expand the command model in ipylab with best effort results (sometimes these are badly behaved). With some familiarity in the ipylab code, the changes in commands.(ts|py) show how one might build a full duplex bus.

Otherwise: anything that looks like blocking is indeed going to block incoming events without an extreme amount of hacking. One would want to consider:

  • using async queues and tasks
  • using the observe pattern from ipwidgets more fully, treating it as a “state sync with callbacks” kind of problem

The latter can be quite expensive if one is always trying to send all the data back and forth However, fine-grained general purpose spreadsheet diff calculation is also expensive. Perhaps there might be a way to create a Range widget with limited selector scope.

1 Like