Kernel#

A Jupyter kernel is a special program that procedures code it got from the front end and pushes result back to the frontend.

import sys
from src.run_jupyter_kernel import IPKernelAppProcess, get_messages

sys.path.append("/tmp")

Manipulate kernels#

The jupyter kernelspec command is designed to work manipulate with jupyter kernels. Use the jupyter kernelspec list command to list available in the current jupyter setup kernels.

!jupyter kernelspec list
Available kernels:
  sql_kernel         /home/fedor/.virtualenvs/knowledge/share/jupyter/kernels/SQL_kernel
  postgres_kernel    /home/fedor/.virtualenvs/knowledge/share/jupyter/kernels/postgres_kernel
  python3            /home/fedor/.virtualenvs/knowledge/share/jupyter/kernels/python3
  universal_sql      /home/fedor/.virtualenvs/knowledge/share/jupyter/kernels/universal_sql
  bash               /home/fedor/.local/share/jupyter/kernels/bash

CLI#

jupyter-kernel tool comes with jupyter_client package.


It confuses me that jupyter_client provides the jupyter-kernel CLI tool. If you have the same doubts, the following cells will prove it.

The code in the following cell shows the file to which jupyter-kernel refers in the system.

!which jupyter-kernel
/usr/local/bin/jupyter-kernel

And behind it is Python code that refers to jupyter_client.kernelapp.main.

!cat $(which jupyter-kernel)
#!/usr/local/bin/python3.12
# -*- coding: utf-8 -*-
import re
import sys
from jupyter_client.kernelapp import main
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

Run from jupyter#

For the purposes of this project, it’s useful to be able to run jupyter kernels in jupyter notebooks. There are a number of problems associated with this, mainly related mainly to the fact that jupyter notebook uses it’s own kernel to work, and this fact blocks the creation of new jupyter kernels. Read more about this and possible solutions in the related page.

As a ready solution to be able to experiment with different jupyter kernel use src.run_jupyter_kernel.IPKernelAppProcess function.


The following cell shows the process of running of the kernel with src.run_jupyter_kernel.IPKernelAppProcess.

p = IPKernelAppProcess("/tmp/example_connection_file.json")
NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.

To exit, you will have to explicitly quit this process, by either sending
"quit" from a client, or using Ctrl-\ in UNIX-like environments.

To read more about this, see https://github.com/ipython/ipython/issues/2049


To connect another client to this kernel, use:
    --existing /tmp/example_connection_file.json

As a result, we got a typical ipykernel starting log.

Build kernel#

There are three ways to create your own Jupyter kernel; check more here. This page focuses on the simplest method: simple Python wrapper kernels.

Kernel is actually a subclass of the ipykernel.kernelbase.Kernel, which implements custom logic in for the kernel.


The following cell shows the implementation of the kernel. This is kernel that to any execution sends to the client {'name': 'stdout', 'text': <input>} where input is a code that is required to be executed.

%%writefile /tmp/my_kernel.py
from ipykernel.kernelbase import Kernel

class EchoKernel(Kernel):
    implementation = 'Echo'
    implementation_version = '1.0'
    language_info = {
        'name': 'Any text',
        'mimetype': 'text/plain',
        'file_extension': '.txt',
    }
    banner = "Echo kernel - as useful as a parrot"

    def do_execute(
        self,
        code,
        silent,
        store_history=True,
        user_expressions=None,
        allow_stdin=False
    ):
        if not silent:
            stream_content = {'name': 'stdout', 'text': code}
            self.send_response(self.iopub_socket, 'stream', stream_content)

        return {
            'status': 'ok',
            'execution_count': self.execution_count,
            'payload': [],
            'user_expressions': {},
        }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=EchoKernel)
Overwriting /tmp/my_kernel.py

The following cell runs EchoKernel.

from my_kernel import EchoKernel
p = IPKernelAppProcess(
    connection_file="/tmp/echo_kernel.json",
    kernel_class=EchoKernel
)
NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.

To exit, you will have to explicitly quit this process, by either sending
"quit" from a client, or using Ctrl-\ in UNIX-like environments.

To read more about this, see https://github.com/ipython/ipython/issues/2049


To connect another client to this kernel, use:
    --existing /tmp/echo_kernel.json

The next code runs this is message in the kernel we just created.

messages = get_messages(
    connection_file="/tmp/echo_kernel.json",
    code="this is message"
)
for msg in messages:
    if msg["header"]["msg_type"] == "stream":
        ans = msg["content"]
ans["text"]
'this is message'

And we got back this is message just as specified in the kernel.

Register kernel#

In the last step of creating jupyter kernels is registering them - so you would be able to run kernel from one of jupyter’s CLI/GUI interfaces. Registering a kernel involves adding a json configuration file that specifies how to run the kernel have to runned and some additional information about the kernel.


The following cell shows kernels already added to the environment.

!jupyter kernelspec list
Available kernels:
  bash       /usr/local/share/jupyter/kernels/bash
  python3    /usr/local/share/jupyter/kernels/python3

At the same path, we need to create a folder for the kernel and place kernel.json inside it. The argv key in kernel.json should execute the module we created earlier.

!mkdir -p /usr/local/share/jupyter/kernels/echo
%%writefile /usr/local/share/jupyter/kernels/echo/kernel.json
{
    "argv":[
        "python3", 
        "/tmp/my_kernel.py", 
        "-f", 
        "{connection_file}"
    ],
    "display_name":"Echo"
}
Writing /usr/local/share/jupyter/kernels/echo/kernel.json

Now the result of the jupyter kernelspec list command is the kernel you just created.

!jupyter kernelspec list
Available kernels:
  bash       /usr/local/share/jupyter/kernels/bash
  echo       /usr/local/share/jupyter/kernels/echo
  python3    /usr/local/share/jupyter/kernels/python3

To ensure that everything works correct let’s try to run echo kernel and send hello, parrot? to be executed.

from jupyter_client import KernelManager
km = KernelManager(kernel_name="echo")
km.start_kernel()

kc = km.client()
kc.start_channels()

kc.execute("hello, parrot?", reply=True)
while True:
   msg = kc.get_iopub_msg(timeout=5)
   if msg['msg_type'] == 'stream':
        break

kc.shutdown(reply=True)
km.shutdown_kernel(now=True)

As a result, the kernel returns exactly what we’ve sent it - exactly as specified in its logic.

msg["content"]["text"]
'hello, parrot?'

Even better, you can run a Jupyter Notebook file, select echo from the list of kernels, and use it just like any other notebook.