-
Notifications
You must be signed in to change notification settings - Fork 3
/
pyside_kernel_widget.py
143 lines (107 loc) · 3.76 KB
/
pyside_kernel_widget.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
""" README:
- this is a proxy application for an actual qt application which wants to intergrate
jupyter
- code to execute:
from kernel_widget import get_kernel_widget
from PySide2.QtWidgets import QPushButton
button = QPushButton()
button.setText("black magic")
def say_hello():
print("hello from here")
button.clicked.connect(say_hello)
get_kernel_widget().layout.addWidget(button)
"""
import json
from pathlib import Path
import sys
import tempfile
import subprocess
import xqtpython
import socket
from contextlib import closing
from types import ModuleType
from PySide2.QtCore import QUrl, QTimer
from PySide2.QtWidgets import (
QApplication,
QVBoxLayout,
QWidget,
)
from PySide2.QtWebEngineWidgets import QWebEngineView
def find_free_port():
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.bind(("", 0))
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return s.getsockname()[1]
class KernelWidget(QWidget):
"""
A webview widget showing a jupyterlab instance
"""
def __init__(self, kernel_name, *args, **kwargs):
super(KernelWidget, self).__init__(*args, **kwargs)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.kernel_name = kernel_name
# browser
self.browser = QWebEngineView()
self.browser.setUrl(QUrl(""))
# create tempdir for kernel.json
self.kernel_file_dir = tempfile.TemporaryDirectory()
self.layout.addWidget(self.browser)
self.server_process = None
self._start_jupyverse()
self._make_widget_accessible()
# start this once the app is running
QTimer.singleShot(0, self._start_kernel)
def _make_widget_accessible(self):
# add a "virtual" module which makes
# this widget available to users
# in the nodebooks cell via
# from kernel_widget import get_kernel_widget
# get_kernel_widget()
m = ModuleType("kernel_widget")
sys.modules[m.__name__] = m
m.__file__ = m.__name__ + ".py"
def get_kernel_widget():
return self
m.get_kernel_widget = get_kernel_widget
def _start_kernel(self):
print("start kernel")
self.kernel = xqtpython.xkernel(
redirect_output_enabled=True,
redirect_display_enabled=True,
)
config = self.kernel.start()
for k in ["shell_port", "control_port", "stdin_port", "iopub_port", "hb_port"]:
config[k] = int(config[k])
config["kernel_name"] = self.kernel_name
kernel_file = Path(self.kernel_file_dir.name) / "kernel.json"
with open(kernel_file, "w") as f:
json.dump(config, f)
def _start_jupyverse(self):
# self.start_server_button.setDisabled(True)
self.server_port = find_free_port()
token = "my_token"
args = [
"jupyverse",
"--set", "kernels.allow_external_kernels=true",
"--set" ,f"kernels.external_connection_dir={str(self.kernel_file_dir.name)}",
"--set" ,f"auth.token={token}",
"--port", f"{self.server_port}",
]
self.server_process = subprocess.Popen(
args, shell=False
)
# we need to wait a tiny bit st the page is ready
def setUrl():
self.browser.setUrl(QUrl(f"http://127.0.0.1:{self.server_port}/?token={token}"))
QTimer.singleShot(2000, setUrl)
def closeEvent(self, event):
# do stuff
if self.server_process is not None:
self.server_process.terminate()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
kernel_widget = KernelWidget(kernel_name="qt-python")
kernel_widget.show()
app.exec_()