Frontent to kernel callback

Hi,
this is Josh, webdeveloper, but new to Jupyterlab development, asking for help getting started exchanging information between (python) kernels and frontend extensions.

This page explains how frontend and kernel can communicate in Jupyter:
https://jupyter-notebook.readthedocs.io/en/stable/comms.html#opening-a-comm-from-the-kernel
JupyterLab requires few changes to the frontend index.ts extension instead of the Jupyter…register_target() call from the Comms document:

...
import { INotebookTracker, NotebookPanel }
from '@jupyterlab/notebook';
...
const extension: JupyterFrontEndPlugin<void> = {
  id: 'myid',
  autoStart: true,
  requires: [ICommandPalette, INotebookTracker],
  activate: (app: JupyterFrontEnd, palette: ICommandPalette,
    notebooks: INotebookTracker) => {
    ....
    notebooks.widgetAdded.connect(( sender :any, nbPanel: NotebookPanel ) => {                
      nbPanel.session.ready.then(()=>{
        nbPanel.session.kernel.registerCommTarget('my_comm_target',
            (comm: any) => {
                comm.onMsg = (msg: any) => {
                    console.log("target my_comm_target...",msg)
                };
                let count:number=0;
                setInterval(()=>{
                  console.log("send: ",count);
                  comm.send({'count':count++});
                },2000)
            }
        );
      });
    });
  }  
};

Together with the following kernel code from the link above, the frontend receives the “{‘foo’: 2}” data from the kernel:

from ipykernel.comm import Comm
my_comm = Comm(target_name='my_comm_target', data={'foo': 1})
my_comm.send({'foo': 2})
@my_comm.on_msg
def _recv(msg):
    print(msg)

The problem is that the on_msg kernel side callback is never reached, although the frontend sends “{‘count’:count++}” every two seconds. What’s wrong with this code?

Josh

1 Like

The problem is somewhat different than I thought, and still your professional help is needed to make the callback work as expected.

I have changed the frontend callback code from my previous post to write to a file instead of only to stdout:

@my_comm.on_msg
def _recv(msg):
    print(msg)
    with open("/tmp/foo.log","a") as f:
        f.write(str(msg)+"\n")

Now the messages are written to the file, but still no output in the browser. It seems like ipythons stdout is redirected to “nowhere” as soon as the Comm object is instantiated. I need to fix this but have no clue how.

Josh

Hi again,
is anybody able to reproduce my problem that the kernel stops printing messages when connected to a CommTarget on the frontend?

Any clue why this happens?

Anybody willing to discuss this maybe by email?

It is hard to develop for Jupyter if we don’t get any sort of reply with such a problem.

Thanks
Josh

Yes I am. I believe a solution for this would be to have a place in JupyterLab to show stderr/stdout messages that are received when we are not running a cell. There is probably an open issue for this already somewhere.

@saulshanabrook thanks a lot for giving a reply. May I ask you for some more details, where you think an open issue for this problem might exist? I search in all directions and didn’t find any.

What do you think about this cell:

import threading
import time

def loop():
    for k in range(60):
        print(k)
        time.sleep(1)
        
        
threading.Thread(target=loop).start()

It stops “running” immediately when the thread is started, but the thread continues to write to stdout. This makes me think my initial problem with stdout from kernel Comms is different, more involved?

I am not sure, unfortunately. I would open a new issue then describing your use case.

Mehmet Bektas recently submitted a PR for a place for output messages to go when they aren’t handled by a cell: https://github.com/jupyterlab/jupyterlab/pull/6833

One solution is to use an output widget to capture the output: https://ipywidgets.readthedocs.io/en/stable/examples/Output%20Widget.html#Debugging-errors-in-callbacks-with-the-output-widget

Hi, first, thanks for the code example, very useful. The message is indeed received, here is an example with setting the value to a global variable.

last_message=0

@my_comm.on_msg
def _recv(msg):
    global last_message
    last_message = msg