Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

save widgets after execute #779

Closed
wants to merge 6 commits into from
@@ -3,7 +3,7 @@

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

import base64
from textwrap import dedent

try:
@@ -273,12 +273,29 @@ def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
cwd=path)
self.kc.allow_stdin = False
self.nb = nb
self.widget_state = {}
self.widget_buffers = {}

try:
nb, resources = super(ExecutePreprocessor, self).preprocess(nb, resources)
finally:
self.kc.stop_channels()
self.km.shutdown_kernel(now=self.shutdown_kernel == 'immediate')
if self.widget_state:

This comment has been minimized.

Copy link
@mpacer

mpacer Mar 19, 2018

Member

Can you isolate this functionality in a separate method?

self.nb.metadata.widgets = {
'application/vnd.jupyter.widget-state+json': {
'state': {
model_id: _serialize_widget_state(state)
for model_id, state in self.widget_state.items() if '_model_name' in state
},
'version_major': 2,
'version_minor': 0,
}
}
for key, widget in self.nb.metadata.widgets['application/vnd.jupyter.widget-state+json']['state'].items():
buffers = self.widget_buffers.get(key)
if buffers:
widget['buffers'] = buffers

delattr(self, 'nb')

@@ -409,6 +426,10 @@ def run_cell(self, cell, cell_index=0):
cell_map[cell_index] = []
continue
elif msg_type.startswith('comm'):
data = content['data']
self.widget_state.setdefault(content['comm_id'], {}).update(data['state'])
if 'buffer_paths' in data and data['buffer_paths']:
self.widget_buffers[content['comm_id']] = _get_buffer_data(msg)
continue

display_id = None
@@ -457,3 +478,28 @@ def executenb(nb, cwd=None, **kwargs):
resources['metadata'] = {'path': cwd}
ep = ExecutePreprocessor(**kwargs)
return ep.preprocess(nb, resources)[0]


def _serialize_widget_state(state):
"""Serialize a widget state, following format in @jupyter-widgets/schema.
TODO: Does not currently split binary buffers or remove default values.
"""
return {
'model_name': state.get('_model_name'),
'model_module': state.get('_model_module'),
'model_module_version': state.get('_model_module_version'),
'state': state,
}


def _get_buffer_data(msg):
buffers = []
path = msg['content']['data']['buffer_paths']
for buffer in msg['buffers']:
buffers.append({
'data': base64.b64encode(buffer.obj).decode('utf-8'),
'encoding': 'base64',
'path': path
})
return buffers
@@ -0,0 +1,47 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "c3f75dd69f3e4602989aaa3711d2977d",
"version_major": 2,
"version_minor": 0
},
"text/html": [
"<p>Failed to display Jupyter Widget of type <code>Label</code>.</p>\n",
"<p>\n",
" If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n",
" that the widgets JavaScript is still loading. If this message persists, it\n",
" likely means that the widgets JavaScript library is either not installed or\n",
" not enabled. See the <a href=\"https://ipywidgets.readthedocs.io/en/stable/user_install.html\">Jupyter\n",
" Widgets Documentation</a> for setup instructions.\n",
"</p>\n",
"<p>\n",
" If you're reading this message in another frontend (for example, a static\n",
" rendering on GitHub or <a href=\"https://nbviewer.jupyter.org/\">NBViewer</a>),\n",
" it may mean that your frontend doesn't currently support widgets.\n",
"</p>\n"
],
"text/plain": [
"Label(value='Hello World')"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import ipywidgets\n",
"ipywidgets.Label('Hello World')"
]
}
],
"metadata": {},

This comment has been minimized.

Copy link
@jasongrout

jasongrout Mar 19, 2018

Member

Where is the widget state for the widget view above?

"nbformat": 4,
"nbformat_minor": 2
}
@@ -54,6 +54,9 @@ def normalize_output(output):
if 'text/plain' in output.get('data', {}):
output['data']['text/plain'] = \
re.sub(addr_pat, '<HEXADDR>', output['data']['text/plain'])
if 'application/vnd.jupyter.widget-view+json' in output.get('data', {}):
output['data']['application/vnd.jupyter.widget-view+json'] \
['model_id'] = '<MODEL_ID>'
for key, value in output.get('data', {}).items():
if isinstance(value, string_types):
output['data'][key] = _normalize_base64(value)
@@ -270,3 +273,33 @@ def test_execute_function(self):
original = copy.deepcopy(input_nb)
executed = executenb(original, os.path.dirname(filename))
self.assert_notebooks_equal(original, executed)

def test_widgets(self):
"""Runs a test notebook with widgets and checks the widget state is saved."""
input_file = os.path.join(current_dir, 'files', 'widget-hello-world.ipynb')
opts = dict(kernel_name="python")
res = self.build_resources()
res['metadata']['path'] = os.path.dirname(input_file)
input_nb, output_nb = self.run_notebook(input_file, opts, res)

output_data = [
output.get('data', {})
for cell in output_nb['cells']
for output in cell['outputs']
]

model_ids = [
data['application/vnd.jupyter.widget-view+json']['model_id']
for data in output_data
if 'application/vnd.jupyter.widget-view+json' in data
]

wdata = output_nb['metadata']['widgets'] \
['application/vnd.jupyter.widget-state+json']
for k in model_ids:

This comment has been minimized.

Copy link
@mpacer

mpacer Mar 19, 2018

Member

Nice tests!

d = wdata['state'][k]
assert 'model_name' in d
assert 'model_module' in d
assert 'state' in d
assert 'version_major' in wdata
assert 'version_minor' in wdata
@@ -199,7 +199,7 @@ def run(self):
]

extra_requirements = {
'test': ['pytest', 'pytest-cov', 'ipykernel', 'jupyter_client>=4.2'],
'test': ['pytest', 'pytest-cov', 'ipykernel', 'jupyter_client>=4.2', 'ipywidgets'],

This comment has been minimized.

Copy link
@mpacer

mpacer Mar 19, 2018

Member

Does this need to be a specific version?

This comment has been minimized.

Copy link
@jasongrout

jasongrout Mar 19, 2018

Member

should probably be at least >=7

'serve': ['tornado>=4.0'],
'execute': ['jupyter_client>=4.2'],
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.