From 4f49778ae8908a1a40effe67672dfa7da8d204fa Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 22 Mar 2017 12:03:18 -0400 Subject: [PATCH 1/2] Make the widget update message the same no matter the direction of syncing. --- ipywidgets/widgets/widget.py | 12 +++++------ jupyter-js-widgets/src/widget.ts | 11 +++++----- jupyter-widgets-schema/messages.md | 34 ++++++------------------------ 3 files changed, 18 insertions(+), 39 deletions(-) diff --git a/ipywidgets/widgets/widget.py b/ipywidgets/widgets/widget.py index 1f594a6b7d..fd789e170a 100644 --- a/ipywidgets/widgets/widget.py +++ b/ipywidgets/widgets/widget.py @@ -508,14 +508,12 @@ def _handle_msg(self, msg): data = msg['content']['data'] method = data['method'] - # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one. - if method == 'backbone': - if 'sync_data' in data: - # get binary buffers too - sync_data = data['sync_data'] + if method == 'update': + if 'state' in data: + state = data['state'] if 'buffer_paths' in data: - _put_buffers(sync_data, data['buffer_paths'], msg['buffers']) - self.set_state(sync_data) # handles all methods + _put_buffers(state, data['buffer_paths'], msg['buffers']) + self.set_state(state) # Handle a state request. elif method == 'request_state': diff --git a/jupyter-js-widgets/src/widget.ts b/jupyter-js-widgets/src/widget.ts index 63f1f1fbb2..9c48ce59a7 100644 --- a/jupyter-js-widgets/src/widget.ts +++ b/jupyter-js-widgets/src/widget.ts @@ -237,12 +237,13 @@ class WidgetModel extends Backbone.Model { if (msg.content.execution_state ==='idle') { // Send buffer if this message caused another message to be // throttled. + // TODO: make sure this is handled just like the original syncing. + // Right now, it doesn't take into account the new binary_paths key if (this.msg_buffer !== null && (this.get('msg_throttle') || 1) === this.pending_msgs) { var data = { - method: 'backbone', - sync_method: 'update', - sync_data: this.msg_buffer + method: 'update', + state: this.msg_buffer }; this.comm.send(data, callbacks); this.msg_buffer = null; @@ -398,8 +399,8 @@ class WidgetModel extends Backbone.Model { // on the python side the inverse happens var split = utils.remove_buffers(state); this.comm.send({ - method: 'backbone', - sync_data: split.state, + method: 'update', + state: split.state, buffer_paths: split.buffer_paths }, callbacks, {}, split.buffers); }).catch((error) => { diff --git a/jupyter-widgets-schema/messages.md b/jupyter-widgets-schema/messages.md index 6fbaadf9a7..a3545e8ec7 100644 --- a/jupyter-widgets-schema/messages.md +++ b/jupyter-widgets-schema/messages.md @@ -210,7 +210,7 @@ In order to display widgets in both the classic notebook and JupyterLab, ipywidg -# Widget messaging protocol, version 2 +# Widget messaging protocol, version 2 (not finalized) A Jupyter widget has both a frontend and kernel object communicating with each other using the `comm` messages provided by the Jupyter kernel messaging protocol. The primary communication that happens is synchronizing widget state, represented in the messages by a dictionary of key-value pairs. The Jupyter widget messaging protocol covers `comm` messages for the following actions: @@ -229,6 +229,8 @@ The `jupyter.widget.version` comm target and associated version validation messa While in version 1, binary buffers could only be top level attributes of the `state` object, now any item in the state can be a binary buffer. All binary buffers that are a descendant of the state object (in a nested object or list) will be removed from an object or replaced by null in a list. The 'path' of each binary buffer and its data are sent separately, so the state object can be reconstructed on the other side of the wire. This change was necessary to allow sending the data for a binary array plus its metadata (shape, type, masks) in one attribute. +The sync update event from the frontend to the kernel was restructured to have the same field names as the event from the kernel to the frontend, namely the method field is `'update'` and the state data is in the `state` attribute. + ## Implementating the Jupyter widgets protocol in the kernel In this section, we concentrate on implementing the Jupyter widget messaging protocol in the kernel. @@ -276,7 +278,7 @@ Symmetrically, when instantiating a widget in the kernel, the kernel widgets lib The type of widget to be instantiated in the frontend is determined by the `_model_name`, `_model_module` and `_model_module_version` keys in the state, which respectively stand for the name of the class that must be instantiated in the frontend, the JavaScript module where this class is defined, and a semver range for that module. See the [Model State](modelstate.md) documentation for the serialized state for core Jupyter widgets. -The initial state is split between values that are serializable with JSON (in the `data.state` dictionary), and binary values (represented in `data.buffer_paths`). +The state is split between values that are serializable with JSON (in the `data.state` dictionary), and binary values (represented in `data.buffer_paths`). The `data.state` value is a dictionary of widget state keys and values that can be serialized to JSON. @@ -284,9 +286,9 @@ Comm messages for state synchronization may contain binary buffers. The `data.bu ### State synchronization -#### Synchronizing from kernel to frontend: `update` +#### Synchronizing widget state: `update` -When a widget's state changes in the kernel, the changed state keys and values are sent to the frontend over the widget's comm channel using an `update` message: +When a widget's state changes in either the kernel or the frontend, the changed state keys and values are sent to the other side over the widget's comm channel using an `update` message: ``` { @@ -299,32 +301,10 @@ When a widget's state changes in the kernel, the changed state keys and values a } ``` -The `state` and `buffer_paths` are the same as in the `comm_open` case. +The `data.state` and `data.buffer_paths` values are the same as in the `comm_open` case. See the [Model state](modelstate.md) documentation for the attributes of core Jupyter widgets. -#### Synchronizing from frontend to kernel: `backbone` - -When a widget's state changes in the frontend, the changed keys are sent to the kernel over the widget's comm channel using a `backbone` message: - -``` -{ - 'comm_id' : 'u-u-i-d', - 'data' : { - 'method': 'backbone', - 'sync_data': { } - 'buffer_paths': [ ] - } -} -``` - -The state update is split between values that are serializable with JSON (in the `data.sync_data` dictionary), and binary values (represented in `data.buffer_keys`). - -The `data.sync_data` value is a dictionary of widget state keys and values that can be serialized to JSON. - -Comm messages for state synchronization may contain binary buffers. The `data.buffer_paths` value contains a list of 'paths' in the `data.sync_data` object corresponding to the binary buffers. For example, if `data.buffer_paths` is `[['x'], ['y', 'z', 0]]`, then the first binary buffer is the value of the `data.sync_data['x']` attribute and the second binary buffer is the value of the `data.sync_data['y']['z'][0]` state attribute. A path representing a list value (i.e., last index of the path is an integer) will be `null` in `data.sync_data`, and a path representing a dictionary key (i.e., last index of the path is a string) will not exist in `data.sync_data`. - - #### State requests: `request_state` When a frontend wants to request the full state of a widget, the frontend sends a `request_state` message: From e652dfcd3b61d2e1d702dac23122c9dfb191606a Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 22 Mar 2017 19:26:02 -0400 Subject: [PATCH 2/2] Make the throttled syncing be just like the non-throttled syncing. --- jupyter-js-widgets/src/widget.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/jupyter-js-widgets/src/widget.ts b/jupyter-js-widgets/src/widget.ts index 9c48ce59a7..25c7ccd8fd 100644 --- a/jupyter-js-widgets/src/widget.ts +++ b/jupyter-js-widgets/src/widget.ts @@ -234,21 +234,16 @@ class WidgetModel extends Backbone.Model { */ _handle_status(msg, callbacks) { if (this.comm !== undefined) { - if (msg.content.execution_state ==='idle') { + this.pending_msgs--; + if (msg.content.execution_state === 'idle') { // Send buffer if this message caused another message to be // throttled. - // TODO: make sure this is handled just like the original syncing. - // Right now, it doesn't take into account the new binary_paths key if (this.msg_buffer !== null && (this.get('msg_throttle') || 1) === this.pending_msgs) { - var data = { - method: 'update', - state: this.msg_buffer - }; - this.comm.send(data, callbacks); + this.send_sync_message(this.msg_buffer, this.msg_buffer_callbacks); this.msg_buffer = null; - } else { - --this.pending_msgs; + this.msg_buffer_callbacks = null; + this.pending_msgs++; } } }