Skip to content

Commit

Permalink
Save widget model state to notebook metadata
Browse files Browse the repository at this point in the history
Currently default values are not removed and binary buffers are not saved.

(updated to conform to widget state schema)
  • Loading branch information
ricklupton authored and maartenbreddels committed Mar 22, 2019
1 parent 12e148f commit ad77716
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 1 deletion.
14 changes: 14 additions & 0 deletions nbconvert/preprocessors/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ def run_cell(self, cell, cell_index=0):
continue
elif msg_type.startswith('comm'):
self.handle_comm_msg(outs, msg, cell_index)
self.widget_state[content['comm_id']] = content['data']['state']
continue

display_id = None
Expand Down Expand Up @@ -556,3 +557,16 @@ def executenb(nb, cwd=None, km=None, **kwargs):
resources['metadata'] = {'path': cwd}
ep = ExecutePreprocessor(**kwargs)
return ep.preprocess(nb, resources, km=km)[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['_model_name'],
'model_module': state['_model_module'],
'model_module_version': state.get('_model_module_version'),
'state': state,
}
47 changes: 47 additions & 0 deletions nbconvert/preprocessors/tests/files/widget-hello-world.ipynb
Original file line number Diff line number Diff line change
@@ -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": {},
"nbformat": 4,
"nbformat_minor": 2
}
33 changes: 33 additions & 0 deletions nbconvert/preprocessors/tests/test_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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)
Expand Down Expand Up @@ -305,3 +308,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:
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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def run(self):
jupyter_client_req = 'jupyter_client>=4.2'

extra_requirements = {
'test': ['pytest', 'pytest-cov', 'ipykernel', jupyter_client_req],
(??) 'test': ['pytest', 'pytest-cov', 'ipykernel', 'jupyter_client>=4.2'],
'serve': ['tornado>=4.0'],
'execute': [jupyter_client_req],
'docs': ['sphinx>=1.5.1',
Expand Down

0 comments on commit ad77716

Please sign in to comment.