117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
|
from .jsonutil import json_clean
|
||
|
from nbformat.v4 import output_from_msg
|
||
|
from typing import Dict, List, Any, Optional
|
||
|
|
||
|
from jupyter_client.client import KernelClient
|
||
|
|
||
|
|
||
|
class OutputWidget:
|
||
|
"""This class mimics a front end output widget"""
|
||
|
def __init__(
|
||
|
self,
|
||
|
comm_id: str,
|
||
|
state: Dict[str, Any],
|
||
|
kernel_client: KernelClient,
|
||
|
executor) -> None:
|
||
|
|
||
|
self.comm_id: str = comm_id
|
||
|
self.state: Dict[str, Any] = state
|
||
|
self.kernel_client: KernelClient = kernel_client
|
||
|
self.executor = executor
|
||
|
self.topic: bytes = ('comm-%s' % self.comm_id).encode('ascii')
|
||
|
self.outputs: List = self.state['outputs']
|
||
|
self.clear_before_next_output: bool = False
|
||
|
|
||
|
def clear_output(
|
||
|
self,
|
||
|
outs: List,
|
||
|
msg: Dict,
|
||
|
cell_index: int) -> None:
|
||
|
|
||
|
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) -> None:
|
||
|
state = {'outputs': self.outputs}
|
||
|
msg = {'method': 'update', 'state': state, 'buffer_paths': []}
|
||
|
self.send(msg)
|
||
|
|
||
|
def _publish_msg(
|
||
|
self,
|
||
|
msg_type: str,
|
||
|
data: Optional[Dict] = None,
|
||
|
metadata: Optional[Dict] = None,
|
||
|
buffers: Optional[List] = None,
|
||
|
**keys) -> None:
|
||
|
"""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: Optional[Dict] = None,
|
||
|
metadata: Optional[Dict] = None,
|
||
|
buffers: Optional[List] = None) -> None:
|
||
|
|
||
|
self._publish_msg('comm_msg', data=data, metadata=metadata, buffers=buffers)
|
||
|
|
||
|
def output(
|
||
|
self,
|
||
|
outs: List,
|
||
|
msg: Dict,
|
||
|
display_id: str,
|
||
|
cell_index: int) -> None:
|
||
|
|
||
|
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: Dict) -> None:
|
||
|
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
|
||
|
|
||
|
def handle_msg(self, msg: Dict) -> None:
|
||
|
content = msg['content']
|
||
|
comm_id = content['comm_id']
|
||
|
assert comm_id == self.comm_id
|
||
|
data = content['data']
|
||
|
if 'state' in data:
|
||
|
self.set_state(data['state'])
|