Skip to content

Commit 811ac3e

Browse files
committed
Use AsyncKernelManager from jupyter_client
1 parent 96148fd commit 811ac3e

File tree

4 files changed

+28
-23
lines changed

4 files changed

+28
-23
lines changed

nbclient/client.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,9 @@ class NotebookClient(LoggingConfigurable):
218218
@default('kernel_manager_class')
219219
def _kernel_manager_class_default(self):
220220
"""Use a dynamic default to avoid importing jupyter_client at startup"""
221-
from jupyter_client import KernelManager
221+
from jupyter_client import AsyncKernelManager
222222

223-
return KernelManager
223+
return AsyncKernelManager
224224

225225
_display_id_map = Dict(
226226
help=dedent(
@@ -317,8 +317,8 @@ async def start_new_kernel_client(self, **kwargs):
317317
----------
318318
kwargs :
319319
Any options for `self.kernel_manager_class.start_kernel()`. Because
320-
that defaults to KernelManager, this will likely include options
321-
accepted by `KernelManager.start_kernel()``, which includes `cwd`.
320+
that defaults to AsyncKernelManager, this will likely include options
321+
accepted by `AsyncKernelManager.start_kernel()``, which includes `cwd`.
322322
323323
Returns
324324
-------
@@ -332,15 +332,15 @@ async def start_new_kernel_client(self, **kwargs):
332332
if self.km.ipykernel and self.ipython_hist_file:
333333
self.extra_arguments += ['--HistoryManager.hist_file={}'.format(self.ipython_hist_file)]
334334

335-
self.km.start_kernel(extra_arguments=self.extra_arguments, **kwargs)
335+
await self.km.start_kernel(extra_arguments=self.extra_arguments, **kwargs)
336336

337337
self.kc = self.km.client()
338338
self.kc.start_channels()
339339
try:
340340
await self.kc.wait_for_ready(timeout=self.startup_timeout)
341341
except RuntimeError:
342342
self.kc.stop_channels()
343-
self.km.shutdown_kernel()
343+
await self.km.shutdown_kernel()
344344
raise
345345
self.kc.allow_stdin = False
346346
return self.kc
@@ -470,8 +470,8 @@ async def _poll_for_reply(self, msg_id, cell, timeout, task_poll_output_msg):
470470
timeout = max(0, deadline - monotonic())
471471
except Empty:
472472
# received no message, check if kernel is still alive
473-
self._check_alive()
474-
self._handle_timeout(timeout, cell)
473+
await self._check_alive()
474+
await self._handle_timeout(timeout, cell)
475475

476476
async def _poll_output_msg(self, parent_msg_id, cell, cell_index):
477477
while True:
@@ -494,18 +494,18 @@ def _get_timeout(self, cell):
494494

495495
return timeout
496496

497-
def _handle_timeout(self, timeout, cell=None):
497+
async def _handle_timeout(self, timeout, cell=None):
498498
self.log.error("Timeout waiting for execute reply (%is)." % timeout)
499499
if self.interrupt_on_timeout:
500500
self.log.error("Interrupting kernel")
501-
self.km.interrupt_kernel()
501+
await self.km.interrupt_kernel()
502502
else:
503503
raise CellTimeoutError.error_from_timeout_and_cell(
504504
"Cell execution timed out", timeout, cell
505505
)
506506

507-
def _check_alive(self):
508-
if not self.kc.is_alive():
507+
async def _check_alive(self):
508+
if not await self.kc.is_alive():
509509
self.log.error("Kernel died while waiting for execute reply.")
510510
raise DeadKernelError("Kernel died")
511511

@@ -518,10 +518,10 @@ async def _wait_for_reply(self, msg_id, cell=None):
518518
try:
519519
msg = await self.kc.shell_channel.get_msg(timeout=self.shell_timeout_interval)
520520
except Empty:
521-
self._check_alive()
521+
await self._check_alive()
522522
cummulative_time += self.shell_timeout_interval
523523
if timeout and cummulative_time > timeout:
524-
self._handle_timeout(timeout, cell)
524+
await self._handle_timeout(timeout, cell)
525525
break
526526
else:
527527
if msg['parent_header'].get('msg_id') == msg_id:
@@ -800,7 +800,7 @@ def execute(nb, cwd=None, km=None, **kwargs):
800800
The notebook object to be executed
801801
cwd : str, optional
802802
If supplied, the kernel will run in this directory
803-
km : KernelManager, optional
803+
km : AsyncKernelManager, optional
804804
If supplied, the specified kernel manager will be used for code execution.
805805
kwargs :
806806
Any other options for ExecutePreprocessor, e.g. timeout, kernel_name

nbclient/tests/fake_kernelmanager.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
from jupyter_client.manager import KernelManager
1+
from jupyter_client.manager import AsyncKernelManager
22

33

4-
class FakeCustomKernelManager(KernelManager):
4+
class FakeCustomKernelManager(AsyncKernelManager):
55
expected_methods = {'__init__': 0, 'client': 0, 'start_kernel': 0}
66

77
def __init__(self, *args, **kwargs):
88
self.log.info('FakeCustomKernelManager initialized')
99
self.expected_methods['__init__'] += 1
1010
super(FakeCustomKernelManager, self).__init__(*args, **kwargs)
1111

12-
def start_kernel(self, *args, **kwargs):
12+
async def start_kernel(self, *args, **kwargs):
1313
self.log.info('FakeCustomKernelManager started a kernel')
1414
self.expected_methods['start_kernel'] += 1
15-
return super(FakeCustomKernelManager, self).start_kernel(*args, **kwargs)
15+
return await super(FakeCustomKernelManager, self).start_kernel(*args, **kwargs)
1616

1717
def client(self, *args, **kwargs):
1818
self.log.info('FakeCustomKernelManager created a client')

nbclient/tests/test_client.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def prepared_wrapper(func):
144144
def test_mock_wrapper(self):
145145
"""
146146
This inner function wrapper populates the executor object with
147-
the fake kernel client. This client has it's iopub and shell
147+
the fake kernel client. This client has its iopub and shell
148148
channels mocked so as to fake the setup handshake and return
149149
the messages passed into prepare_cell_mocks as the execute_cell loop
150150
processes them.
@@ -161,6 +161,7 @@ def test_mock_wrapper(self):
161161
iopub_channel=MagicMock(get_msg=message_mock),
162162
shell_channel=MagicMock(get_msg=shell_channel_message_mock()),
163163
execute=MagicMock(return_value=parent_id),
164+
is_alive=MagicMock(return_value=make_async(True))
164165
)
165166
executor.parent_id = parent_id
166167
return func(self, executor, cell_mock, message_mock)
@@ -491,7 +492,7 @@ def test_kernel_death(self):
491492
km = executor.start_kernel_manager()
492493

493494
with patch.object(km, "is_alive") as alive_mock:
494-
alive_mock.return_value = False
495+
alive_mock.return_value = make_async(False)
495496
# Will be a RuntimeError or subclass DeadKernelError depending
496497
# on if jupyter_client or nbconvert catches the dead client first
497498
with pytest.raises(RuntimeError):
@@ -672,7 +673,11 @@ def test_busy_message(self, executor, cell_mock, message_mock):
672673
)
673674
def test_deadline_exec_reply(self, executor, cell_mock, message_mock):
674675
# exec_reply is never received, so we expect to hit the timeout.
675-
executor.kc.shell_channel.get_msg = MagicMock(side_effect=Empty())
676+
async def get_msg(timeout):
677+
await asyncio.sleep(timeout)
678+
raise Empty
679+
680+
executor.kc.shell_channel.get_msg = get_msg
676681
executor.timeout = 1
677682

678683
with pytest.raises(TimeoutError):

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
traitlets>=4.2
2-
jupyter_client>=6.0.0
2+
jupyter_client @ git+https://github.com/jupyter/jupyter_client@master
33
nbformat>=5.0
44
async_generator
55
nest_asyncio

0 commit comments

Comments
 (0)