Skip to content
This repository has been archived by the owner on Sep 1, 2022. It is now read-only.

Hybrid Elements Message Spec

Gino Bustelo edited this page May 10, 2016 · 2 revisions

!!! NEEDS UPDATING TO LATEST MESSAGES

As explained earlier, DeclarativeWidgets contains a set of elements that are referred to as Hybrid Elements and are made up of both a Browser and a Kernel component. This document goes into detail about the communication messages between the 2 sides. By adhering to the following spec, Kernel can add support for DeclarativeWidgets.

Requirements

The main requirement for supporting DeclarativeWidgets is for the Kernel to implement the Comm Protocol. This serves as the backbone for 2-way communication between the Browser and the Kernel.

Widget Instantiation

  • The frontend sends a comm_open message on the jupyter.widget channel containing a widget_class field:

      {
      	header: {
      		msg_type: "comm_open",
      		...
      	},
      	...
      	content: {
      		comm_id: "<COMM_ID>",
      		target_name: "ipython.widget",
      		data: {
      			widget_class: "<WIDGET_CLASS_NAME>"
      		}
      	}
      }
    

Upon receiving this message, the handler for target_name must create the appropriate instance based on the content of widget_class and provide that instance with access to the comm.

Function Widget

Initialization

  • The widget_class in the initial comm_open will be urth.widgets.widget_function.Function

  • A sync_data comm_msg will be sent from the frontend following the comm_open with a function_name field whose value is the name of the function to use. Extra options such as limit are also included in this message:

      { 
      	header: {
      		msg_type: "comm_msg",
      		...
      	},
      	...
      	content: {
      		data: {
      			method: "backbone",
      			sync_data: { 
      				function_name: "<FUNCTION_NAME>",
      				limit: <LIMIT>
      			}
      		},
      		...
      	}
      }
    
  • The backend should then send the function's signature, a mapping of argument name to properties (e.g. JavaScript type), in the following format:

      {
      	header: {
      		msg_type: "comm_msg",
      		...
      	},
      	content: {
      		data: {
      			method: "update"
      			state: {
      				argspec: {
      					"<ARG_NAME_1>": {
      						type: "<ARG_1_TYPE>",
      						required: true
      					},
      					...
      					"<ARG_NAME_N>": {
      						type: "<ARG_N_TYPE>"
      					}
      				}
      			}
      		}
      	},
      	...
      }
    

If a type cannot be mapped to a JavaScript type, the language implementation's type should be sent. The required field is set to true when the argument must be present for function invocation. For instance, the required field is not necessary for a parameter with a default argument.

  • Finally, the backend should send a status message. See the Status Messages section for a full description of the message format.

    • Send a status: ok message if the function name registration succeeded.
    • Send a status: error message if the function name registration failed, e.g. if no function with the given name has been declared, or if an error occurred while determining the signature.

Invocation

  • A custom comm_msg will be sent from the frontend with an event: "invoke" field, and an args: {...} field. The args field contains an object with mappings from argument name to argument value that should be used for the function invocation. The function to be invoked is the function whose name was sent during initialization in the function_name field.

      {
      	header: {
      		msg_type: "comm_msg",
      		...
      	},
      	...
      	content: {
      		data: {
      			method: "custom",
      			content: {
      				event: "invoke",
      				args: {
      					"<ARG_NAME_1>": "<ARG_VALUE_1>",
      					...
      					"<ARG_NAME_N>": "<ARG_VALUE_N>"
      				}
      			}
      		}
      	}
      }
    

Note that all argument values are sent as strings. It is up to the language implementation to convert argument values to their proper types if necessary.

  • The function's return value should be sent from the backend as a custom comm_msg with method: update. The message contains a state object with a result field that holds the function's return value.

      {	
      	header: {	
      		msg_type: "comm_msg",
      		...
      	},
      	...
      	content: {	
      		data: {
      			method: "update",
      			state: {
      				"result": <FUNCTION_RETURN_VALUE>
      			}
      		},
      		...
      	}
      }
    

Note that the language implementation should send a representation of the function return value that can be serialized as JSON.

  • Finally, the backend should send a status message. See the Status Messages section for a full description of the message format.

    • Send a status: ok message if the function invocation succeeded.
    • Send a status: error message if the function invocation failed.

DataFrame Widget

Initialization

  • The widget_class in the initial comm_open will be urth.widgets.widget_dataframe.DataFrame

  • A sync_data comm_msg will be sent from the frontend following the comm_open with a variable_name field whose value is the name of the data variable to use. Extra options such as limit are also included in this message:

      { 
      	header: {
      		msg_type: "comm_msg",
      		...
      	},
      	...
      	content: {
      		data: {
      			method: "backbone",
      			sync_data: { 
      				variable_name: "<FUNCTION_NAME>",
      				limit: <LIMIT>
      			}
      		},
      		...
      	}
      }
    
  • The backend should then send a status message. See the Status Messages section for a full description of the message format.

    • Send a status: ok message if the DataFrame name registration succeeded.
    • Send a status: error message if the DataFrame name registration failed, e.g. if no DataFrame with the given name has been declared.
  • The frontend will then send a sync message (see below)

Sync

  • A custom comm_msg will be sent from the frontend with an event: "sync" field. This signals the backend to send a representation of the current value of variable_name.

      {
      	header: {
      		msg_type: "comm_msg",
      		...
      	},
      	...
      	content: {
      		data: {
      			method: "custom",
      			content: {
      				event: "sync"
      			}
      		}
      	}
      }
    
  • A representation of variable_name's current value should be sent from the backend as a custom comm_msg with method: update. The message contains a state object with a value field that holds variable_name's value representation. By convention, the DataFrame's value is represented by columns, index, and data fields:

      {	
      	header: {	
      		msg_type: "comm_msg",
      		...
      	},
      	...
      	content: {	
      		data: {
      			method: "update",
      			state: {
      				"value": {
      					"columns": ["<COLNAME_1>", ..., "<COLNAME_N>"],
      					"index": ["<ROWNAME_1>", ..., "<ROWNAME_M>"],
      					"data": [[<data_11>, ..., <data_1n],
      							  ...
      							 [<data_m1>, ..., <data_mn]]
      				}
      			}
      		},
      		...
      	}
      }
    

Status Messages

Declarative widgets use status messages to communicate success or failure. For instance, a Function widget sends an status: ok message following a successful function invocation, and a DataFrame widget sends a status: error message given an invalid DataFrame variable name.

A status message is a custom comm_msg with method: update, and a state object with a status field representing the status:

	{	
		header: {	
			msg_type: "comm_msg",
			...
		},
		...
		content: {	
			data: {
				method: "update",
				state: {
					"status": {
						"status": "ok" | "error",
						"msg": success or error message string,
						"timestamp": long
					}
				}
			},
			...
		}
	}

Implementation Notes

Below are some key operations that will inevitably need to be supported by a backend implementation of Declarative Widgets.

Function Widget

  • Executing a function with a given name, using a mapping of argument names to argument values as strings. For instance, the Scala widgets contain the function:
def invokeFunction(funcName: String, args: Map[String, String]): Try[Any]
  • Obtaining the argument names and types of a function with a given name. For instance, the Scala widgets contain the function:
def argTypes(funcName: String): List[(Name, Type)]

DataFrame Widget

  • Retrieving the current value of a variable given the variable's name. For instance, the Scala widgets retrieve the value using a function provided by the Spark Kernel's Interpreter:
kernelInterpreter.read(dataFrameVariableName)