##// END OF EJS Templates
Merge pull request #6216 from SylvainCorlay/comm-unregister...
Min RK -
r17480:dc95bffe merge
parent child Browse files
Show More
@@ -1,190 +1,190 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 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 //-----------------------------------------------------------------------
11 //-----------------------------------------------------------------------
12 // CommManager class
12 // CommManager class
13 //-----------------------------------------------------------------------
13 //-----------------------------------------------------------------------
14
14
15 var CommManager = function (kernel) {
15 var CommManager = function (kernel) {
16 this.comms = {};
16 this.comms = {};
17 this.targets = {};
17 this.targets = {};
18 if (kernel !== undefined) {
18 if (kernel !== undefined) {
19 this.init_kernel(kernel);
19 this.init_kernel(kernel);
20 }
20 }
21 };
21 };
22
22
23 CommManager.prototype.init_kernel = function (kernel) {
23 CommManager.prototype.init_kernel = function (kernel) {
24 // connect the kernel, and register message handlers
24 // connect the kernel, and register message handlers
25 this.kernel = kernel;
25 this.kernel = kernel;
26 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
26 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
27 for (var i = 0; i < msg_types.length; i++) {
27 for (var i = 0; i < msg_types.length; i++) {
28 var msg_type = msg_types[i];
28 var msg_type = msg_types[i];
29 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
29 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
30 }
30 }
31 };
31 };
32
32
33 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
33 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
34 // Create a new Comm, register it, and open its Kernel-side counterpart
34 // Create a new Comm, register it, and open its Kernel-side counterpart
35 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
35 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
36 var comm = new Comm(target_name);
36 var comm = new Comm(target_name);
37 this.register_comm(comm);
37 this.register_comm(comm);
38 comm.open(data, callbacks, metadata);
38 comm.open(data, callbacks, metadata);
39 return comm;
39 return comm;
40 };
40 };
41
41
42 CommManager.prototype.register_target = function (target_name, f) {
42 CommManager.prototype.register_target = function (target_name, f) {
43 // Register a target function for a given target name
43 // Register a target function for a given target name
44 this.targets[target_name] = f;
44 this.targets[target_name] = f;
45 };
45 };
46
46
47 CommManager.prototype.unregister_target = function (target_name, f) {
47 CommManager.prototype.unregister_target = function (target_name, f) {
48 // Unregister a target function for a given target name
48 // Unregister a target function for a given target name
49 delete this.targets[target_name];
49 delete this.targets[target_name];
50 };
50 };
51
51
52 CommManager.prototype.register_comm = function (comm) {
52 CommManager.prototype.register_comm = function (comm) {
53 // Register a comm in the mapping
53 // Register a comm in the mapping
54 this.comms[comm.comm_id] = comm;
54 this.comms[comm.comm_id] = comm;
55 comm.kernel = this.kernel;
55 comm.kernel = this.kernel;
56 return comm.comm_id;
56 return comm.comm_id;
57 };
57 };
58
58
59 CommManager.prototype.unregister_comm = function (comm_id) {
59 CommManager.prototype.unregister_comm = function (comm) {
60 // Remove a comm from the mapping
60 // Remove a comm from the mapping
61 delete this.comms[comm_id];
61 delete this.comms[comm.comm_id];
62 };
62 };
63
63
64 // comm message handlers
64 // comm message handlers
65
65
66 CommManager.prototype.comm_open = function (msg) {
66 CommManager.prototype.comm_open = function (msg) {
67 var content = msg.content;
67 var content = msg.content;
68 var f = this.targets[content.target_name];
68 var f = this.targets[content.target_name];
69 if (f === undefined) {
69 if (f === undefined) {
70 console.log("No such target registered: ", content.target_name);
70 console.log("No such target registered: ", content.target_name);
71 console.log("Available targets are: ", this.targets);
71 console.log("Available targets are: ", this.targets);
72 return;
72 return;
73 }
73 }
74 var comm = new Comm(content.target_name, content.comm_id);
74 var comm = new Comm(content.target_name, content.comm_id);
75 this.register_comm(comm);
75 this.register_comm(comm);
76 try {
76 try {
77 f(comm, msg);
77 f(comm, msg);
78 } catch (e) {
78 } catch (e) {
79 console.log("Exception opening new comm:", e, e.stack, msg);
79 console.log("Exception opening new comm:", e, e.stack, msg);
80 comm.close();
80 comm.close();
81 this.unregister_comm(comm);
81 this.unregister_comm(comm);
82 }
82 }
83 };
83 };
84
84
85 CommManager.prototype.comm_close = function (msg) {
85 CommManager.prototype.comm_close = function (msg) {
86 var content = msg.content;
86 var content = msg.content;
87 var comm = this.comms[content.comm_id];
87 var comm = this.comms[content.comm_id];
88 if (comm === undefined) {
88 if (comm === undefined) {
89 return;
89 return;
90 }
90 }
91 delete this.comms[content.comm_id];
91 this.unregister_comm(comm);
92 try {
92 try {
93 comm.handle_close(msg);
93 comm.handle_close(msg);
94 } catch (e) {
94 } catch (e) {
95 console.log("Exception closing comm: ", e, e.stack, msg);
95 console.log("Exception closing comm: ", e, e.stack, msg);
96 }
96 }
97 };
97 };
98
98
99 CommManager.prototype.comm_msg = function (msg) {
99 CommManager.prototype.comm_msg = function (msg) {
100 var content = msg.content;
100 var content = msg.content;
101 var comm = this.comms[content.comm_id];
101 var comm = this.comms[content.comm_id];
102 if (comm === undefined) {
102 if (comm === undefined) {
103 return;
103 return;
104 }
104 }
105 try {
105 try {
106 comm.handle_msg(msg);
106 comm.handle_msg(msg);
107 } catch (e) {
107 } catch (e) {
108 console.log("Exception handling comm msg: ", e, e.stack, msg);
108 console.log("Exception handling comm msg: ", e, e.stack, msg);
109 }
109 }
110 };
110 };
111
111
112 //-----------------------------------------------------------------------
112 //-----------------------------------------------------------------------
113 // Comm base class
113 // Comm base class
114 //-----------------------------------------------------------------------
114 //-----------------------------------------------------------------------
115
115
116 var Comm = function (target_name, comm_id) {
116 var Comm = function (target_name, comm_id) {
117 this.target_name = target_name;
117 this.target_name = target_name;
118 this.comm_id = comm_id || utils.uuid();
118 this.comm_id = comm_id || utils.uuid();
119 this._msg_callback = this._close_callback = null;
119 this._msg_callback = this._close_callback = null;
120 };
120 };
121
121
122 // methods for sending messages
122 // methods for sending messages
123 Comm.prototype.open = function (data, callbacks, metadata) {
123 Comm.prototype.open = function (data, callbacks, metadata) {
124 var content = {
124 var content = {
125 comm_id : this.comm_id,
125 comm_id : this.comm_id,
126 target_name : this.target_name,
126 target_name : this.target_name,
127 data : data || {},
127 data : data || {},
128 };
128 };
129 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
129 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
130 };
130 };
131
131
132 Comm.prototype.send = function (data, callbacks, metadata) {
132 Comm.prototype.send = function (data, callbacks, metadata) {
133 var content = {
133 var content = {
134 comm_id : this.comm_id,
134 comm_id : this.comm_id,
135 data : data || {},
135 data : data || {},
136 };
136 };
137 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata);
137 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata);
138 };
138 };
139
139
140 Comm.prototype.close = function (data, callbacks, metadata) {
140 Comm.prototype.close = function (data, callbacks, metadata) {
141 var content = {
141 var content = {
142 comm_id : this.comm_id,
142 comm_id : this.comm_id,
143 data : data || {},
143 data : data || {},
144 };
144 };
145 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
145 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
146 };
146 };
147
147
148 // methods for registering callbacks for incoming messages
148 // methods for registering callbacks for incoming messages
149 Comm.prototype._register_callback = function (key, callback) {
149 Comm.prototype._register_callback = function (key, callback) {
150 this['_' + key + '_callback'] = callback;
150 this['_' + key + '_callback'] = callback;
151 };
151 };
152
152
153 Comm.prototype.on_msg = function (callback) {
153 Comm.prototype.on_msg = function (callback) {
154 this._register_callback('msg', callback);
154 this._register_callback('msg', callback);
155 };
155 };
156
156
157 Comm.prototype.on_close = function (callback) {
157 Comm.prototype.on_close = function (callback) {
158 this._register_callback('close', callback);
158 this._register_callback('close', callback);
159 };
159 };
160
160
161 // methods for handling incoming messages
161 // methods for handling incoming messages
162
162
163 Comm.prototype._maybe_callback = function (key, msg) {
163 Comm.prototype._maybe_callback = function (key, msg) {
164 var callback = this['_' + key + '_callback'];
164 var callback = this['_' + key + '_callback'];
165 if (callback) {
165 if (callback) {
166 try {
166 try {
167 callback(msg);
167 callback(msg);
168 } catch (e) {
168 } catch (e) {
169 console.log("Exception in Comm callback", e, e.stack, msg);
169 console.log("Exception in Comm callback", e, e.stack, msg);
170 }
170 }
171 }
171 }
172 };
172 };
173
173
174 Comm.prototype.handle_msg = function (msg) {
174 Comm.prototype.handle_msg = function (msg) {
175 this._maybe_callback('msg', msg);
175 this._maybe_callback('msg', msg);
176 };
176 };
177
177
178 Comm.prototype.handle_close = function (msg) {
178 Comm.prototype.handle_close = function (msg) {
179 this._maybe_callback('close', msg);
179 this._maybe_callback('close', msg);
180 };
180 };
181
181
182 // For backwards compatability.
182 // For backwards compatability.
183 IPython.CommManager = CommManager;
183 IPython.CommManager = CommManager;
184 IPython.Comm = Comm;
184 IPython.Comm = Comm;
185
185
186 return {
186 return {
187 'CommManager': CommManager,
187 'CommManager': CommManager,
188 'Comm': Comm
188 'Comm': Comm
189 };
189 };
190 });
190 });
@@ -1,453 +1,449 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
16
17 from IPython.core.getipython import get_ipython
17 from IPython.core.getipython import get_ipython
18 from IPython.kernel.comm import Comm
18 from IPython.kernel.comm import Comm
19 from IPython.config import LoggingConfigurable
19 from IPython.config import LoggingConfigurable
20 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int
20 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int
21 from IPython.utils.py3compat import string_types
21 from IPython.utils.py3compat import string_types
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Classes
24 # Classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 class CallbackDispatcher(LoggingConfigurable):
26 class CallbackDispatcher(LoggingConfigurable):
27 """A structure for registering and running callbacks"""
27 """A structure for registering and running callbacks"""
28 callbacks = List()
28 callbacks = List()
29
29
30 def __call__(self, *args, **kwargs):
30 def __call__(self, *args, **kwargs):
31 """Call all of the registered callbacks."""
31 """Call all of the registered callbacks."""
32 value = None
32 value = None
33 for callback in self.callbacks:
33 for callback in self.callbacks:
34 try:
34 try:
35 local_value = callback(*args, **kwargs)
35 local_value = callback(*args, **kwargs)
36 except Exception as e:
36 except Exception as e:
37 ip = get_ipython()
37 ip = get_ipython()
38 if ip is None:
38 if ip is None:
39 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
39 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
40 else:
40 else:
41 ip.showtraceback()
41 ip.showtraceback()
42 else:
42 else:
43 value = local_value if local_value is not None else value
43 value = local_value if local_value is not None else value
44 return value
44 return value
45
45
46 def register_callback(self, callback, remove=False):
46 def register_callback(self, callback, remove=False):
47 """(Un)Register a callback
47 """(Un)Register a callback
48
48
49 Parameters
49 Parameters
50 ----------
50 ----------
51 callback: method handle
51 callback: method handle
52 Method to be registered or unregistered.
52 Method to be registered or unregistered.
53 remove=False: bool
53 remove=False: bool
54 Whether to unregister the callback."""
54 Whether to unregister the callback."""
55
55
56 # (Un)Register the callback.
56 # (Un)Register the callback.
57 if remove and callback in self.callbacks:
57 if remove and callback in self.callbacks:
58 self.callbacks.remove(callback)
58 self.callbacks.remove(callback)
59 elif not remove and callback not in self.callbacks:
59 elif not remove and callback not in self.callbacks:
60 self.callbacks.append(callback)
60 self.callbacks.append(callback)
61
61
62 def _show_traceback(method):
62 def _show_traceback(method):
63 """decorator for showing tracebacks in IPython"""
63 """decorator for showing tracebacks in IPython"""
64 def m(self, *args, **kwargs):
64 def m(self, *args, **kwargs):
65 try:
65 try:
66 return(method(self, *args, **kwargs))
66 return(method(self, *args, **kwargs))
67 except Exception as e:
67 except Exception as e:
68 ip = get_ipython()
68 ip = get_ipython()
69 if ip is None:
69 if ip is None:
70 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
70 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
71 else:
71 else:
72 ip.showtraceback()
72 ip.showtraceback()
73 return m
73 return m
74
74
75 class Widget(LoggingConfigurable):
75 class Widget(LoggingConfigurable):
76 #-------------------------------------------------------------------------
76 #-------------------------------------------------------------------------
77 # Class attributes
77 # Class attributes
78 #-------------------------------------------------------------------------
78 #-------------------------------------------------------------------------
79 _widget_construction_callback = None
79 _widget_construction_callback = None
80 widgets = {}
80 widgets = {}
81
81
82 @staticmethod
82 @staticmethod
83 def on_widget_constructed(callback):
83 def on_widget_constructed(callback):
84 """Registers a callback to be called when a widget is constructed.
84 """Registers a callback to be called when a widget is constructed.
85
85
86 The callback must have the following signature:
86 The callback must have the following signature:
87 callback(widget)"""
87 callback(widget)"""
88 Widget._widget_construction_callback = callback
88 Widget._widget_construction_callback = callback
89
89
90 @staticmethod
90 @staticmethod
91 def _call_widget_constructed(widget):
91 def _call_widget_constructed(widget):
92 """Static method, called when a widget is constructed."""
92 """Static method, called when a widget is constructed."""
93 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
93 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
94 Widget._widget_construction_callback(widget)
94 Widget._widget_construction_callback(widget)
95
95
96 #-------------------------------------------------------------------------
96 #-------------------------------------------------------------------------
97 # Traits
97 # Traits
98 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
99 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
99 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
100 registered in the front-end to create and sync this widget with.""")
100 registered in the front-end to create and sync this widget with.""")
101 _view_name = Unicode(help="""Default view registered in the front-end
101 _view_name = Unicode(help="""Default view registered in the front-end
102 to use to represent the widget.""", sync=True)
102 to use to represent the widget.""", sync=True)
103 _comm = Instance('IPython.kernel.comm.Comm')
103 _comm = Instance('IPython.kernel.comm.Comm')
104
104
105 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
105 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
106 front-end can send before receiving an idle msg from the back-end.""")
106 front-end can send before receiving an idle msg from the back-end.""")
107
107
108 keys = List()
108 keys = List()
109 def _keys_default(self):
109 def _keys_default(self):
110 return [name for name in self.traits(sync=True)]
110 return [name for name in self.traits(sync=True)]
111
111
112 _property_lock = Tuple((None, None))
112 _property_lock = Tuple((None, None))
113
113
114 _display_callbacks = Instance(CallbackDispatcher, ())
114 _display_callbacks = Instance(CallbackDispatcher, ())
115 _msg_callbacks = Instance(CallbackDispatcher, ())
115 _msg_callbacks = Instance(CallbackDispatcher, ())
116
116
117 #-------------------------------------------------------------------------
117 #-------------------------------------------------------------------------
118 # (Con/de)structor
118 # (Con/de)structor
119 #-------------------------------------------------------------------------
119 #-------------------------------------------------------------------------
120 def __init__(self, **kwargs):
120 def __init__(self, **kwargs):
121 """Public constructor"""
121 """Public constructor"""
122 super(Widget, self).__init__(**kwargs)
122 super(Widget, self).__init__(**kwargs)
123
123
124 self.on_trait_change(self._handle_property_changed, self.keys)
124 self.on_trait_change(self._handle_property_changed, self.keys)
125 Widget._call_widget_constructed(self)
125 Widget._call_widget_constructed(self)
126
126
127 def __del__(self):
127 def __del__(self):
128 """Object disposal"""
128 """Object disposal"""
129 self.close()
129 self.close()
130
130
131 #-------------------------------------------------------------------------
131 #-------------------------------------------------------------------------
132 # Properties
132 # Properties
133 #-------------------------------------------------------------------------
133 #-------------------------------------------------------------------------
134
134
135 @property
135 @property
136 def comm(self):
136 def comm(self):
137 """Gets the Comm associated with this widget.
137 """Gets the Comm associated with this widget.
138
138
139 If a Comm doesn't exist yet, a Comm will be created automagically."""
139 If a Comm doesn't exist yet, a Comm will be created automagically."""
140 if self._comm is None:
140 if self._comm is None:
141 # Create a comm.
141 # Create a comm.
142 self._comm = Comm(target_name=self._model_name)
142 self._comm = Comm(target_name=self._model_name)
143 self._comm.on_msg(self._handle_msg)
143 self._comm.on_msg(self._handle_msg)
144 self._comm.on_close(self._close)
145 Widget.widgets[self.model_id] = self
144 Widget.widgets[self.model_id] = self
146
145
147 # first update
146 # first update
148 self.send_state()
147 self.send_state()
149 return self._comm
148 return self._comm
150
149
151 @property
150 @property
152 def model_id(self):
151 def model_id(self):
153 """Gets the model id of this widget.
152 """Gets the model id of this widget.
154
153
155 If a Comm doesn't exist yet, a Comm will be created automagically."""
154 If a Comm doesn't exist yet, a Comm will be created automagically."""
156 return self.comm.comm_id
155 return self.comm.comm_id
157
156
158 #-------------------------------------------------------------------------
157 #-------------------------------------------------------------------------
159 # Methods
158 # Methods
160 #-------------------------------------------------------------------------
159 #-------------------------------------------------------------------------
161 def _close(self):
162 """Private close - cleanup objects, registry entries"""
163 del Widget.widgets[self.model_id]
164 self._comm = None
165
160
166 def close(self):
161 def close(self):
167 """Close method.
162 """Close method.
168
163
169 Closes the widget which closes the underlying comm.
164 Closes the underlying comm.
170 When the comm is closed, all of the widget views are automatically
165 When the comm is closed, all of the widget views are automatically
171 removed from the front-end."""
166 removed from the front-end."""
167 del Widget.widgets[self.model_id]
172 if self._comm is not None:
168 if self._comm is not None:
173 self._comm.close()
169 self._comm.close()
174 self._close()
170 self._comm = None
175
171
176 def send_state(self, key=None):
172 def send_state(self, key=None):
177 """Sends the widget state, or a piece of it, to the front-end.
173 """Sends the widget state, or a piece of it, to the front-end.
178
174
179 Parameters
175 Parameters
180 ----------
176 ----------
181 key : unicode (optional)
177 key : unicode (optional)
182 A single property's name to sync with the front-end.
178 A single property's name to sync with the front-end.
183 """
179 """
184 self._send({
180 self._send({
185 "method" : "update",
181 "method" : "update",
186 "state" : self.get_state()
182 "state" : self.get_state()
187 })
183 })
188
184
189 def get_state(self, key=None):
185 def get_state(self, key=None):
190 """Gets the widget state, or a piece of it.
186 """Gets the widget state, or a piece of it.
191
187
192 Parameters
188 Parameters
193 ----------
189 ----------
194 key : unicode (optional)
190 key : unicode (optional)
195 A single property's name to get.
191 A single property's name to get.
196 """
192 """
197 keys = self.keys if key is None else [key]
193 keys = self.keys if key is None else [key]
198 state = {}
194 state = {}
199 for k in keys:
195 for k in keys:
200 f = self.trait_metadata(k, 'to_json')
196 f = self.trait_metadata(k, 'to_json')
201 if f is None:
197 if f is None:
202 f = self._trait_to_json
198 f = self._trait_to_json
203 value = getattr(self, k)
199 value = getattr(self, k)
204 state[k] = f(value)
200 state[k] = f(value)
205 return state
201 return state
206
202
207 def send(self, content):
203 def send(self, content):
208 """Sends a custom msg to the widget model in the front-end.
204 """Sends a custom msg to the widget model in the front-end.
209
205
210 Parameters
206 Parameters
211 ----------
207 ----------
212 content : dict
208 content : dict
213 Content of the message to send.
209 Content of the message to send.
214 """
210 """
215 self._send({"method": "custom", "content": content})
211 self._send({"method": "custom", "content": content})
216
212
217 def on_msg(self, callback, remove=False):
213 def on_msg(self, callback, remove=False):
218 """(Un)Register a custom msg receive callback.
214 """(Un)Register a custom msg receive callback.
219
215
220 Parameters
216 Parameters
221 ----------
217 ----------
222 callback: callable
218 callback: callable
223 callback will be passed two arguments when a message arrives::
219 callback will be passed two arguments when a message arrives::
224
220
225 callback(widget, content)
221 callback(widget, content)
226
222
227 remove: bool
223 remove: bool
228 True if the callback should be unregistered."""
224 True if the callback should be unregistered."""
229 self._msg_callbacks.register_callback(callback, remove=remove)
225 self._msg_callbacks.register_callback(callback, remove=remove)
230
226
231 def on_displayed(self, callback, remove=False):
227 def on_displayed(self, callback, remove=False):
232 """(Un)Register a widget displayed callback.
228 """(Un)Register a widget displayed callback.
233
229
234 Parameters
230 Parameters
235 ----------
231 ----------
236 callback: method handler
232 callback: method handler
237 Must have a signature of::
233 Must have a signature of::
238
234
239 callback(widget, **kwargs)
235 callback(widget, **kwargs)
240
236
241 kwargs from display are passed through without modification.
237 kwargs from display are passed through without modification.
242 remove: bool
238 remove: bool
243 True if the callback should be unregistered."""
239 True if the callback should be unregistered."""
244 self._display_callbacks.register_callback(callback, remove=remove)
240 self._display_callbacks.register_callback(callback, remove=remove)
245
241
246 #-------------------------------------------------------------------------
242 #-------------------------------------------------------------------------
247 # Support methods
243 # Support methods
248 #-------------------------------------------------------------------------
244 #-------------------------------------------------------------------------
249 @contextmanager
245 @contextmanager
250 def _lock_property(self, key, value):
246 def _lock_property(self, key, value):
251 """Lock a property-value pair.
247 """Lock a property-value pair.
252
248
253 NOTE: This, in addition to the single lock for all state changes, is
249 NOTE: This, in addition to the single lock for all state changes, is
254 flawed. In the future we may want to look into buffering state changes
250 flawed. In the future we may want to look into buffering state changes
255 back to the front-end."""
251 back to the front-end."""
256 self._property_lock = (key, value)
252 self._property_lock = (key, value)
257 try:
253 try:
258 yield
254 yield
259 finally:
255 finally:
260 self._property_lock = (None, None)
256 self._property_lock = (None, None)
261
257
262 def _should_send_property(self, key, value):
258 def _should_send_property(self, key, value):
263 """Check the property lock (property_lock)"""
259 """Check the property lock (property_lock)"""
264 return key != self._property_lock[0] or \
260 return key != self._property_lock[0] or \
265 value != self._property_lock[1]
261 value != self._property_lock[1]
266
262
267 # Event handlers
263 # Event handlers
268 @_show_traceback
264 @_show_traceback
269 def _handle_msg(self, msg):
265 def _handle_msg(self, msg):
270 """Called when a msg is received from the front-end"""
266 """Called when a msg is received from the front-end"""
271 data = msg['content']['data']
267 data = msg['content']['data']
272 method = data['method']
268 method = data['method']
273 if not method in ['backbone', 'custom']:
269 if not method in ['backbone', 'custom']:
274 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
270 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
275
271
276 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
272 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
277 if method == 'backbone' and 'sync_data' in data:
273 if method == 'backbone' and 'sync_data' in data:
278 sync_data = data['sync_data']
274 sync_data = data['sync_data']
279 self._handle_receive_state(sync_data) # handles all methods
275 self._handle_receive_state(sync_data) # handles all methods
280
276
281 # Handle a custom msg from the front-end
277 # Handle a custom msg from the front-end
282 elif method == 'custom':
278 elif method == 'custom':
283 if 'content' in data:
279 if 'content' in data:
284 self._handle_custom_msg(data['content'])
280 self._handle_custom_msg(data['content'])
285
281
286 def _handle_receive_state(self, sync_data):
282 def _handle_receive_state(self, sync_data):
287 """Called when a state is received from the front-end."""
283 """Called when a state is received from the front-end."""
288 for name in self.keys:
284 for name in self.keys:
289 if name in sync_data:
285 if name in sync_data:
290 f = self.trait_metadata(name, 'from_json')
286 f = self.trait_metadata(name, 'from_json')
291 if f is None:
287 if f is None:
292 f = self._trait_from_json
288 f = self._trait_from_json
293 value = f(sync_data[name])
289 value = f(sync_data[name])
294 with self._lock_property(name, value):
290 with self._lock_property(name, value):
295 setattr(self, name, value)
291 setattr(self, name, value)
296
292
297 def _handle_custom_msg(self, content):
293 def _handle_custom_msg(self, content):
298 """Called when a custom msg is received."""
294 """Called when a custom msg is received."""
299 self._msg_callbacks(self, content)
295 self._msg_callbacks(self, content)
300
296
301 def _handle_property_changed(self, name, old, new):
297 def _handle_property_changed(self, name, old, new):
302 """Called when a property has been changed."""
298 """Called when a property has been changed."""
303 # Make sure this isn't information that the front-end just sent us.
299 # Make sure this isn't information that the front-end just sent us.
304 if self._should_send_property(name, new):
300 if self._should_send_property(name, new):
305 # Send new state to front-end
301 # Send new state to front-end
306 self.send_state(key=name)
302 self.send_state(key=name)
307
303
308 def _handle_displayed(self, **kwargs):
304 def _handle_displayed(self, **kwargs):
309 """Called when a view has been displayed for this widget instance"""
305 """Called when a view has been displayed for this widget instance"""
310 self._display_callbacks(self, **kwargs)
306 self._display_callbacks(self, **kwargs)
311
307
312 def _trait_to_json(self, x):
308 def _trait_to_json(self, x):
313 """Convert a trait value to json
309 """Convert a trait value to json
314
310
315 Traverse lists/tuples and dicts and serialize their values as well.
311 Traverse lists/tuples and dicts and serialize their values as well.
316 Replace any widgets with their model_id
312 Replace any widgets with their model_id
317 """
313 """
318 if isinstance(x, dict):
314 if isinstance(x, dict):
319 return {k: self._trait_to_json(v) for k, v in x.items()}
315 return {k: self._trait_to_json(v) for k, v in x.items()}
320 elif isinstance(x, (list, tuple)):
316 elif isinstance(x, (list, tuple)):
321 return [self._trait_to_json(v) for v in x]
317 return [self._trait_to_json(v) for v in x]
322 elif isinstance(x, Widget):
318 elif isinstance(x, Widget):
323 return "IPY_MODEL_" + x.model_id
319 return "IPY_MODEL_" + x.model_id
324 else:
320 else:
325 return x # Value must be JSON-able
321 return x # Value must be JSON-able
326
322
327 def _trait_from_json(self, x):
323 def _trait_from_json(self, x):
328 """Convert json values to objects
324 """Convert json values to objects
329
325
330 Replace any strings representing valid model id values to Widget references.
326 Replace any strings representing valid model id values to Widget references.
331 """
327 """
332 if isinstance(x, dict):
328 if isinstance(x, dict):
333 return {k: self._trait_from_json(v) for k, v in x.items()}
329 return {k: self._trait_from_json(v) for k, v in x.items()}
334 elif isinstance(x, (list, tuple)):
330 elif isinstance(x, (list, tuple)):
335 return [self._trait_from_json(v) for v in x]
331 return [self._trait_from_json(v) for v in x]
336 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
332 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
337 # we want to support having child widgets at any level in a hierarchy
333 # we want to support having child widgets at any level in a hierarchy
338 # trusting that a widget UUID will not appear out in the wild
334 # trusting that a widget UUID will not appear out in the wild
339 return Widget.widgets[x]
335 return Widget.widgets[x]
340 else:
336 else:
341 return x
337 return x
342
338
343 def _ipython_display_(self, **kwargs):
339 def _ipython_display_(self, **kwargs):
344 """Called when `IPython.display.display` is called on the widget."""
340 """Called when `IPython.display.display` is called on the widget."""
345 # Show view. By sending a display message, the comm is opened and the
341 # Show view. By sending a display message, the comm is opened and the
346 # initial state is sent.
342 # initial state is sent.
347 self._send({"method": "display"})
343 self._send({"method": "display"})
348 self._handle_displayed(**kwargs)
344 self._handle_displayed(**kwargs)
349
345
350 def _send(self, msg):
346 def _send(self, msg):
351 """Sends a message to the model in the front-end."""
347 """Sends a message to the model in the front-end."""
352 self.comm.send(msg)
348 self.comm.send(msg)
353
349
354
350
355 class DOMWidget(Widget):
351 class DOMWidget(Widget):
356 visible = Bool(True, help="Whether the widget is visible.", sync=True)
352 visible = Bool(True, help="Whether the widget is visible.", sync=True)
357 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
353 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
358
354
359 def get_css(self, key, selector=""):
355 def get_css(self, key, selector=""):
360 """Get a CSS property of the widget.
356 """Get a CSS property of the widget.
361
357
362 Note: This function does not actually request the CSS from the
358 Note: This function does not actually request the CSS from the
363 front-end; Only properties that have been set with set_css can be read.
359 front-end; Only properties that have been set with set_css can be read.
364
360
365 Parameters
361 Parameters
366 ----------
362 ----------
367 key: unicode
363 key: unicode
368 CSS key
364 CSS key
369 selector: unicode (optional)
365 selector: unicode (optional)
370 JQuery selector used when the CSS key/value was set.
366 JQuery selector used when the CSS key/value was set.
371 """
367 """
372 if selector in self._css and key in self._css[selector]:
368 if selector in self._css and key in self._css[selector]:
373 return self._css[selector][key]
369 return self._css[selector][key]
374 else:
370 else:
375 return None
371 return None
376
372
377 def set_css(self, dict_or_key, value=None, selector=''):
373 def set_css(self, dict_or_key, value=None, selector=''):
378 """Set one or more CSS properties of the widget.
374 """Set one or more CSS properties of the widget.
379
375
380 This function has two signatures:
376 This function has two signatures:
381 - set_css(css_dict, selector='')
377 - set_css(css_dict, selector='')
382 - set_css(key, value, selector='')
378 - set_css(key, value, selector='')
383
379
384 Parameters
380 Parameters
385 ----------
381 ----------
386 css_dict : dict
382 css_dict : dict
387 CSS key/value pairs to apply
383 CSS key/value pairs to apply
388 key: unicode
384 key: unicode
389 CSS key
385 CSS key
390 value:
386 value:
391 CSS value
387 CSS value
392 selector: unicode (optional, kwarg only)
388 selector: unicode (optional, kwarg only)
393 JQuery selector to use to apply the CSS key/value. If no selector
389 JQuery selector to use to apply the CSS key/value. If no selector
394 is provided, an empty selector is used. An empty selector makes the
390 is provided, an empty selector is used. An empty selector makes the
395 front-end try to apply the css to a default element. The default
391 front-end try to apply the css to a default element. The default
396 element is an attribute unique to each view, which is a DOM element
392 element is an attribute unique to each view, which is a DOM element
397 of the view that should be styled with common CSS (see
393 of the view that should be styled with common CSS (see
398 `$el_to_style` in the Javascript code).
394 `$el_to_style` in the Javascript code).
399 """
395 """
400 if value is None:
396 if value is None:
401 css_dict = dict_or_key
397 css_dict = dict_or_key
402 else:
398 else:
403 css_dict = {dict_or_key: value}
399 css_dict = {dict_or_key: value}
404
400
405 for (key, value) in css_dict.items():
401 for (key, value) in css_dict.items():
406 # First remove the selector/key pair from the css list if it exists.
402 # First remove the selector/key pair from the css list if it exists.
407 # Then add the selector/key pair and new value to the bottom of the
403 # Then add the selector/key pair and new value to the bottom of the
408 # list.
404 # list.
409 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
405 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
410 self._css += [(selector, key, value)]
406 self._css += [(selector, key, value)]
411 self.send_state('_css')
407 self.send_state('_css')
412
408
413 def add_class(self, class_names, selector=""):
409 def add_class(self, class_names, selector=""):
414 """Add class[es] to a DOM element.
410 """Add class[es] to a DOM element.
415
411
416 Parameters
412 Parameters
417 ----------
413 ----------
418 class_names: unicode or list
414 class_names: unicode or list
419 Class name(s) to add to the DOM element(s).
415 Class name(s) to add to the DOM element(s).
420 selector: unicode (optional)
416 selector: unicode (optional)
421 JQuery selector to select the DOM element(s) that the class(es) will
417 JQuery selector to select the DOM element(s) that the class(es) will
422 be added to.
418 be added to.
423 """
419 """
424 class_list = class_names
420 class_list = class_names
425 if isinstance(class_list, (list, tuple)):
421 if isinstance(class_list, (list, tuple)):
426 class_list = ' '.join(class_list)
422 class_list = ' '.join(class_list)
427
423
428 self.send({
424 self.send({
429 "msg_type" : "add_class",
425 "msg_type" : "add_class",
430 "class_list" : class_list,
426 "class_list" : class_list,
431 "selector" : selector
427 "selector" : selector
432 })
428 })
433
429
434 def remove_class(self, class_names, selector=""):
430 def remove_class(self, class_names, selector=""):
435 """Remove class[es] from a DOM element.
431 """Remove class[es] from a DOM element.
436
432
437 Parameters
433 Parameters
438 ----------
434 ----------
439 class_names: unicode or list
435 class_names: unicode or list
440 Class name(s) to remove from the DOM element(s).
436 Class name(s) to remove from the DOM element(s).
441 selector: unicode (optional)
437 selector: unicode (optional)
442 JQuery selector to select the DOM element(s) that the class(es) will
438 JQuery selector to select the DOM element(s) that the class(es) will
443 be removed from.
439 be removed from.
444 """
440 """
445 class_list = class_names
441 class_list = class_names
446 if isinstance(class_list, (list, tuple)):
442 if isinstance(class_list, (list, tuple)):
447 class_list = ' '.join(class_list)
443 class_list = ' '.join(class_list)
448
444
449 self.send({
445 self.send({
450 "msg_type" : "remove_class",
446 "msg_type" : "remove_class",
451 "class_list" : class_list,
447 "class_list" : class_list,
452 "selector" : selector,
448 "selector" : selector,
453 })
449 })
@@ -1,133 +1,135 b''
1 """Base class for a Comm"""
1 """Base class for a Comm"""
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 uuid
6 import uuid
7
7
8 from IPython.config import LoggingConfigurable
8 from IPython.config import LoggingConfigurable
9 from IPython.core.getipython import get_ipython
9 from IPython.core.getipython import get_ipython
10
10
11 from IPython.utils.jsonutil import json_clean
11 from IPython.utils.jsonutil import json_clean
12 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
12 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
13
13
14
14
15 class Comm(LoggingConfigurable):
15 class Comm(LoggingConfigurable):
16
16
17 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
17 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
18 def _shell_default(self):
18 def _shell_default(self):
19 return get_ipython()
19 return get_ipython()
20
20
21 iopub_socket = Any()
21 iopub_socket = Any()
22 def _iopub_socket_default(self):
22 def _iopub_socket_default(self):
23 return self.shell.kernel.iopub_socket
23 return self.shell.kernel.iopub_socket
24 session = Instance('IPython.kernel.zmq.session.Session')
24 session = Instance('IPython.kernel.zmq.session.Session')
25 def _session_default(self):
25 def _session_default(self):
26 if self.shell is None:
26 if self.shell is None:
27 return
27 return
28 return self.shell.kernel.session
28 return self.shell.kernel.session
29
29
30 target_name = Unicode('comm')
30 target_name = Unicode('comm')
31
31
32 topic = Bytes()
32 topic = Bytes()
33 def _topic_default(self):
33 def _topic_default(self):
34 return ('comm-%s' % self.comm_id).encode('ascii')
34 return ('comm-%s' % self.comm_id).encode('ascii')
35
35
36 _open_data = Dict(help="data dict, if any, to be included in comm_open")
36 _open_data = Dict(help="data dict, if any, to be included in comm_open")
37 _close_data = Dict(help="data dict, if any, to be included in comm_close")
37 _close_data = Dict(help="data dict, if any, to be included in comm_close")
38
38
39 _msg_callback = Any()
39 _msg_callback = Any()
40 _close_callback = Any()
40 _close_callback = Any()
41
41
42 _closed = Bool(False)
42 _closed = Bool(False)
43 comm_id = Unicode()
43 comm_id = Unicode()
44 def _comm_id_default(self):
44 def _comm_id_default(self):
45 return uuid.uuid4().hex
45 return uuid.uuid4().hex
46
46
47 primary = Bool(True, help="Am I the primary or secondary Comm?")
47 primary = Bool(True, help="Am I the primary or secondary Comm?")
48
48
49 def __init__(self, target_name='', data=None, **kwargs):
49 def __init__(self, target_name='', data=None, **kwargs):
50 if target_name:
50 if target_name:
51 kwargs['target_name'] = target_name
51 kwargs['target_name'] = target_name
52 super(Comm, self).__init__(**kwargs)
52 super(Comm, self).__init__(**kwargs)
53 get_ipython().comm_manager.register_comm(self)
54 if self.primary:
53 if self.primary:
55 # I am primary, open my peer.
54 # I am primary, open my peer.
56 self.open(data)
55 self.open(data)
57
56
58 def _publish_msg(self, msg_type, data=None, metadata=None, **keys):
57 def _publish_msg(self, msg_type, data=None, metadata=None, **keys):
59 """Helper for sending a comm message on IOPub"""
58 """Helper for sending a comm message on IOPub"""
60 data = {} if data is None else data
59 data = {} if data is None else data
61 metadata = {} if metadata is None else metadata
60 metadata = {} if metadata is None else metadata
62 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
61 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
63 self.session.send(self.iopub_socket, msg_type,
62 self.session.send(self.iopub_socket, msg_type,
64 content,
63 content,
65 metadata=json_clean(metadata),
64 metadata=json_clean(metadata),
66 parent=self.shell.get_parent(),
65 parent=self.shell.get_parent(),
67 ident=self.topic,
66 ident=self.topic,
68 )
67 )
69
68
70 def __del__(self):
69 def __del__(self):
71 """trigger close on gc"""
70 """trigger close on gc"""
72 self.close()
71 self.close()
73
72
74 # publishing messages
73 # publishing messages
75
74
76 def open(self, data=None, metadata=None):
75 def open(self, data=None, metadata=None):
77 """Open the frontend-side version of this comm"""
76 """Open the frontend-side version of this comm"""
78 if data is None:
77 if data is None:
79 data = self._open_data
78 data = self._open_data
79 self._closed = False
80 get_ipython().comm_manager.register_comm(self)
80 self._publish_msg('comm_open', data, metadata, target_name=self.target_name)
81 self._publish_msg('comm_open', data, metadata, target_name=self.target_name)
81
82
82 def close(self, data=None, metadata=None):
83 def close(self, data=None, metadata=None):
83 """Close the frontend-side version of this comm"""
84 """Close the frontend-side version of this comm"""
84 if self._closed:
85 if self._closed:
85 # only close once
86 # only close once
86 return
87 return
87 if data is None:
88 if data is None:
88 data = self._close_data
89 data = self._close_data
89 self._publish_msg('comm_close', data, metadata)
90 self._publish_msg('comm_close', data, metadata)
91 get_ipython().comm_manager.unregister_comm(self)
90 self._closed = True
92 self._closed = True
91
93
92 def send(self, data=None, metadata=None):
94 def send(self, data=None, metadata=None):
93 """Send a message to the frontend-side version of this comm"""
95 """Send a message to the frontend-side version of this comm"""
94 self._publish_msg('comm_msg', data, metadata)
96 self._publish_msg('comm_msg', data, metadata)
95
97
96 # registering callbacks
98 # registering callbacks
97
99
98 def on_close(self, callback):
100 def on_close(self, callback):
99 """Register a callback for comm_close
101 """Register a callback for comm_close
100
102
101 Will be called with the `data` of the close message.
103 Will be called with the `data` of the close message.
102
104
103 Call `on_close(None)` to disable an existing callback.
105 Call `on_close(None)` to disable an existing callback.
104 """
106 """
105 self._close_callback = callback
107 self._close_callback = callback
106
108
107 def on_msg(self, callback):
109 def on_msg(self, callback):
108 """Register a callback for comm_msg
110 """Register a callback for comm_msg
109
111
110 Will be called with the `data` of any comm_msg messages.
112 Will be called with the `data` of any comm_msg messages.
111
113
112 Call `on_msg(None)` to disable an existing callback.
114 Call `on_msg(None)` to disable an existing callback.
113 """
115 """
114 self._msg_callback = callback
116 self._msg_callback = callback
115
117
116 # handling of incoming messages
118 # handling of incoming messages
117
119
118 def handle_close(self, msg):
120 def handle_close(self, msg):
119 """Handle a comm_close message"""
121 """Handle a comm_close message"""
120 self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
122 self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
121 if self._close_callback:
123 if self._close_callback:
122 self._close_callback(msg)
124 self._close_callback(msg)
123
125
124 def handle_msg(self, msg):
126 def handle_msg(self, msg):
125 """Handle a comm_msg message"""
127 """Handle a comm_msg message"""
126 self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
128 self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
127 if self._msg_callback:
129 if self._msg_callback:
128 self.shell.events.trigger('pre_execute')
130 self.shell.events.trigger('pre_execute')
129 self._msg_callback(msg)
131 self._msg_callback(msg)
130 self.shell.events.trigger('post_execute')
132 self.shell.events.trigger('post_execute')
131
133
132
134
133 __all__ = ['Comm']
135 __all__ = ['Comm']
@@ -1,151 +1,148 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 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
31 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
32 def _shell_default(self):
32 def _shell_default(self):
33 return get_ipython()
33 return get_ipython()
34 iopub_socket = Any()
34 iopub_socket = Any()
35 def _iopub_socket_default(self):
35 def _iopub_socket_default(self):
36 return self.shell.kernel.iopub_socket
36 return self.shell.kernel.iopub_socket
37 session = Instance('IPython.kernel.zmq.session.Session')
37 session = Instance('IPython.kernel.zmq.session.Session')
38 def _session_default(self):
38 def _session_default(self):
39 if self.shell is None:
39 if self.shell is None:
40 return
40 return
41 return self.shell.kernel.session
41 return self.shell.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.iopub_socket = self.iopub_socket
71 comm.iopub_socket = self.iopub_socket
72 self.comms[comm_id] = comm
72 self.comms[comm_id] = comm
73 return comm_id
73 return comm_id
74
74
75 def unregister_comm(self, comm_id):
75 def unregister_comm(self, comm):
76 """Unregister a comm, and close its counterpart"""
76 """Unregister a comm, and close its counterpart"""
77 # unlike get_comm, this should raise a KeyError
77 # unlike get_comm, this should raise a KeyError
78 comm = self.comms.pop(comm_id)
78 comm = self.comms.pop(comm.comm_id)
79 comm.close()
80
79
81 def get_comm(self, comm_id):
80 def get_comm(self, comm_id):
82 """Get a comm with a particular id
81 """Get a comm with a particular id
83
82
84 Returns the comm if found, otherwise None.
83 Returns the comm if found, otherwise None.
85
84
86 This will not raise an error,
85 This will not raise an error,
87 it will log messages if the comm cannot be found.
86 it will log messages if the comm cannot be found.
88 """
87 """
89 if comm_id not in self.comms:
88 if comm_id not in self.comms:
90 self.log.error("No such comm: %s", comm_id)
89 self.log.error("No such comm: %s", comm_id)
91 self.log.debug("Current comms: %s", lazy_keys(self.comms))
90 self.log.debug("Current comms: %s", lazy_keys(self.comms))
92 return
91 return
93 # call, because we store weakrefs
92 # call, because we store weakrefs
94 comm = self.comms[comm_id]
93 comm = self.comms[comm_id]
95 return comm
94 return comm
96
95
97 # Message handlers
96 # Message handlers
98 def comm_open(self, stream, ident, msg):
97 def comm_open(self, stream, ident, msg):
99 """Handler for comm_open messages"""
98 """Handler for comm_open messages"""
100 content = msg['content']
99 content = msg['content']
101 comm_id = content['comm_id']
100 comm_id = content['comm_id']
102 target_name = content['target_name']
101 target_name = content['target_name']
103 f = self.targets.get(target_name, None)
102 f = self.targets.get(target_name, None)
104 comm = Comm(comm_id=comm_id,
103 comm = Comm(comm_id=comm_id,
105 shell=self.shell,
104 shell=self.shell,
106 iopub_socket=self.iopub_socket,
105 iopub_socket=self.iopub_socket,
107 primary=False,
106 primary=False,
108 )
107 )
109 if f is None:
108 if f is None:
110 self.log.error("No such comm target registered: %s", target_name)
109 self.log.error("No such comm target registered: %s", target_name)
111 comm.close()
110 comm.close()
112 return
111 return
113 self.register_comm(comm)
114 try:
112 try:
115 f(comm, msg)
113 f(comm, msg)
116 except Exception:
114 except Exception:
117 self.log.error("Exception opening comm with target: %s", target_name, exc_info=True)
115 self.log.error("Exception opening comm with target: %s", target_name, exc_info=True)
118 comm.close()
116 comm.close()
119 self.unregister_comm(comm_id)
120
117
121 def comm_msg(self, stream, ident, msg):
118 def comm_msg(self, stream, ident, msg):
122 """Handler for comm_msg messages"""
119 """Handler for comm_msg messages"""
123 content = msg['content']
120 content = msg['content']
124 comm_id = content['comm_id']
121 comm_id = content['comm_id']
125 comm = self.get_comm(comm_id)
122 comm = self.get_comm(comm_id)
126 if comm is None:
123 if comm is None:
127 # no such comm
124 # no such comm
128 return
125 return
129 try:
126 try:
130 comm.handle_msg(msg)
127 comm.handle_msg(msg)
131 except Exception:
128 except Exception:
132 self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True)
129 self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True)
133
130
134 def comm_close(self, stream, ident, msg):
131 def comm_close(self, stream, ident, msg):
135 """Handler for comm_close messages"""
132 """Handler for comm_close messages"""
136 content = msg['content']
133 content = msg['content']
137 comm_id = content['comm_id']
134 comm_id = content['comm_id']
138 comm = self.get_comm(comm_id)
135 comm = self.get_comm(comm_id)
139 if comm is None:
136 if comm is None:
140 # no such comm
137 # no such comm
141 self.log.debug("No such comm to close: %s", comm_id)
138 self.log.debug("No such comm to close: %s", comm_id)
142 return
139 return
143 del self.comms[comm_id]
140 del self.comms[comm_id]
144
141
145 try:
142 try:
146 comm.handle_close(msg)
143 comm.handle_close(msg)
147 except Exception:
144 except Exception:
148 self.log.error("Exception handling comm_close for %s", comm_id, exc_info=True)
145 self.log.error("Exception handling comm_close for %s", comm_id, exc_info=True)
149
146
150
147
151 __all__ = ['CommManager']
148 __all__ = ['CommManager']
General Comments 0
You need to be logged in to leave comments. Login now