Stefan
Stefan

Reputation: 12340

How to correctly use kernel.execute method of Jupyter notebook in Javascript (timing issues)?

Below is a draft for using the Python kernel of the Jupyter notebook to execute Python code from a custom JavaScript client and here is a related question:

Where is a docs for Jupyter front-end extensions JavaScript API?

My draft works for some examples. However, I have still some timing issues.

=>Is there a bug in the execution order of the execute method of the Jupyter kernel?

Or is this the expected behavior? If so, what is the recommended way to use the execute method / how should I adapt my code so that my "JupyterTerminal" can be used in a general way?

When executing the following python code with my method executePythonCode, first the callback shell is called, resolving my promise. My calling main code will continue execution. Later on, the iopub callback is called, giving me the wanted text content.

file = open("D:/forecast4/trunk/databases/Demo/template.xml", "r")
print(file.read())

However, I would expect the iopub to be called first and the shell to be called second. I want my calling code to wait until the text has been retrieved.

I tried to remove the resolve(); command in the shell part, so that my promise is not resolved too early. That works for the above text reading example.

However, when executing code without output, the Promise is never resolved. Therefore, I seem to need that resolve() call in the shell callback.

Example on how to use Jupyter notebook kernel to execute Python code from a custom JavaScript client (still has some timing issues):

export default class JupyterTerminal {

constructor(jupyter){           
    this.__kernel = jupyter.notebook.kernel;
}

 async executePythonCode(pythonCode){
    
    var self=this;

    return new Promise(function(resolve, reject) {  

        var isResolved=false;
            
        var callbacks = {
                shell : {
                        reply : (data)=>{
                            var content = data.content
                            switch(content.status){
                                case 'ok':          
                                    isResolved=true;            
                                    resolve();
                                    break;
                                case 'error':
                                    reject(content.evalue)
                                    break;
                                default:
                                    throw new Error('Not yet implemented content status "' + content.status + '"');
                            }
                            
                        },
                },
                iopub : {
                         output : (data)=>{                              
                            var content = data.content;
                            switch(content.name){
                                case 'stderr':      
                                    if(isResolved){
                                        var message = 'Could not handle strr output while executing Python code '
                                                    +'because Promise already has been resolved:\n' + content.text;
                                        console.error(message);
                                    }                                   
                                    reject(content.text);
                                    break;
                                case 'stdout':
                                    if(isResolved){
                                        var message = 'Could not handle stout output while executing Python code '
                                                    +'because Promise already has been resolved:\n' + content.text;
                                        console.error(message);
                                    }   
                                    resolve(content.text);                                                                          
                                    break;                                          
                                case undefined:
                                    reject(content.ename + ': ' + content.evalue);  
                                    break;                                                                  
                                default:
                                    throw new Error('Not yet implemented content type "' + content.name + '"');
                            }
                         }
                }
        };                

        self.__kernel.execute(pythonCode, callbacks);
    });     
}

}

Upvotes: 2

Views: 2014

Answers (1)

Jonathan Gutow
Jonathan Gutow

Reputation: 305

Here is the simplest version I can come up with that seems to work. You will have to test it with your case to see if it is actually any better. The key is to remember that without the print() wrapping your statement there is no output. In the example below it will resolve with 'ok' as the status if you just put in the statement 1+1.

function wait_for_python(cmdstr){
    return new Promise((resolve,reject) => {
        var callbacks = {
            iopub: {
                output: (data) => resolve(data.content.text.trim())
            },
            
            shell: {
                reply: (data) => resolve(data.content.status)
            }

        };
        Jupyter.notebook.kernel.execute(cmdstr, callbacks);
    });
}

wait_for_python('print(1+1)').then((resolve)=>alert(resolve));

Upvotes: 2

Related Questions