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