diff --git a/setup.py b/setup.py index 0925a5b05..e63f13e9f 100644 --- a/setup.py +++ b/setup.py @@ -233,7 +233,7 @@ def get_data_files(): 'async_generator', 'jupyter_server>=0.3.0', 'jupyter_client>=6.1.3', - 'nbclient>=0.2.0', + 'nbclient==0.4.0a1', 'nbconvert==6.0.0a3', 'jupyterlab_pygments>=0.1.0,<0.2', 'pygments>=2.4.1,<3' # Explicitly requiring pygments which is a second-order dependency. diff --git a/voila/execute.py b/voila/execute.py index 032196266..a8d1f4205 100644 --- a/voila/execute.py +++ b/voila/execute.py @@ -6,16 +6,13 @@ # # # The full license is in the file LICENSE, distributed with this software. # ############################################################################# -import collections import logging from nbconvert.preprocessors import ClearOutputPreprocessor from nbclient.exceptions import CellExecutionError from nbclient import NotebookClient -from nbformat.v4 import output_from_msg from traitlets import Unicode -from ipykernel.jsonutil import json_clean def strip_code_cell_warnings(cell): @@ -38,80 +35,6 @@ def should_strip_error(config): return 'Voila' not in config or 'log_level' not in config['Voila'] or config['Voila']['log_level'] != logging.DEBUG -class OutputWidget: - """This class mimics a front end output widget""" - def __init__(self, comm_id, state, kernel_client, executor): - self.comm_id = comm_id - self.state = state - self.kernel_client = kernel_client - self.executor = executor - self.topic = ('comm-%s' % self.comm_id).encode('ascii') - self.outputs = self.state['outputs'] - self.clear_before_next_output = False - - def clear_output(self, outs, msg, cell_index): - self.parent_header = msg['parent_header'] - content = msg['content'] - if content.get('wait'): - self.clear_before_next_output = True - else: - self.outputs = [] - # sync back the state to the kernel - self.sync_state() - if hasattr(self.executor, 'widget_state'): - # sync the state to the nbconvert state as well, since that is used for testing - self.executor.widget_state[self.comm_id]['outputs'] = self.outputs - - def sync_state(self): - state = {'outputs': self.outputs} - msg = {'method': 'update', 'state': state, 'buffer_paths': []} - self.send(msg) - - def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): - """Helper for sending a comm message on IOPub""" - data = {} if data is None else data - metadata = {} if metadata is None else metadata - content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) - msg = self.kernel_client.session.msg(msg_type, content=content, parent=self.parent_header, metadata=metadata) - self.kernel_client.shell_channel.send(msg) - - def send(self, data=None, metadata=None, buffers=None): - self._publish_msg('comm_msg', data=data, metadata=metadata, buffers=buffers) - - def output(self, outs, msg, display_id, cell_index): - if self.clear_before_next_output: - self.outputs = [] - self.clear_before_next_output = False - self.parent_header = msg['parent_header'] - output = output_from_msg(msg) - - if self.outputs: - # try to coalesce/merge output text - last_output = self.outputs[-1] - if (last_output['output_type'] == 'stream' and - output['output_type'] == 'stream' and - last_output['name'] == output['name']): - last_output['text'] += output['text'] - else: - self.outputs.append(output) - else: - self.outputs.append(output) - self.sync_state() - if hasattr(self.executor, 'widget_state'): - # sync the state to the nbconvert state as well, since that is used for testing - self.executor.widget_state[self.comm_id]['outputs'] = self.outputs - - def set_state(self, state): - if 'msg_id' in state: - msg_id = state.get('msg_id') - if msg_id: - self.executor.register_output_hook(msg_id, self) - self.msg_id = msg_id - else: - self.executor.remove_output_hook(self.msg_id, self) - self.msg_id = msg_id - - class VoilaExecutor(NotebookClient): """Execute, but respect the output widget behaviour""" cell_error_instruction = Unicode( @@ -132,8 +55,6 @@ class VoilaExecutor(NotebookClient): def __init__(self, nb, km=None, **kwargs): super(VoilaExecutor, self).__init__(nb, km=km, **kwargs) - self.output_hook_stack = collections.defaultdict(list) # maps to list of hooks, where the last is used - self.output_objects = {} def execute(self, nb, resources, km=None): try: @@ -166,52 +87,6 @@ async def execute_cell(self, cell, resources, cell_index, store_history=True): return result - def register_output_hook(self, msg_id, hook): - # mimics - # https://jupyterlab.github.io/jupyterlab/services/interfaces/kernel.ikernelconnection.html#registermessagehook - self.output_hook_stack[msg_id].append(hook) - - def remove_output_hook(self, msg_id, hook): - # mimics - # https://jupyterlab.github.io/jupyterlab/services/interfaces/kernel.ikernelconnection.html#removemessagehook - removed_hook = self.output_hook_stack[msg_id].pop() - assert removed_hook == hook - - def output(self, outs, msg, display_id, cell_index): - parent_msg_id = msg['parent_header'].get('msg_id') - if self.output_hook_stack[parent_msg_id]: - hook = self.output_hook_stack[parent_msg_id][-1] - hook.output(outs, msg, display_id, cell_index) - return - super(VoilaExecutor, self).output(outs, msg, display_id, cell_index) - - def handle_comm_msg(self, outs, msg, cell_index): - super(VoilaExecutor, self).handle_comm_msg(outs, msg, cell_index) - self.log.debug('comm msg: %r', msg) - if msg['msg_type'] == 'comm_open' and msg['content'].get('target_name') == 'jupyter.widget': - content = msg['content'] - data = content['data'] - state = data['state'] - comm_id = msg['content']['comm_id'] - if state['_model_module'] == '@jupyter-widgets/output' and state['_model_name'] == 'OutputModel': - self.output_objects[comm_id] = OutputWidget(comm_id, state, self.kc, self) - elif msg['msg_type'] == 'comm_msg': - content = msg['content'] - data = content['data'] - if 'state' in data: - state = data['state'] - comm_id = msg['content']['comm_id'] - if comm_id in self.output_objects: - self.output_objects[comm_id].set_state(state) - - def clear_output(self, outs, msg, cell_index): - parent_msg_id = msg['parent_header'].get('msg_id') - if self.output_hook_stack[parent_msg_id]: - hook = self.output_hook_stack[parent_msg_id][-1] - hook.clear_output(outs, msg, cell_index) - return - super(VoilaExecutor, self).clear_output(outs, msg, cell_index) - def strip_notebook_errors(self, nb): """Strip error messages and traceback from a Notebook.""" cells = nb['cells']