##// END OF EJS Templates
Support specifying requirejs modules for comm targets
Thomas Kluyver -
Show More
@@ -1,190 +1,210 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 //-----------------------------------------------------------------------
12 12 // CommManager class
13 13 //-----------------------------------------------------------------------
14 14
15 15 var CommManager = function (kernel) {
16 16 this.comms = {};
17 17 this.targets = {};
18 18 if (kernel !== undefined) {
19 19 this.init_kernel(kernel);
20 20 }
21 21 };
22 22
23 23 CommManager.prototype.init_kernel = function (kernel) {
24 24 // connect the kernel, and register message handlers
25 25 this.kernel = kernel;
26 26 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
27 27 for (var i = 0; i < msg_types.length; i++) {
28 28 var msg_type = msg_types[i];
29 29 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
30 30 }
31 31 };
32 32
33 33 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
34 34 // Create a new Comm, register it, and open its Kernel-side counterpart
35 35 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
36 36 var comm = new Comm(target_name);
37 37 this.register_comm(comm);
38 38 comm.open(data, callbacks, metadata);
39 39 return comm;
40 40 };
41 41
42 42 CommManager.prototype.register_target = function (target_name, f) {
43 43 // Register a target function for a given target name
44 44 this.targets[target_name] = f;
45 45 };
46 46
47 47 CommManager.prototype.unregister_target = function (target_name, f) {
48 48 // Unregister a target function for a given target name
49 49 delete this.targets[target_name];
50 50 };
51 51
52 52 CommManager.prototype.register_comm = function (comm) {
53 53 // Register a comm in the mapping
54 54 this.comms[comm.comm_id] = comm;
55 55 comm.kernel = this.kernel;
56 56 return comm.comm_id;
57 57 };
58 58
59 59 CommManager.prototype.unregister_comm = function (comm) {
60 60 // Remove a comm from the mapping
61 61 delete this.comms[comm.comm_id];
62 62 };
63 63
64 64 // comm message handlers
65 65
66 66 CommManager.prototype.comm_open = function (msg) {
67 67 var content = msg.content;
68 var that = this;
69
70 var instantiate_comm = function(target) {
71 var comm = new Comm(content.target_name, content.comm_id);
72 that.register_comm(comm);
73 try {
74 target(comm, msg);
75 } catch (e) {
76 console.log("Exception opening new comm:", e, e.stack, msg);
77 comm.close();
78 that.unregister_comm(comm);
79 }
80 };
81
82 if (content.target_module) {
83 // Load requirejs module for comm target
84 require([content.target_module], function(mod) {
85 var target = mod[content.target_name];
86 if (target !== undefined) {
87 instantiate_comm(target)
88 } else {
89 console.log("Comm target " + content.target_name +
90 " not found in module " + content.target_module);
91 }
92 }, function(err) { console.log(err); });
93 } else {
94 // No requirejs module specified: look for target in registry
68 95 var f = this.targets[content.target_name];
69 96 if (f === undefined) {
70 97 console.log("No such target registered: ", content.target_name);
71 98 console.log("Available targets are: ", this.targets);
72 99 return;
73 100 }
74 var comm = new Comm(content.target_name, content.comm_id);
75 this.register_comm(comm);
76 try {
77 f(comm, msg);
78 } catch (e) {
79 console.log("Exception opening new comm:", e, e.stack, msg);
80 comm.close();
81 this.unregister_comm(comm);
101 instantiate_comm(f)
82 102 }
83 103 };
84 104
85 105 CommManager.prototype.comm_close = function (msg) {
86 106 var content = msg.content;
87 107 var comm = this.comms[content.comm_id];
88 108 if (comm === undefined) {
89 109 return;
90 110 }
91 111 this.unregister_comm(comm);
92 112 try {
93 113 comm.handle_close(msg);
94 114 } catch (e) {
95 115 console.log("Exception closing comm: ", e, e.stack, msg);
96 116 }
97 117 };
98 118
99 119 CommManager.prototype.comm_msg = function (msg) {
100 120 var content = msg.content;
101 121 var comm = this.comms[content.comm_id];
102 122 if (comm === undefined) {
103 123 return;
104 124 }
105 125 try {
106 126 comm.handle_msg(msg);
107 127 } catch (e) {
108 128 console.log("Exception handling comm msg: ", e, e.stack, msg);
109 129 }
110 130 };
111 131
112 132 //-----------------------------------------------------------------------
113 133 // Comm base class
114 134 //-----------------------------------------------------------------------
115 135
116 136 var Comm = function (target_name, comm_id) {
117 137 this.target_name = target_name;
118 138 this.comm_id = comm_id || utils.uuid();
119 139 this._msg_callback = this._close_callback = null;
120 140 };
121 141
122 142 // methods for sending messages
123 143 Comm.prototype.open = function (data, callbacks, metadata) {
124 144 var content = {
125 145 comm_id : this.comm_id,
126 146 target_name : this.target_name,
127 147 data : data || {},
128 148 };
129 149 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
130 150 };
131 151
132 152 Comm.prototype.send = function (data, callbacks, metadata, buffers) {
133 153 var content = {
134 154 comm_id : this.comm_id,
135 155 data : data || {},
136 156 };
137 157 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata, buffers);
138 158 };
139 159
140 160 Comm.prototype.close = function (data, callbacks, metadata) {
141 161 var content = {
142 162 comm_id : this.comm_id,
143 163 data : data || {},
144 164 };
145 165 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
146 166 };
147 167
148 168 // methods for registering callbacks for incoming messages
149 169 Comm.prototype._register_callback = function (key, callback) {
150 170 this['_' + key + '_callback'] = callback;
151 171 };
152 172
153 173 Comm.prototype.on_msg = function (callback) {
154 174 this._register_callback('msg', callback);
155 175 };
156 176
157 177 Comm.prototype.on_close = function (callback) {
158 178 this._register_callback('close', callback);
159 179 };
160 180
161 181 // methods for handling incoming messages
162 182
163 183 Comm.prototype._maybe_callback = function (key, msg) {
164 184 var callback = this['_' + key + '_callback'];
165 185 if (callback) {
166 186 try {
167 187 callback(msg);
168 188 } catch (e) {
169 189 console.log("Exception in Comm callback", e, e.stack, msg);
170 190 }
171 191 }
172 192 };
173 193
174 194 Comm.prototype.handle_msg = function (msg) {
175 195 this._maybe_callback('msg', msg);
176 196 };
177 197
178 198 Comm.prototype.handle_close = function (msg) {
179 199 this._maybe_callback('close', msg);
180 200 };
181 201
182 202 // For backwards compatability.
183 203 IPython.CommManager = CommManager;
184 204 IPython.Comm = Comm;
185 205
186 206 return {
187 207 'CommManager': CommManager,
188 208 'Comm': Comm
189 209 };
190 210 });
@@ -1,158 +1,162 b''
1 1 """Base class for a Comm"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import uuid
7 7
8 8 from IPython.config import LoggingConfigurable
9 9 from IPython.kernel.zmq.kernelbase import Kernel
10 10
11 11 from IPython.utils.jsonutil import json_clean
12 12 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
13 13
14 14
15 15 class Comm(LoggingConfigurable):
16 16
17 17 # If this is instantiated by a non-IPython kernel, shell will be None
18 18 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
19 19 allow_none=True)
20 20 kernel = Instance('IPython.kernel.zmq.kernelbase.Kernel')
21 21 def _kernel_default(self):
22 22 if Kernel.initialized():
23 23 return Kernel.instance()
24 24
25 25 iopub_socket = Any()
26 26 def _iopub_socket_default(self):
27 27 return self.kernel.iopub_socket
28 28 session = Instance('IPython.kernel.zmq.session.Session')
29 29 def _session_default(self):
30 30 if self.kernel is not None:
31 31 return self.kernel.session
32 32
33 33 target_name = Unicode('comm')
34 target_module = Unicode(None, allow_none=True, help="""requirejs module from
35 which to load comm target.""")
34 36
35 37 topic = Bytes()
36 38 def _topic_default(self):
37 39 return ('comm-%s' % self.comm_id).encode('ascii')
38 40
39 41 _open_data = Dict(help="data dict, if any, to be included in comm_open")
40 42 _close_data = Dict(help="data dict, if any, to be included in comm_close")
41 43
42 44 _msg_callback = Any()
43 45 _close_callback = Any()
44 46
45 47 _closed = Bool(True)
46 48 comm_id = Unicode()
47 49 def _comm_id_default(self):
48 50 return uuid.uuid4().hex
49 51
50 52 primary = Bool(True, help="Am I the primary or secondary Comm?")
51 53
52 54 def __init__(self, target_name='', data=None, **kwargs):
53 55 if target_name:
54 56 kwargs['target_name'] = target_name
55 57 super(Comm, self).__init__(**kwargs)
56 58 if self.primary:
57 59 # I am primary, open my peer.
58 60 self.open(data)
59 61 else:
60 62 self._closed = False
61 63
62 64 def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys):
63 65 """Helper for sending a comm message on IOPub"""
64 66 data = {} if data is None else data
65 67 metadata = {} if metadata is None else metadata
66 68 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
67 69 self.session.send(self.iopub_socket, msg_type,
68 70 content,
69 71 metadata=json_clean(metadata),
70 72 parent=self.kernel._parent_header,
71 73 ident=self.topic,
72 74 buffers=buffers,
73 75 )
74 76
75 77 def __del__(self):
76 78 """trigger close on gc"""
77 79 self.close()
78 80
79 81 # publishing messages
80 82
81 83 def open(self, data=None, metadata=None, buffers=None):
82 84 """Open the frontend-side version of this comm"""
83 85 if data is None:
84 86 data = self._open_data
85 87 comm_manager = getattr(self.kernel, 'comm_manager', None)
86 88 if comm_manager is None:
87 89 raise RuntimeError("Comms cannot be opened without a kernel "
88 90 "and a comm_manager attached to that kernel.")
89 91
90 92 comm_manager.register_comm(self)
91 93 try:
92 94 self._publish_msg('comm_open',
93 95 data=data, metadata=metadata, buffers=buffers,
94 target_name=self.target_name)
96 target_name=self.target_name,
97 target_module=self.target_module,
98 )
95 99 self._closed = False
96 100 except:
97 101 comm_manager.unregister_comm(self)
98 102 raise
99 103
100 104 def close(self, data=None, metadata=None, buffers=None):
101 105 """Close the frontend-side version of this comm"""
102 106 if self._closed:
103 107 # only close once
104 108 return
105 109 self._closed = True
106 110 if data is None:
107 111 data = self._close_data
108 112 self._publish_msg('comm_close',
109 113 data=data, metadata=metadata, buffers=buffers,
110 114 )
111 115 self.kernel.comm_manager.unregister_comm(self)
112 116
113 117 def send(self, data=None, metadata=None, buffers=None):
114 118 """Send a message to the frontend-side version of this comm"""
115 119 self._publish_msg('comm_msg',
116 120 data=data, metadata=metadata, buffers=buffers,
117 121 )
118 122
119 123 # registering callbacks
120 124
121 125 def on_close(self, callback):
122 126 """Register a callback for comm_close
123 127
124 128 Will be called with the `data` of the close message.
125 129
126 130 Call `on_close(None)` to disable an existing callback.
127 131 """
128 132 self._close_callback = callback
129 133
130 134 def on_msg(self, callback):
131 135 """Register a callback for comm_msg
132 136
133 137 Will be called with the `data` of any comm_msg messages.
134 138
135 139 Call `on_msg(None)` to disable an existing callback.
136 140 """
137 141 self._msg_callback = callback
138 142
139 143 # handling of incoming messages
140 144
141 145 def handle_close(self, msg):
142 146 """Handle a comm_close message"""
143 147 self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
144 148 if self._close_callback:
145 149 self._close_callback(msg)
146 150
147 151 def handle_msg(self, msg):
148 152 """Handle a comm_msg message"""
149 153 self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
150 154 if self._msg_callback:
151 155 if self.shell:
152 156 self.shell.events.trigger('pre_execute')
153 157 self._msg_callback(msg)
154 158 if self.shell:
155 159 self.shell.events.trigger('post_execute')
156 160
157 161
158 162 __all__ = ['Comm']
General Comments 0
You need to be logged in to leave comments. Login now