##// END OF EJS Templates
Enable widget instanciation from front-end.
Jonathan Frederic -
Show More
@@ -1,224 +1,259 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/namespace"
9 9 ], function (_, Backbone, $, IPython) {
10 10 "use strict";
11 11 //--------------------------------------------------------------------
12 12 // WidgetManager class
13 13 //--------------------------------------------------------------------
14 14 var WidgetManager = function (comm_manager, notebook) {
15 15 // Public constructor
16 16 WidgetManager._managers.push(this);
17 17
18 18 // Attach a comm manager to the
19 19 this.keyboard_manager = notebook.keyboard_manager;
20 20 this.notebook = notebook;
21 21 this.comm_manager = comm_manager;
22 22 this._models = {}; /* Dictionary of model ids and model instances */
23 23
24 24 // Register with the comm manager.
25 25 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
26 26 };
27 27
28 28 //--------------------------------------------------------------------
29 29 // Class level
30 30 //--------------------------------------------------------------------
31 31 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
32 32 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
33 33 WidgetManager._managers = []; /* List of widget managers */
34 34
35 35 WidgetManager.register_widget_model = function (model_name, model_type) {
36 36 // Registers a widget model by name.
37 37 WidgetManager._model_types[model_name] = model_type;
38 38 };
39 39
40 40 WidgetManager.register_widget_view = function (view_name, view_type) {
41 41 // Registers a widget view by name.
42 42 WidgetManager._view_types[view_name] = view_type;
43 43 };
44 44
45 45 //--------------------------------------------------------------------
46 46 // Instance level
47 47 //--------------------------------------------------------------------
48 48 WidgetManager.prototype.display_view = function(msg, model) {
49 49 // Displays a view for a particular model.
50 50 var cell = this.get_msg_cell(msg.parent_header.msg_id);
51 51 if (cell === null) {
52 52 console.log("Could not determine where the display" +
53 53 " message was from. Widget will not be displayed");
54 54 } else {
55 55 var that = this;
56 56 this.create_view(model, {cell: cell, callback: function(view) {
57 57 that._handle_display_view(view);
58 58 if (cell.widget_subarea) {
59 59 cell.widget_subarea.append(view.$el);
60 60 }
61 61 view.trigger('displayed');
62 62 }});
63 63 }
64 64 };
65 65
66 66 WidgetManager.prototype._handle_display_view = function (view) {
67 67 // Have the IPython keyboard manager disable its event
68 68 // handling so the widget can capture keyboard input.
69 69 // Note, this is only done on the outer most widgets.
70 70 if (this.keyboard_manager) {
71 71 this.keyboard_manager.register_events(view.$el);
72 72
73 73 if (view.additional_elements) {
74 74 for (var i = 0; i < view.additional_elements.length; i++) {
75 75 this.keyboard_manager.register_events(view.additional_elements[i]);
76 76 }
77 77 }
78 78 }
79 79 };
80 80
81 81
82 82 WidgetManager.prototype.create_view = function(model, options) {
83 83 // Creates a view for a particular model.
84 84
85 85 var view_name = model.get('_view_name');
86 86 var view_mod = model.get('_view_module');
87 87 var errback = options.errback || function(err) {console.log(err);};
88 88
89 89 var instantiate_view = function(ViewType) {
90 90 if (ViewType) {
91 91 // If a view is passed into the method, use that view's cell as
92 92 // the cell for the view that is created.
93 93 options = options || {};
94 94 if (options.parent !== undefined) {
95 95 options.cell = options.parent.options.cell;
96 96 }
97 97
98 98 // Create and render the view...
99 99 var parameters = {model: model, options: options};
100 100 var view = new ViewType(parameters);
101 101 view.render();
102 102 model.on('destroy', view.remove, view);
103 103 options.callback(view);
104 104 } else {
105 105 errback({unknown_view: true, view_name: view_name,
106 106 view_module: view_mod});
107 107 }
108 108 };
109 109
110 110 if (view_mod) {
111 111 require([view_mod], function(module) {
112 112 instantiate_view(module[view_name]);
113 113 }, errback);
114 114 } else {
115 115 instantiate_view(WidgetManager._view_types[view_name]);
116 116 }
117 117 };
118 118
119 119 WidgetManager.prototype.get_msg_cell = function (msg_id) {
120 120 var cell = null;
121 121 // First, check to see if the msg was triggered by cell execution.
122 122 if (this.notebook) {
123 123 cell = this.notebook.get_msg_cell(msg_id);
124 124 }
125 125 if (cell !== null) {
126 126 return cell;
127 127 }
128 128 // Second, check to see if a get_cell callback was defined
129 129 // for the message. get_cell callbacks are registered for
130 130 // widget messages, so this block is actually checking to see if the
131 131 // message was triggered by a widget.
132 132 var kernel = this.comm_manager.kernel;
133 133 if (kernel) {
134 134 var callbacks = kernel.get_callbacks_for_msg(msg_id);
135 135 if (callbacks && callbacks.iopub &&
136 136 callbacks.iopub.get_cell !== undefined) {
137 137 return callbacks.iopub.get_cell();
138 138 }
139 139 }
140 140
141 141 // Not triggered by a cell or widget (no get_cell callback
142 142 // exists).
143 143 return null;
144 144 };
145 145
146 146 WidgetManager.prototype.callbacks = function (view) {
147 147 // callback handlers specific a view
148 148 var callbacks = {};
149 149 if (view && view.options.cell) {
150 150
151 151 // Try to get output handlers
152 152 var cell = view.options.cell;
153 153 var handle_output = null;
154 154 var handle_clear_output = null;
155 155 if (cell.output_area) {
156 156 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
157 157 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
158 158 }
159 159
160 160 // Create callback dict using what is known
161 161 var that = this;
162 162 callbacks = {
163 163 iopub : {
164 164 output : handle_output,
165 165 clear_output : handle_clear_output,
166 166
167 167 // Special function only registered by widget messages.
168 168 // Allows us to get the cell for a message so we know
169 169 // where to add widgets if the code requires it.
170 170 get_cell : function () {
171 171 return cell;
172 172 },
173 173 },
174 174 };
175 175 }
176 176 return callbacks;
177 177 };
178 178
179 179 WidgetManager.prototype.get_model = function (model_id) {
180 180 // Look-up a model instance by its id.
181 181 var model = this._models[model_id];
182 182 if (model !== undefined && model.id == model_id) {
183 183 return model;
184 184 }
185 185 return null;
186 186 };
187 187
188 188 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
189 189 // Handle when a comm is opened.
190 return this.create_model({model_name: msg.content.data.target_name, comm: comm});
191 };
192
193 WidgetManager.prototype.create_model = function (model_name, target_name) {
194 // Create and return a new widget model.
195 //
196 // Parameters
197 // ----------
198 // model_name: string
199 // Target name of the widget model to create.
200 // target_name: string
201 // Target name of the widget in the back-end.
202 return this._create_model({model_name: model_name, target_name: target_name});
203 };
204
205 WidgetManager.prototype._create_model = function (options) {
206 // Create and return a new widget model.
207 //
208 // Parameters
209 // ----------
210 // options: dictionary
211 // Dictionary of options with the following contents:
212 // model_name: string
213 // Target name of the widget model to create.
214 // target_name: (optional) string
215 // Target name of the widget in the back-end.
216 // comm: (optional) Comm
217
218 // Create a comm if it wasn't provided.
219 var comm = options.comm;
220 if (!comm) {
221 comm = this.comm_manager.new_comm('ipython.widget', {'target_name': options.target_name});
222 }
223
224 // Create and return a new model that is connected to the comm.
190 225 var that = this;
191 226
192 227 var instantiate_model = function(ModelType) {
193 228 var model_id = comm.comm_id;
194 229 var widget_model = new ModelType(that, model_id, comm);
195 widget_model.on('comm:close', function () {
230 widget_model.on('comm:close', function () {sss
196 231 delete that._models[model_id];
197 232 });
198 233 that._models[model_id] = widget_model;
199 234 };
200 235
201 236 var widget_type_name = msg.content.data.model_name;
202 237 var widget_module = msg.content.data.model_module;
203 238
204 239 if (widget_module) {
205 240 // Load the module containing the widget model
206 241 require([widget_module], function(mod) {
207 242 if (mod[widget_type_name]) {
208 243 instantiate_model(mod[widget_type_name]);
209 244 } else {
210 245 console.log("Error creating widget model: " + widget_type_name
211 246 + " not found in " + widget_module);
212 247 }
213 248 }, function(err) { console.log(err); });
214 249 } else {
215 250 // No module specified, load from the global models registry
216 251 instantiate_model(WidgetManager._model_types[widget_type_name]);
217 252 }
218 253 };
219 254
220 255 // Backwards compatability.
221 256 IPython.WidgetManager = WidgetManager;
222 257
223 258 return {'WidgetManager': WidgetManager};
224 259 });
@@ -1,454 +1,469 b''
1 1 """Base Widget class. Allows user to create widgets in the back-end that render
2 2 in the IPython notebook front-end.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from contextlib import contextmanager
16 16 import collections
17 17
18 18 from IPython.core.getipython import get_ipython
19 19 from IPython.kernel.comm import Comm
20 20 from IPython.config import LoggingConfigurable
21 from IPython.utils.importstring import import_item
21 22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 23 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 24 from IPython.utils.py3compat import string_types
24 25
25 26 #-----------------------------------------------------------------------------
26 27 # Classes
27 28 #-----------------------------------------------------------------------------
28 29 class CallbackDispatcher(LoggingConfigurable):
29 30 """A structure for registering and running callbacks"""
30 31 callbacks = List()
31 32
32 33 def __call__(self, *args, **kwargs):
33 34 """Call all of the registered callbacks."""
34 35 value = None
35 36 for callback in self.callbacks:
36 37 try:
37 38 local_value = callback(*args, **kwargs)
38 39 except Exception as e:
39 40 ip = get_ipython()
40 41 if ip is None:
41 42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
42 43 else:
43 44 ip.showtraceback()
44 45 else:
45 46 value = local_value if local_value is not None else value
46 47 return value
47 48
48 49 def register_callback(self, callback, remove=False):
49 50 """(Un)Register a callback
50 51
51 52 Parameters
52 53 ----------
53 54 callback: method handle
54 55 Method to be registered or unregistered.
55 56 remove=False: bool
56 57 Whether to unregister the callback."""
57 58
58 59 # (Un)Register the callback.
59 60 if remove and callback in self.callbacks:
60 61 self.callbacks.remove(callback)
61 62 elif not remove and callback not in self.callbacks:
62 63 self.callbacks.append(callback)
63 64
64 65 def _show_traceback(method):
65 66 """decorator for showing tracebacks in IPython"""
66 67 def m(self, *args, **kwargs):
67 68 try:
68 69 return(method(self, *args, **kwargs))
69 70 except Exception as e:
70 71 ip = get_ipython()
71 72 if ip is None:
72 73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
73 74 else:
74 75 ip.showtraceback()
75 76 return m
76 77
77 78 class Widget(LoggingConfigurable):
78 79 #-------------------------------------------------------------------------
79 80 # Class attributes
80 81 #-------------------------------------------------------------------------
81 82 _widget_construction_callback = None
82 83 widgets = {}
83 84
84 85 @staticmethod
85 86 def on_widget_constructed(callback):
86 87 """Registers a callback to be called when a widget is constructed.
87 88
88 89 The callback must have the following signature:
89 90 callback(widget)"""
90 91 Widget._widget_construction_callback = callback
91 92
92 93 @staticmethod
93 94 def _call_widget_constructed(widget):
94 95 """Static method, called when a widget is constructed."""
95 96 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
96 97 Widget._widget_construction_callback(widget)
97 98
99 @staticmethod
100 def handle_comm_opened(comm, msg):
101 """Static method, called when a widget is constructed."""
102 target_name = msg['content']['data']['target_name']
103 widget_class = import_item(target_name)
104 widget = widget_class(open_comm=False)
105 widget.set_comm(comm)
106
107
98 108 #-------------------------------------------------------------------------
99 109 # Traits
100 110 #-------------------------------------------------------------------------
101 111 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
102 112 in which to find _model_name. If empty, look in the global registry.""")
103 113 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
104 114 registered in the front-end to create and sync this widget with.""")
105 115 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
106 116 If empty, look in the global registry.""", sync=True)
107 117 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
108 118 to use to represent the widget.""", sync=True)
109 119 comm = Instance('IPython.kernel.comm.Comm')
110 120
111 121 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
112 122 front-end can send before receiving an idle msg from the back-end.""")
113 123
114 124 version = Int(0, sync=True, help="""Widget's version""")
115 125 keys = List()
116 126 def _keys_default(self):
117 127 return [name for name in self.traits(sync=True)]
118 128
119 129 _property_lock = Tuple((None, None))
120 130 _send_state_lock = Int(0)
121 131 _states_to_send = Set(allow_none=False)
122 132 _display_callbacks = Instance(CallbackDispatcher, ())
123 133 _msg_callbacks = Instance(CallbackDispatcher, ())
124 134
125 135 #-------------------------------------------------------------------------
126 136 # (Con/de)structor
127 137 #-------------------------------------------------------------------------
128 def __init__(self, **kwargs):
138 def __init__(self, open_comm=True, **kwargs):
129 139 """Public constructor"""
130 140 self._model_id = kwargs.pop('model_id', None)
131 141 super(Widget, self).__init__(**kwargs)
132 142
133 143 Widget._call_widget_constructed(self)
144 if open_comm:
134 145 self.open()
135 146
136 147 def __del__(self):
137 148 """Object disposal"""
138 149 self.close()
139 150
140 151 #-------------------------------------------------------------------------
141 152 # Properties
142 153 #-------------------------------------------------------------------------
143 154
144 155 def open(self):
145 156 """Open a comm to the frontend if one isn't already open."""
146 157 if self.comm is None:
147 158 args = dict(target_name='ipython.widget',
148 159 data={'model_name': self._model_name,
149 160 'model_module': self._model_module})
150 161 if self._model_id is not None:
151 162 args['comm_id'] = self._model_id
152 self.comm = Comm(**args)
163 self.set_comm(Comm(**args))
164
165 # first update
166 self.send_state()
167
168 def set_comm(self, comm):
169 """Set's the comm of the widget."""
170 self.comm = comm
153 171 self._model_id = self.model_id
154 172
155 173 self.comm.on_msg(self._handle_msg)
156 174 Widget.widgets[self.model_id] = self
157 175
158 # first update
159 self.send_state()
160
161 176 @property
162 177 def model_id(self):
163 178 """Gets the model id of this widget.
164 179
165 180 If a Comm doesn't exist yet, a Comm will be created automagically."""
166 181 return self.comm.comm_id
167 182
168 183 #-------------------------------------------------------------------------
169 184 # Methods
170 185 #-------------------------------------------------------------------------
171 186
172 187 def close(self):
173 188 """Close method.
174 189
175 190 Closes the underlying comm.
176 191 When the comm is closed, all of the widget views are automatically
177 192 removed from the front-end."""
178 193 if self.comm is not None:
179 194 Widget.widgets.pop(self.model_id, None)
180 195 self.comm.close()
181 196 self.comm = None
182 197
183 198 def send_state(self, key=None):
184 199 """Sends the widget state, or a piece of it, to the front-end.
185 200
186 201 Parameters
187 202 ----------
188 203 key : unicode, or iterable (optional)
189 204 A single property's name or iterable of property names to sync with the front-end.
190 205 """
191 206 self._send({
192 207 "method" : "update",
193 208 "state" : self.get_state(key=key)
194 209 })
195 210
196 211 def get_state(self, key=None):
197 212 """Gets the widget state, or a piece of it.
198 213
199 214 Parameters
200 215 ----------
201 216 key : unicode or iterable (optional)
202 217 A single property's name or iterable of property names to get.
203 218 """
204 219 if key is None:
205 220 keys = self.keys
206 221 elif isinstance(key, string_types):
207 222 keys = [key]
208 223 elif isinstance(key, collections.Iterable):
209 224 keys = key
210 225 else:
211 226 raise ValueError("key must be a string, an iterable of keys, or None")
212 227 state = {}
213 228 for k in keys:
214 229 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
215 230 value = getattr(self, k)
216 231 state[k] = f(value)
217 232 return state
218 233
219 234 def set_state(self, sync_data):
220 235 """Called when a state is received from the front-end."""
221 236 for name in self.keys:
222 237 if name in sync_data:
223 238 json_value = sync_data[name]
224 239 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
225 240 with self._lock_property(name, json_value):
226 241 setattr(self, name, from_json(json_value))
227 242
228 243 def send(self, content):
229 244 """Sends a custom msg to the widget model in the front-end.
230 245
231 246 Parameters
232 247 ----------
233 248 content : dict
234 249 Content of the message to send.
235 250 """
236 251 self._send({"method": "custom", "content": content})
237 252
238 253 def on_msg(self, callback, remove=False):
239 254 """(Un)Register a custom msg receive callback.
240 255
241 256 Parameters
242 257 ----------
243 258 callback: callable
244 259 callback will be passed two arguments when a message arrives::
245 260
246 261 callback(widget, content)
247 262
248 263 remove: bool
249 264 True if the callback should be unregistered."""
250 265 self._msg_callbacks.register_callback(callback, remove=remove)
251 266
252 267 def on_displayed(self, callback, remove=False):
253 268 """(Un)Register a widget displayed callback.
254 269
255 270 Parameters
256 271 ----------
257 272 callback: method handler
258 273 Must have a signature of::
259 274
260 275 callback(widget, **kwargs)
261 276
262 277 kwargs from display are passed through without modification.
263 278 remove: bool
264 279 True if the callback should be unregistered."""
265 280 self._display_callbacks.register_callback(callback, remove=remove)
266 281
267 282 #-------------------------------------------------------------------------
268 283 # Support methods
269 284 #-------------------------------------------------------------------------
270 285 @contextmanager
271 286 def _lock_property(self, key, value):
272 287 """Lock a property-value pair.
273 288
274 289 The value should be the JSON state of the property.
275 290
276 291 NOTE: This, in addition to the single lock for all state changes, is
277 292 flawed. In the future we may want to look into buffering state changes
278 293 back to the front-end."""
279 294 self._property_lock = (key, value)
280 295 try:
281 296 yield
282 297 finally:
283 298 self._property_lock = (None, None)
284 299
285 300 @contextmanager
286 301 def hold_sync(self):
287 302 """Hold syncing any state until the context manager is released"""
288 303 # We increment a value so that this can be nested. Syncing will happen when
289 304 # all levels have been released.
290 305 self._send_state_lock += 1
291 306 try:
292 307 yield
293 308 finally:
294 309 self._send_state_lock -=1
295 310 if self._send_state_lock == 0:
296 311 self.send_state(self._states_to_send)
297 312 self._states_to_send.clear()
298 313
299 314 def _should_send_property(self, key, value):
300 315 """Check the property lock (property_lock)"""
301 316 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
302 317 if (key == self._property_lock[0]
303 318 and to_json(value) == self._property_lock[1]):
304 319 return False
305 320 elif self._send_state_lock > 0:
306 321 self._states_to_send.add(key)
307 322 return False
308 323 else:
309 324 return True
310 325
311 326 # Event handlers
312 327 @_show_traceback
313 328 def _handle_msg(self, msg):
314 329 """Called when a msg is received from the front-end"""
315 330 data = msg['content']['data']
316 331 method = data['method']
317 332 if not method in ['backbone', 'custom']:
318 333 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
319 334
320 335 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
321 336 if method == 'backbone' and 'sync_data' in data:
322 337 sync_data = data['sync_data']
323 338 self.set_state(sync_data) # handles all methods
324 339
325 340 # Handle a custom msg from the front-end
326 341 elif method == 'custom':
327 342 if 'content' in data:
328 343 self._handle_custom_msg(data['content'])
329 344
330 345 def _handle_custom_msg(self, content):
331 346 """Called when a custom msg is received."""
332 347 self._msg_callbacks(self, content)
333 348
334 349 def _notify_trait(self, name, old_value, new_value):
335 350 """Called when a property has been changed."""
336 351 # Trigger default traitlet callback machinery. This allows any user
337 352 # registered validation to be processed prior to allowing the widget
338 353 # machinery to handle the state.
339 354 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
340 355
341 356 # Send the state after the user registered callbacks for trait changes
342 357 # have all fired (allows for user to validate values).
343 358 if self.comm is not None and name in self.keys:
344 359 # Make sure this isn't information that the front-end just sent us.
345 360 if self._should_send_property(name, new_value):
346 361 # Send new state to front-end
347 362 self.send_state(key=name)
348 363
349 364 def _handle_displayed(self, **kwargs):
350 365 """Called when a view has been displayed for this widget instance"""
351 366 self._display_callbacks(self, **kwargs)
352 367
353 368 def _trait_to_json(self, x):
354 369 """Convert a trait value to json
355 370
356 371 Traverse lists/tuples and dicts and serialize their values as well.
357 372 Replace any widgets with their model_id
358 373 """
359 374 if isinstance(x, dict):
360 375 return {k: self._trait_to_json(v) for k, v in x.items()}
361 376 elif isinstance(x, (list, tuple)):
362 377 return [self._trait_to_json(v) for v in x]
363 378 elif isinstance(x, Widget):
364 379 return "IPY_MODEL_" + x.model_id
365 380 else:
366 381 return x # Value must be JSON-able
367 382
368 383 def _trait_from_json(self, x):
369 384 """Convert json values to objects
370 385
371 386 Replace any strings representing valid model id values to Widget references.
372 387 """
373 388 if isinstance(x, dict):
374 389 return {k: self._trait_from_json(v) for k, v in x.items()}
375 390 elif isinstance(x, (list, tuple)):
376 391 return [self._trait_from_json(v) for v in x]
377 392 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
378 393 # we want to support having child widgets at any level in a hierarchy
379 394 # trusting that a widget UUID will not appear out in the wild
380 395 return Widget.widgets[x[10:]]
381 396 else:
382 397 return x
383 398
384 399 def _ipython_display_(self, **kwargs):
385 400 """Called when `IPython.display.display` is called on the widget."""
386 401 # Show view.
387 402 if self._view_name is not None:
388 403 self._send({"method": "display"})
389 404 self._handle_displayed(**kwargs)
390 405
391 406 def _send(self, msg):
392 407 """Sends a message to the model in the front-end."""
393 408 self.comm.send(msg)
394 409
395 410
396 411 class DOMWidget(Widget):
397 412 visible = Bool(True, help="Whether the widget is visible.", sync=True)
398 413 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
399 414 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
400 415
401 416 width = CUnicode(sync=True)
402 417 height = CUnicode(sync=True)
403 418 padding = CUnicode(sync=True)
404 419 margin = CUnicode(sync=True)
405 420
406 421 color = Unicode(sync=True)
407 422 background_color = Unicode(sync=True)
408 423 border_color = Unicode(sync=True)
409 424
410 425 border_width = CUnicode(sync=True)
411 426 border_radius = CUnicode(sync=True)
412 427 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
413 428 'none',
414 429 'hidden',
415 430 'dotted',
416 431 'dashed',
417 432 'solid',
418 433 'double',
419 434 'groove',
420 435 'ridge',
421 436 'inset',
422 437 'outset',
423 438 'initial',
424 439 'inherit', ''],
425 440 default_value='', sync=True)
426 441
427 442 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
428 443 'normal',
429 444 'italic',
430 445 'oblique',
431 446 'initial',
432 447 'inherit', ''],
433 448 default_value='', sync=True)
434 449 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
435 450 'normal',
436 451 'bold',
437 452 'bolder',
438 453 'lighter',
439 454 'initial',
440 455 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
441 456 default_value='', sync=True)
442 457 font_size = CUnicode(sync=True)
443 458 font_family = Unicode(sync=True)
444 459
445 460 def __init__(self, *pargs, **kwargs):
446 461 super(DOMWidget, self).__init__(*pargs, **kwargs)
447 462
448 463 def _validate_border(name, old, new):
449 464 if new is not None and new != '':
450 465 if name != 'border_width' and not self.border_width:
451 466 self.border_width = 1
452 467 if name != 'border_style' and self.border_style == '':
453 468 self.border_style = 'solid'
454 469 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
@@ -1,150 +1,156 b''
1 1 """Base class to manage comms"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import sys
7 7
8 8 from IPython.config import LoggingConfigurable
9 9 from IPython.core.prompts import LazyEvaluate
10 10 from IPython.core.getipython import get_ipython
11 11
12 12 from IPython.utils.importstring import import_item
13 13 from IPython.utils.py3compat import string_types
14 14 from IPython.utils.traitlets import Instance, Unicode, Dict, Any
15 15
16 16 from .comm import Comm
17 17
18 18
19 19 def lazy_keys(dikt):
20 20 """Return lazy-evaluated string representation of a dictionary's keys
21 21
22 22 Key list is only constructed if it will actually be used.
23 23 Used for debug-logging.
24 24 """
25 25 return LazyEvaluate(lambda d: list(d.keys()))
26 26
27 27
28 28 class CommManager(LoggingConfigurable):
29 29 """Manager for Comms in the Kernel"""
30 30
31 31 # If this is instantiated by a non-IPython kernel, shell will be None
32 32 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
33 33 allow_none=True)
34 34 kernel = Instance('IPython.kernel.zmq.kernelbase.Kernel')
35 35
36 36 iopub_socket = Any()
37 37 def _iopub_socket_default(self):
38 38 return self.kernel.iopub_socket
39 39 session = Instance('IPython.kernel.zmq.session.Session')
40 40 def _session_default(self):
41 41 return self.kernel.session
42 42
43 43 comms = Dict()
44 44 targets = Dict()
45 45
46 46 # Public APIs
47 47
48 48 def register_target(self, target_name, f):
49 49 """Register a callable f for a given target name
50 50
51 51 f will be called with two arguments when a comm_open message is received with `target`:
52 52
53 53 - the Comm instance
54 54 - the `comm_open` message itself.
55 55
56 56 f can be a Python callable or an import string for one.
57 57 """
58 58 if isinstance(f, string_types):
59 59 f = import_item(f)
60 60
61 61 self.targets[target_name] = f
62 62
63 63 def unregister_target(self, target_name, f):
64 64 """Unregister a callable registered with register_target"""
65 65 return self.targets.pop(target_name);
66 66
67 67 def register_comm(self, comm):
68 68 """Register a new comm"""
69 69 comm_id = comm.comm_id
70 70 comm.shell = self.shell
71 71 comm.kernel = self.kernel
72 72 comm.iopub_socket = self.iopub_socket
73 73 self.comms[comm_id] = comm
74 74 return comm_id
75 75
76 76 def unregister_comm(self, comm):
77 77 """Unregister a comm, and close its counterpart"""
78 78 # unlike get_comm, this should raise a KeyError
79 79 comm = self.comms.pop(comm.comm_id)
80 80
81 81 def get_comm(self, comm_id):
82 82 """Get a comm with a particular id
83 83
84 84 Returns the comm if found, otherwise None.
85 85
86 86 This will not raise an error,
87 87 it will log messages if the comm cannot be found.
88 88 """
89 89 if comm_id not in self.comms:
90 90 self.log.error("No such comm: %s", comm_id)
91 91 self.log.debug("Current comms: %s", lazy_keys(self.comms))
92 92 return
93 93 # call, because we store weakrefs
94 94 comm = self.comms[comm_id]
95 95 return comm
96 96
97 97 # Message handlers
98 98 def comm_open(self, stream, ident, msg):
99 99 """Handler for comm_open messages"""
100 100 content = msg['content']
101 101 comm_id = content['comm_id']
102 102 target_name = content['target_name']
103 103 f = self.targets.get(target_name, None)
104 104 comm = Comm(comm_id=comm_id,
105 105 shell=self.shell,
106 106 kernel=self.kernel,
107 107 iopub_socket=self.iopub_socket,
108 108 primary=False,
109 109 )
110 self.register_comm(comm)
110 111 if f is None:
111 112 self.log.error("No such comm target registered: %s", target_name)
112 comm.close()
113 return
113 else:
114 114 try:
115 115 f(comm, msg)
116 return
116 117 except Exception:
117 118 self.log.error("Exception opening comm with target: %s", target_name, exc_info=True)
119
120 # Failure.
121 try:
118 122 comm.close()
123 except:
124 pass # Eat errors, nomnomnom
119 125
120 126 def comm_msg(self, stream, ident, msg):
121 127 """Handler for comm_msg messages"""
122 128 content = msg['content']
123 129 comm_id = content['comm_id']
124 130 comm = self.get_comm(comm_id)
125 131 if comm is None:
126 132 # no such comm
127 133 return
128 134 try:
129 135 comm.handle_msg(msg)
130 136 except Exception:
131 137 self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True)
132 138
133 139 def comm_close(self, stream, ident, msg):
134 140 """Handler for comm_close messages"""
135 141 content = msg['content']
136 142 comm_id = content['comm_id']
137 143 comm = self.get_comm(comm_id)
138 144 if comm is None:
139 145 # no such comm
140 146 self.log.debug("No such comm to close: %s", comm_id)
141 147 return
142 148 del self.comms[comm_id]
143 149
144 150 try:
145 151 comm.handle_close(msg)
146 152 except Exception:
147 153 self.log.error("Exception handling comm_close for %s", comm_id, exc_info=True)
148 154
149 155
150 156 __all__ = ['CommManager']
General Comments 0
You need to be logged in to leave comments. Login now