Skip to content

Commit e3de711

Browse files
committed
owpythonscript: Offer to run kernel in-process
1 parent ab2913c commit e3de711

File tree

3 files changed

+101
-44
lines changed

3 files changed

+101
-44
lines changed

Orange/widgets/data/owpythonscript.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from orangewidget.widget import Msg
1919
from qtconsole import styles
2020
from qtconsole.client import QtKernelClient
21+
from qtconsole.inprocess import QtInProcessKernelManager
2122
from qtconsole.manager import QtKernelManager
2223

2324
from AnyQt.QtWidgets import (
@@ -430,6 +431,7 @@ class Outputs:
430431
splitterState: Optional[bytes] = Setting(None)
431432

432433
vimModeEnabled = Setting(False)
434+
useInProcessKernel = Setting(False)
433435

434436
class Warning(OWWidget.Warning):
435437
illegal_var_type = Msg('{} should be of type {}, not {}.')
@@ -592,6 +594,14 @@ def _(color, text):
592594
self.vim_indicator.indicator_text = text
593595
self.vim_indicator.update()
594596

597+
# Kernel type
598+
599+
gui.checkBox(
600+
self.editor_controls, self, 'useInProcessKernel', 'Use in-process kernel',
601+
tooltip="Avoids initializing data, but freezes Orange during computation.",
602+
callback=self.init_kernel
603+
)
604+
595605
# Library
596606

597607
self.libraryListSource = []
@@ -704,9 +714,14 @@ def init_kernel(self):
704714
ident = str(uuid.uuid4()).split('-')[-1]
705715
cf = os.path.join(self._temp_connection_dir, 'kernel-%s.json' % ident)
706716

707-
self.kernel_manager = QtKernelManager(
708-
connection_file=cf
709-
)
717+
if self.useInProcessKernel:
718+
self.kernel_manager = QtInProcessKernelManager(
719+
connection_file=cf
720+
)
721+
else:
722+
self.kernel_manager = QtKernelManager(
723+
connection_file=cf
724+
)
710725

711726
self.kernel_manager.start_kernel(
712727
extra_arguments=[
@@ -723,9 +738,11 @@ def init_kernel(self):
723738
self.editor.kernel_manager = self.kernel_manager
724739
self.editor.kernel_client = self.kernel_client
725740
if self.console is not None:
741+
self.console.set_in_process(self.useInProcessKernel)
726742
self.console.kernel_manager = self.kernel_manager
727743
self.console.kernel_client = self.kernel_client
728744
self.console.set_kernel_id(ident)
745+
self.update_variables_in_console()
729746

730747
def shutdown_kernel(self):
731748
self.kernel_client.stop_channels()
@@ -815,7 +832,9 @@ def handleNewSignals(self):
815832
self.func_sig.update_signal_text({
816833
n: len(getattr(self, n)) for n in self.signal_names
817834
})
835+
self.update_variables_in_console()
818836

837+
def update_variables_in_console(self):
819838
self.set_status('Injecting variables...')
820839
vars = self.initial_locals_state()
821840
self.console.set_vars(vars)

Orange/widgets/data/utils/python_console.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import threading
77

88
from AnyQt.QtCore import Qt, Signal
9+
from Orange.widgets.data.utils.python_kernel import update_kernel_vars, collect_kernel_vars
910
from Orange.widgets.data.utils.python_serialize import OrangeZMQMixin
1011
from qtconsole.client import QtKernelClient
1112
from qtconsole.rich_jupyter_widget import RichJupyterWidget
@@ -33,6 +34,7 @@ class OrangeConsoleWidget(OrangeZMQMixin, RichJupyterWidget):
3334

3435
def __init__(self, *args, style_sheet='', **kwargs):
3536
super().__init__(*args, **kwargs)
37+
self.__is_in_process = False
3638
self.__is_ready = False
3739

3840
self.__queued_broadcast = None
@@ -43,19 +45,20 @@ def __init__(self, *args, style_sheet='', **kwargs):
4345
self.__broadcasting = False
4446
self.__threads = []
4547

46-
self.inject_vars_comm = None
47-
self.collect_vars_comm = None
48-
4948
self.style_sheet = style_sheet + \
5049
'.run-prompt { color: #aa22ff; }'
5150

51+
self.queue_init_client()
52+
53+
def queue_init_client(self):
5254
# Let the widget/kernel start up before trying to run a script,
5355
# by storing a queued execution payload when the widget's commit
5456
# method is invoked before <In [0]:> appears.
5557
@self.becomes_ready.connect
5658
def _():
5759
self.becomes_ready.disconnect(_) # reset callback
58-
self.init_client()
60+
if not self.is_in_process():
61+
self.init_client()
5962
self.becomes_ready.connect(self.__on_ready)
6063
self.__on_ready()
6164

@@ -78,6 +81,24 @@ def __run_queued_payload(self):
7881
self.__queued_execution = None
7982
self.run_script(*qe)
8083

84+
def set_in_process(self, enabled):
85+
if self.__is_in_process == enabled:
86+
return
87+
self.__is_in_process = enabled
88+
self.__is_ready = False
89+
self.__executing = False
90+
self.__broadcasting = False
91+
self.__prompt_num = 1
92+
93+
try:
94+
self.becomes_ready.disconnect(self.__on_ready)
95+
except:
96+
pass
97+
self.queue_init_client()
98+
99+
def is_in_process(self):
100+
return self.__is_in_process
101+
81102
def run_script(self, script):
82103
"""
83104
Inject the in vars, run the script,
@@ -127,7 +148,12 @@ def set_vars(self, vars):
127148
self.in_prompt = "Injecting variables..."
128149
self._update_prompt(self.__prompt_num)
129150

130-
super().set_vars(vars)
151+
if self.is_in_process():
152+
kernel = self.kernel_manager.kernel
153+
update_kernel_vars(kernel, vars, self.signals)
154+
self.on_variables_injected()
155+
else:
156+
super().set_vars(vars)
131157

132158
def on_variables_injected(self):
133159
log.debug('Cleared injecting variables')
@@ -186,6 +212,13 @@ def _handle_execute_reply(self, msg):
186212
self._update_prompt(self.__prompt_num)
187213
self.execution_finished.emit(True)
188214

215+
# collect variables manually, handle_new_vars will not be called
216+
if self.is_in_process():
217+
kernel = self.kernel_manager.kernel
218+
self.results_ready.emit(
219+
collect_kernel_vars(kernel, self.signals)
220+
)
221+
189222
# override
190223
def _handle_kernel_died(self, since_last_heartbeat):
191224
super()._handle_kernel_died(since_last_heartbeat)

Orange/widgets/data/utils/python_kernel.py

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,53 +11,58 @@
1111

1212
class OrangeIPythonKernel(OrangeZMQMixin, IPythonKernel):
1313

14-
signals = ("data", "learner", "classifier", "object")
15-
1614
def __init__(self, *args, **kwargs):
1715
super().__init__(*args, **kwargs)
18-
self.variables = defaultdict(list)
16+
self.variables = {}
1917
self.init_comms_kernel()
20-
self.handle_new_vars({})
2118

2219
def handle_new_vars(self, vars):
23-
default_vars = defaultdict(list)
24-
default_vars.update(vars)
25-
26-
input_vars = {}
27-
28-
for signal in self.signals:
29-
# remove old out_ vars
30-
out_name = 'out_' + signal
31-
if out_name in self.shell.user_ns:
32-
del self.shell.user_ns[out_name]
33-
self.shell.user_ns_hidden.pop(out_name, None)
34-
35-
if signal + 's' in vars and vars[signal + 's']:
36-
input_vars['in_' + signal + 's'] = vars[signal + 's']
37-
38-
# prepend script to set single signal values,
39-
# e.g. in_data = in_datas[0]
40-
input_vars['in_' + signal] = input_vars['in_' + signal + 's'][0]
41-
else:
42-
input_vars['in_' + signal] = None
43-
input_vars['in_' + signal + 's'] = []
44-
45-
self.shell.push(input_vars)
20+
input_vars = update_kernel_vars(self, vars, self.signals)
4621
self.variables.update(input_vars)
4722

4823
def execute_request(self, *args, **kwargs):
4924
result = super().execute_request(*args, **kwargs)
5025
if not self.is_initialized():
5126
return result
5227

53-
vars = defaultdict(list)
54-
for signal in self.signals:
55-
key = signal + 's'
56-
name = 'out_' + signal
57-
if name in self.shell.user_ns:
58-
var = self.shell.user_ns[name]
59-
vars[key].append(var)
60-
61-
self.set_vars(vars)
28+
variables = collect_kernel_vars(self, self.signals)
29+
prepared_variables = {
30+
k[4:] + 's': [v]
31+
for k, v in variables.items()
32+
}
33+
self.set_vars(prepared_variables)
6234

6335
return result
36+
37+
38+
def update_kernel_vars(kernel, vars, signals):
39+
input_vars = {}
40+
41+
for signal in signals:
42+
# remove old out_ vars
43+
out_name = 'out_' + signal
44+
if out_name in kernel.shell.user_ns:
45+
del kernel.shell.user_ns[out_name]
46+
kernel.shell.user_ns_hidden.pop(out_name, None)
47+
48+
if signal + 's' in vars and vars[signal + 's']:
49+
input_vars['in_' + signal + 's'] = vars[signal + 's']
50+
51+
# prepend script to set single signal values,
52+
# e.g. in_data = in_datas[0]
53+
input_vars['in_' + signal] = input_vars['in_' + signal + 's'][0]
54+
else:
55+
input_vars['in_' + signal] = None
56+
input_vars['in_' + signal + 's'] = []
57+
kernel.shell.push(input_vars)
58+
return input_vars
59+
60+
61+
def collect_kernel_vars(kernel, signals):
62+
variables = {}
63+
for signal in signals:
64+
name = 'out_' + signal
65+
if name in kernel.shell.user_ns:
66+
var = kernel.shell.user_ns[name]
67+
variables[name] = var
68+
return variables

0 commit comments

Comments
 (0)