ipykernel icon indicating copy to clipboard operation
ipykernel copied to clipboard

Update embedding examples

Open jacobwilliams opened this issue 8 months ago • 1 comments

The internal_ipkernel.py and ipkernel_qtapp.py are out of date. Some issues:

  • It is using PyQt4, which is very old, and not supported on some platforms (e.g. osx-arm64)
  • Explicit use of PyQt means it doesn't work with PySide. Should use qtpy so it will work with Pyside2/6 or PyQt5/6.
  • This line: from IPython.lib.kernel import connect_qtconsole fails on recent ipythons. I think the replacement is from ipykernel import connect_qtconsole. See https://github.com/ipython/ipython/issues/13610
  • The connect calls also would need to be updated (I think those are the old style signals).

jacobwilliams avatar Apr 13 '25 13:04 jacobwilliams

FWIW something like this works:

#!/usr/bin/env python
"""Example integrating an IPython kernel into a GUI App.

This trivial GUI application internally starts an IPython kernel, to which Qt
consoles can be connected either by the user at the command line or started
from the GUI itself, via a button.  The GUI can also manipulate one variable in
the kernel's namespace, and print the namespace to the console.

Play with it by running the script and then opening one or more consoles, and
pushing the 'Counter++' and 'Namespace' buttons.

Upon exit, it should automatically close all consoles opened from the GUI.

Consoles attached separately from a terminal will not be terminated, though
they will notice that their kernel died.
"""
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import sys
import PySide6
from PySide6.QtWidgets import *

from ipykernel import connect_qtconsole
from ipykernel.kernelapp import IPKernelApp

# -----------------------------------------------------------------------------
# Functions and classes
# -----------------------------------------------------------------------------

class InternalIPKernel:
    """An internal ipykernel class."""

    def init_ipkernel(self, backend):
        """Start IPython kernel with GUI event loop and mpl support."""
        self.ipkernel = IPKernelApp.instance()
        self.ipkernel.initialize(
            [
                "python",
                "--matplotlib=qt",
                #'--log-level=10'
            ]
        )

        # To create and track active qt consoles
        self.consoles = []

        # This application will also act on the shell user namespace
        self.namespace = self.ipkernel.shell.user_ns

        # Example: a variable that will be seen by the user in the shell, and
        # that the GUI modifies (the 'Counter++' button increments it):
        self.namespace["app_counter"] = 0
        # self.namespace['ipkernel'] = self.ipkernel  # dbg

    def print_namespace(self, evt=None):
        """Print the namespace."""
        print("\n***Variables in User namespace***")
        for k, v in self.namespace.items():
            if not k.startswith("_"):
                print(f"{k} -> {v!r}")
        sys.stdout.flush()

    def new_qt_console(self, evt=None):
        """start a new qtconsole connected to our kernel"""
        self.consoles.append(connect_qtconsole(self.ipkernel.abs_connection_file))
        return self.consoles[-1]

    def count(self, evt=None):
        """Get the app counter value."""
        self.namespace["app_counter"] += 1

    def cleanup_consoles(self, evt=None):
        """Clean up the consoles."""
        for c in self.consoles:
            c.kill()
        self.consoles.clear()

class SimpleWindow(QWidget, InternalIPKernel):
    """A custom Qt widget for IPykernel."""

    def __init__(self, app):
        """Initialize the widget."""
        QWidget.__init__(self)
        self.app = app
        self.init_ipkernel("qt")
        self.add_widgets()

    def add_widgets(self):
        """Add the widget."""
        self.setGeometry(300, 300, 400, 70)
        self.setWindowTitle("IPython in your app")

        # Add simple buttons:
        console = QPushButton("Qt Console", self)
        console.setGeometry(10, 10, 100, 35)
        console.clicked.connect(self.new_qt_console)

        namespace = QPushButton("Namespace", self)
        namespace.setGeometry(120, 10, 100, 35)
        namespace.clicked.connect(self.print_namespace)

        count = QPushButton("Count++", self)
        count.setGeometry(230, 10, 80, 35)
        count.clicked.connect(self.count)

        # Quit and cleanup
        quit = QPushButton("Quit", self)
        quit.setGeometry(320, 10, 60, 35)
        quit.clicked.connect(self.ipkernel.kernel.do_shutdown)

        self.app.lastWindowClosed.connect(self.ipkernel.kernel.do_shutdown)

        self.app.aboutToQuit.connect(self.cleanup_consoles)


# -----------------------------------------------------------------------------
# Main script
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    app = QApplication([])
    # Create our window
    win = SimpleWindow(app)
    win.show()

    # Very important, IPython-specific step: this gets GUI event loop
    # integration going, and it replaces calling app.exec_()
    win.ipkernel.start()

patstew avatar Oct 14 '25 19:10 patstew