##// END OF EJS Templates
restore msg_id/msg_type aliases in top level of msg dict...
MinRK -
Show More
@@ -1,697 +1,703 b''
1 """Session object for building, serializing, sending, and receiving messages in
1 """Session object for building, serializing, sending, and receiving messages in
2 IPython. The Session object supports serialization, HMAC signatures, and
2 IPython. The Session object supports serialization, HMAC signatures, and
3 metadata on messages.
3 metadata on messages.
4
4
5 Also defined here are utilities for working with Sessions:
5 Also defined here are utilities for working with Sessions:
6 * A SessionFactory to be used as a base class for configurables that work with
6 * A SessionFactory to be used as a base class for configurables that work with
7 Sessions.
7 Sessions.
8 * A Message object for convenience that allows attribute-access to the msg dict.
8 * A Message object for convenience that allows attribute-access to the msg dict.
9
9
10 Authors:
10 Authors:
11
11
12 * Min RK
12 * Min RK
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15 """
15 """
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2010-2011 The IPython Development Team
17 # Copyright (C) 2010-2011 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 import hmac
27 import hmac
28 import logging
28 import logging
29 import os
29 import os
30 import pprint
30 import pprint
31 import uuid
31 import uuid
32 from datetime import datetime
32 from datetime import datetime
33
33
34 try:
34 try:
35 import cPickle
35 import cPickle
36 pickle = cPickle
36 pickle = cPickle
37 except:
37 except:
38 cPickle = None
38 cPickle = None
39 import pickle
39 import pickle
40
40
41 import zmq
41 import zmq
42 from zmq.utils import jsonapi
42 from zmq.utils import jsonapi
43 from zmq.eventloop.ioloop import IOLoop
43 from zmq.eventloop.ioloop import IOLoop
44 from zmq.eventloop.zmqstream import ZMQStream
44 from zmq.eventloop.zmqstream import ZMQStream
45
45
46 from IPython.config.configurable import Configurable, LoggingConfigurable
46 from IPython.config.configurable import Configurable, LoggingConfigurable
47 from IPython.utils.importstring import import_item
47 from IPython.utils.importstring import import_item
48 from IPython.utils.jsonutil import extract_dates, squash_dates, date_default
48 from IPython.utils.jsonutil import extract_dates, squash_dates, date_default
49 from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set,
49 from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set,
50 DottedObjectName)
50 DottedObjectName)
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # utility functions
53 # utility functions
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 def squash_unicode(obj):
56 def squash_unicode(obj):
57 """coerce unicode back to bytestrings."""
57 """coerce unicode back to bytestrings."""
58 if isinstance(obj,dict):
58 if isinstance(obj,dict):
59 for key in obj.keys():
59 for key in obj.keys():
60 obj[key] = squash_unicode(obj[key])
60 obj[key] = squash_unicode(obj[key])
61 if isinstance(key, unicode):
61 if isinstance(key, unicode):
62 obj[squash_unicode(key)] = obj.pop(key)
62 obj[squash_unicode(key)] = obj.pop(key)
63 elif isinstance(obj, list):
63 elif isinstance(obj, list):
64 for i,v in enumerate(obj):
64 for i,v in enumerate(obj):
65 obj[i] = squash_unicode(v)
65 obj[i] = squash_unicode(v)
66 elif isinstance(obj, unicode):
66 elif isinstance(obj, unicode):
67 obj = obj.encode('utf8')
67 obj = obj.encode('utf8')
68 return obj
68 return obj
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # globals and defaults
71 # globals and defaults
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 key = 'on_unknown' if jsonapi.jsonmod.__name__ == 'jsonlib' else 'default'
73 key = 'on_unknown' if jsonapi.jsonmod.__name__ == 'jsonlib' else 'default'
74 json_packer = lambda obj: jsonapi.dumps(obj, **{key:date_default})
74 json_packer = lambda obj: jsonapi.dumps(obj, **{key:date_default})
75 json_unpacker = lambda s: extract_dates(jsonapi.loads(s))
75 json_unpacker = lambda s: extract_dates(jsonapi.loads(s))
76
76
77 pickle_packer = lambda o: pickle.dumps(o,-1)
77 pickle_packer = lambda o: pickle.dumps(o,-1)
78 pickle_unpacker = pickle.loads
78 pickle_unpacker = pickle.loads
79
79
80 default_packer = json_packer
80 default_packer = json_packer
81 default_unpacker = json_unpacker
81 default_unpacker = json_unpacker
82
82
83
83
84 DELIM=b"<IDS|MSG>"
84 DELIM=b"<IDS|MSG>"
85
85
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87 # Classes
87 # Classes
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89
89
90 class SessionFactory(LoggingConfigurable):
90 class SessionFactory(LoggingConfigurable):
91 """The Base class for configurables that have a Session, Context, logger,
91 """The Base class for configurables that have a Session, Context, logger,
92 and IOLoop.
92 and IOLoop.
93 """
93 """
94
94
95 logname = Unicode('')
95 logname = Unicode('')
96 def _logname_changed(self, name, old, new):
96 def _logname_changed(self, name, old, new):
97 self.log = logging.getLogger(new)
97 self.log = logging.getLogger(new)
98
98
99 # not configurable:
99 # not configurable:
100 context = Instance('zmq.Context')
100 context = Instance('zmq.Context')
101 def _context_default(self):
101 def _context_default(self):
102 return zmq.Context.instance()
102 return zmq.Context.instance()
103
103
104 session = Instance('IPython.zmq.session.Session')
104 session = Instance('IPython.zmq.session.Session')
105
105
106 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
106 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
107 def _loop_default(self):
107 def _loop_default(self):
108 return IOLoop.instance()
108 return IOLoop.instance()
109
109
110 def __init__(self, **kwargs):
110 def __init__(self, **kwargs):
111 super(SessionFactory, self).__init__(**kwargs)
111 super(SessionFactory, self).__init__(**kwargs)
112
112
113 if self.session is None:
113 if self.session is None:
114 # construct the session
114 # construct the session
115 self.session = Session(**kwargs)
115 self.session = Session(**kwargs)
116
116
117
117
118 class Message(object):
118 class Message(object):
119 """A simple message object that maps dict keys to attributes.
119 """A simple message object that maps dict keys to attributes.
120
120
121 A Message can be created from a dict and a dict from a Message instance
121 A Message can be created from a dict and a dict from a Message instance
122 simply by calling dict(msg_obj)."""
122 simply by calling dict(msg_obj)."""
123
123
124 def __init__(self, msg_dict):
124 def __init__(self, msg_dict):
125 dct = self.__dict__
125 dct = self.__dict__
126 for k, v in dict(msg_dict).iteritems():
126 for k, v in dict(msg_dict).iteritems():
127 if isinstance(v, dict):
127 if isinstance(v, dict):
128 v = Message(v)
128 v = Message(v)
129 dct[k] = v
129 dct[k] = v
130
130
131 # Having this iterator lets dict(msg_obj) work out of the box.
131 # Having this iterator lets dict(msg_obj) work out of the box.
132 def __iter__(self):
132 def __iter__(self):
133 return iter(self.__dict__.iteritems())
133 return iter(self.__dict__.iteritems())
134
134
135 def __repr__(self):
135 def __repr__(self):
136 return repr(self.__dict__)
136 return repr(self.__dict__)
137
137
138 def __str__(self):
138 def __str__(self):
139 return pprint.pformat(self.__dict__)
139 return pprint.pformat(self.__dict__)
140
140
141 def __contains__(self, k):
141 def __contains__(self, k):
142 return k in self.__dict__
142 return k in self.__dict__
143
143
144 def __getitem__(self, k):
144 def __getitem__(self, k):
145 return self.__dict__[k]
145 return self.__dict__[k]
146
146
147
147
148 def msg_header(msg_id, msg_type, username, session):
148 def msg_header(msg_id, msg_type, username, session):
149 date = datetime.now()
149 date = datetime.now()
150 return locals()
150 return locals()
151
151
152 def extract_header(msg_or_header):
152 def extract_header(msg_or_header):
153 """Given a message or header, return the header."""
153 """Given a message or header, return the header."""
154 if not msg_or_header:
154 if not msg_or_header:
155 return {}
155 return {}
156 try:
156 try:
157 # See if msg_or_header is the entire message.
157 # See if msg_or_header is the entire message.
158 h = msg_or_header['header']
158 h = msg_or_header['header']
159 except KeyError:
159 except KeyError:
160 try:
160 try:
161 # See if msg_or_header is just the header
161 # See if msg_or_header is just the header
162 h = msg_or_header['msg_id']
162 h = msg_or_header['msg_id']
163 except KeyError:
163 except KeyError:
164 raise
164 raise
165 else:
165 else:
166 h = msg_or_header
166 h = msg_or_header
167 if not isinstance(h, dict):
167 if not isinstance(h, dict):
168 h = dict(h)
168 h = dict(h)
169 return h
169 return h
170
170
171 class Session(Configurable):
171 class Session(Configurable):
172 """Object for handling serialization and sending of messages.
172 """Object for handling serialization and sending of messages.
173
173
174 The Session object handles building messages and sending them
174 The Session object handles building messages and sending them
175 with ZMQ sockets or ZMQStream objects. Objects can communicate with each
175 with ZMQ sockets or ZMQStream objects. Objects can communicate with each
176 other over the network via Session objects, and only need to work with the
176 other over the network via Session objects, and only need to work with the
177 dict-based IPython message spec. The Session will handle
177 dict-based IPython message spec. The Session will handle
178 serialization/deserialization, security, and metadata.
178 serialization/deserialization, security, and metadata.
179
179
180 Sessions support configurable serialiization via packer/unpacker traits,
180 Sessions support configurable serialiization via packer/unpacker traits,
181 and signing with HMAC digests via the key/keyfile traits.
181 and signing with HMAC digests via the key/keyfile traits.
182
182
183 Parameters
183 Parameters
184 ----------
184 ----------
185
185
186 debug : bool
186 debug : bool
187 whether to trigger extra debugging statements
187 whether to trigger extra debugging statements
188 packer/unpacker : str : 'json', 'pickle' or import_string
188 packer/unpacker : str : 'json', 'pickle' or import_string
189 importstrings for methods to serialize message parts. If just
189 importstrings for methods to serialize message parts. If just
190 'json' or 'pickle', predefined JSON and pickle packers will be used.
190 'json' or 'pickle', predefined JSON and pickle packers will be used.
191 Otherwise, the entire importstring must be used.
191 Otherwise, the entire importstring must be used.
192
192
193 The functions must accept at least valid JSON input, and output *bytes*.
193 The functions must accept at least valid JSON input, and output *bytes*.
194
194
195 For example, to use msgpack:
195 For example, to use msgpack:
196 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
196 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
197 pack/unpack : callables
197 pack/unpack : callables
198 You can also set the pack/unpack callables for serialization directly.
198 You can also set the pack/unpack callables for serialization directly.
199 session : bytes
199 session : bytes
200 the ID of this Session object. The default is to generate a new UUID.
200 the ID of this Session object. The default is to generate a new UUID.
201 username : unicode
201 username : unicode
202 username added to message headers. The default is to ask the OS.
202 username added to message headers. The default is to ask the OS.
203 key : bytes
203 key : bytes
204 The key used to initialize an HMAC signature. If unset, messages
204 The key used to initialize an HMAC signature. If unset, messages
205 will not be signed or checked.
205 will not be signed or checked.
206 keyfile : filepath
206 keyfile : filepath
207 The file containing a key. If this is set, `key` will be initialized
207 The file containing a key. If this is set, `key` will be initialized
208 to the contents of the file.
208 to the contents of the file.
209
209
210 """
210 """
211
211
212 debug=Bool(False, config=True, help="""Debug output in the Session""")
212 debug=Bool(False, config=True, help="""Debug output in the Session""")
213
213
214 packer = DottedObjectName('json',config=True,
214 packer = DottedObjectName('json',config=True,
215 help="""The name of the packer for serializing messages.
215 help="""The name of the packer for serializing messages.
216 Should be one of 'json', 'pickle', or an import name
216 Should be one of 'json', 'pickle', or an import name
217 for a custom callable serializer.""")
217 for a custom callable serializer.""")
218 def _packer_changed(self, name, old, new):
218 def _packer_changed(self, name, old, new):
219 if new.lower() == 'json':
219 if new.lower() == 'json':
220 self.pack = json_packer
220 self.pack = json_packer
221 self.unpack = json_unpacker
221 self.unpack = json_unpacker
222 elif new.lower() == 'pickle':
222 elif new.lower() == 'pickle':
223 self.pack = pickle_packer
223 self.pack = pickle_packer
224 self.unpack = pickle_unpacker
224 self.unpack = pickle_unpacker
225 else:
225 else:
226 self.pack = import_item(str(new))
226 self.pack = import_item(str(new))
227
227
228 unpacker = DottedObjectName('json', config=True,
228 unpacker = DottedObjectName('json', config=True,
229 help="""The name of the unpacker for unserializing messages.
229 help="""The name of the unpacker for unserializing messages.
230 Only used with custom functions for `packer`.""")
230 Only used with custom functions for `packer`.""")
231 def _unpacker_changed(self, name, old, new):
231 def _unpacker_changed(self, name, old, new):
232 if new.lower() == 'json':
232 if new.lower() == 'json':
233 self.pack = json_packer
233 self.pack = json_packer
234 self.unpack = json_unpacker
234 self.unpack = json_unpacker
235 elif new.lower() == 'pickle':
235 elif new.lower() == 'pickle':
236 self.pack = pickle_packer
236 self.pack = pickle_packer
237 self.unpack = pickle_unpacker
237 self.unpack = pickle_unpacker
238 else:
238 else:
239 self.unpack = import_item(str(new))
239 self.unpack = import_item(str(new))
240
240
241 session = CBytes(b'', config=True,
241 session = CBytes(b'', config=True,
242 help="""The UUID identifying this session.""")
242 help="""The UUID identifying this session.""")
243 def _session_default(self):
243 def _session_default(self):
244 return bytes(uuid.uuid4())
244 return bytes(uuid.uuid4())
245
245
246 username = Unicode(os.environ.get('USER',u'username'), config=True,
246 username = Unicode(os.environ.get('USER',u'username'), config=True,
247 help="""Username for the Session. Default is your system username.""")
247 help="""Username for the Session. Default is your system username.""")
248
248
249 # message signature related traits:
249 # message signature related traits:
250 key = CBytes(b'', config=True,
250 key = CBytes(b'', config=True,
251 help="""execution key, for extra authentication.""")
251 help="""execution key, for extra authentication.""")
252 def _key_changed(self, name, old, new):
252 def _key_changed(self, name, old, new):
253 if new:
253 if new:
254 self.auth = hmac.HMAC(new)
254 self.auth = hmac.HMAC(new)
255 else:
255 else:
256 self.auth = None
256 self.auth = None
257 auth = Instance(hmac.HMAC)
257 auth = Instance(hmac.HMAC)
258 digest_history = Set()
258 digest_history = Set()
259
259
260 keyfile = Unicode('', config=True,
260 keyfile = Unicode('', config=True,
261 help="""path to file containing execution key.""")
261 help="""path to file containing execution key.""")
262 def _keyfile_changed(self, name, old, new):
262 def _keyfile_changed(self, name, old, new):
263 with open(new, 'rb') as f:
263 with open(new, 'rb') as f:
264 self.key = f.read().strip()
264 self.key = f.read().strip()
265
265
266 pack = Any(default_packer) # the actual packer function
266 pack = Any(default_packer) # the actual packer function
267 def _pack_changed(self, name, old, new):
267 def _pack_changed(self, name, old, new):
268 if not callable(new):
268 if not callable(new):
269 raise TypeError("packer must be callable, not %s"%type(new))
269 raise TypeError("packer must be callable, not %s"%type(new))
270
270
271 unpack = Any(default_unpacker) # the actual packer function
271 unpack = Any(default_unpacker) # the actual packer function
272 def _unpack_changed(self, name, old, new):
272 def _unpack_changed(self, name, old, new):
273 # unpacker is not checked - it is assumed to be
273 # unpacker is not checked - it is assumed to be
274 if not callable(new):
274 if not callable(new):
275 raise TypeError("unpacker must be callable, not %s"%type(new))
275 raise TypeError("unpacker must be callable, not %s"%type(new))
276
276
277 def __init__(self, **kwargs):
277 def __init__(self, **kwargs):
278 """create a Session object
278 """create a Session object
279
279
280 Parameters
280 Parameters
281 ----------
281 ----------
282
282
283 debug : bool
283 debug : bool
284 whether to trigger extra debugging statements
284 whether to trigger extra debugging statements
285 packer/unpacker : str : 'json', 'pickle' or import_string
285 packer/unpacker : str : 'json', 'pickle' or import_string
286 importstrings for methods to serialize message parts. If just
286 importstrings for methods to serialize message parts. If just
287 'json' or 'pickle', predefined JSON and pickle packers will be used.
287 'json' or 'pickle', predefined JSON and pickle packers will be used.
288 Otherwise, the entire importstring must be used.
288 Otherwise, the entire importstring must be used.
289
289
290 The functions must accept at least valid JSON input, and output
290 The functions must accept at least valid JSON input, and output
291 *bytes*.
291 *bytes*.
292
292
293 For example, to use msgpack:
293 For example, to use msgpack:
294 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
294 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
295 pack/unpack : callables
295 pack/unpack : callables
296 You can also set the pack/unpack callables for serialization
296 You can also set the pack/unpack callables for serialization
297 directly.
297 directly.
298 session : bytes
298 session : bytes
299 the ID of this Session object. The default is to generate a new
299 the ID of this Session object. The default is to generate a new
300 UUID.
300 UUID.
301 username : unicode
301 username : unicode
302 username added to message headers. The default is to ask the OS.
302 username added to message headers. The default is to ask the OS.
303 key : bytes
303 key : bytes
304 The key used to initialize an HMAC signature. If unset, messages
304 The key used to initialize an HMAC signature. If unset, messages
305 will not be signed or checked.
305 will not be signed or checked.
306 keyfile : filepath
306 keyfile : filepath
307 The file containing a key. If this is set, `key` will be
307 The file containing a key. If this is set, `key` will be
308 initialized to the contents of the file.
308 initialized to the contents of the file.
309 """
309 """
310 super(Session, self).__init__(**kwargs)
310 super(Session, self).__init__(**kwargs)
311 self._check_packers()
311 self._check_packers()
312 self.none = self.pack({})
312 self.none = self.pack({})
313
313
314 @property
314 @property
315 def msg_id(self):
315 def msg_id(self):
316 """always return new uuid"""
316 """always return new uuid"""
317 return str(uuid.uuid4())
317 return str(uuid.uuid4())
318
318
319 def _check_packers(self):
319 def _check_packers(self):
320 """check packers for binary data and datetime support."""
320 """check packers for binary data and datetime support."""
321 pack = self.pack
321 pack = self.pack
322 unpack = self.unpack
322 unpack = self.unpack
323
323
324 # check simple serialization
324 # check simple serialization
325 msg = dict(a=[1,'hi'])
325 msg = dict(a=[1,'hi'])
326 try:
326 try:
327 packed = pack(msg)
327 packed = pack(msg)
328 except Exception:
328 except Exception:
329 raise ValueError("packer could not serialize a simple message")
329 raise ValueError("packer could not serialize a simple message")
330
330
331 # ensure packed message is bytes
331 # ensure packed message is bytes
332 if not isinstance(packed, bytes):
332 if not isinstance(packed, bytes):
333 raise ValueError("message packed to %r, but bytes are required"%type(packed))
333 raise ValueError("message packed to %r, but bytes are required"%type(packed))
334
334
335 # check that unpack is pack's inverse
335 # check that unpack is pack's inverse
336 try:
336 try:
337 unpacked = unpack(packed)
337 unpacked = unpack(packed)
338 except Exception:
338 except Exception:
339 raise ValueError("unpacker could not handle the packer's output")
339 raise ValueError("unpacker could not handle the packer's output")
340
340
341 # check datetime support
341 # check datetime support
342 msg = dict(t=datetime.now())
342 msg = dict(t=datetime.now())
343 try:
343 try:
344 unpacked = unpack(pack(msg))
344 unpacked = unpack(pack(msg))
345 except Exception:
345 except Exception:
346 self.pack = lambda o: pack(squash_dates(o))
346 self.pack = lambda o: pack(squash_dates(o))
347 self.unpack = lambda s: extract_dates(unpack(s))
347 self.unpack = lambda s: extract_dates(unpack(s))
348
348
349 def msg_header(self, msg_type):
349 def msg_header(self, msg_type):
350 return msg_header(self.msg_id, msg_type, self.username, self.session)
350 return msg_header(self.msg_id, msg_type, self.username, self.session)
351
351
352 def msg(self, msg_type, content=None, parent=None, subheader=None, header=None):
352 def msg(self, msg_type, content=None, parent=None, subheader=None, header=None):
353 """Return the nested message dict.
353 """Return the nested message dict.
354
354
355 This format is different from what is sent over the wire. The
355 This format is different from what is sent over the wire. The
356 serialize/unserialize methods converts this nested message dict to the wire
356 serialize/unserialize methods converts this nested message dict to the wire
357 format, which is a list of message parts.
357 format, which is a list of message parts.
358 """
358 """
359 msg = {}
359 msg = {}
360 msg['header'] = self.msg_header(msg_type) if header is None else header
360 header = self.msg_header(msg_type) if header is None else header
361 msg['header'] = header
362 msg['msg_id'] = header['msg_id']
363 msg['msg_type'] = header['msg_type']
361 msg['parent_header'] = {} if parent is None else extract_header(parent)
364 msg['parent_header'] = {} if parent is None else extract_header(parent)
362 msg['content'] = {} if content is None else content
365 msg['content'] = {} if content is None else content
363 sub = {} if subheader is None else subheader
366 sub = {} if subheader is None else subheader
364 msg['header'].update(sub)
367 msg['header'].update(sub)
365 return msg
368 return msg
366
369
367 def sign(self, msg_list):
370 def sign(self, msg_list):
368 """Sign a message with HMAC digest. If no auth, return b''.
371 """Sign a message with HMAC digest. If no auth, return b''.
369
372
370 Parameters
373 Parameters
371 ----------
374 ----------
372 msg_list : list
375 msg_list : list
373 The [p_header,p_parent,p_content] part of the message list.
376 The [p_header,p_parent,p_content] part of the message list.
374 """
377 """
375 if self.auth is None:
378 if self.auth is None:
376 return b''
379 return b''
377 h = self.auth.copy()
380 h = self.auth.copy()
378 for m in msg_list:
381 for m in msg_list:
379 h.update(m)
382 h.update(m)
380 return h.hexdigest()
383 return h.hexdigest()
381
384
382 def serialize(self, msg, ident=None):
385 def serialize(self, msg, ident=None):
383 """Serialize the message components to bytes.
386 """Serialize the message components to bytes.
384
387
385 This is roughly the inverse of unserialize. The serialize/unserialize
388 This is roughly the inverse of unserialize. The serialize/unserialize
386 methods work with full message lists, whereas pack/unpack work with
389 methods work with full message lists, whereas pack/unpack work with
387 the individual message parts in the message list.
390 the individual message parts in the message list.
388
391
389 Parameters
392 Parameters
390 ----------
393 ----------
391 msg : dict or Message
394 msg : dict or Message
392 The nexted message dict as returned by the self.msg method.
395 The nexted message dict as returned by the self.msg method.
393
396
394 Returns
397 Returns
395 -------
398 -------
396 msg_list : list
399 msg_list : list
397 The list of bytes objects to be sent with the format:
400 The list of bytes objects to be sent with the format:
398 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
401 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
399 buffer1,buffer2,...]. In this list, the p_* entities are
402 buffer1,buffer2,...]. In this list, the p_* entities are
400 the packed or serialized versions, so if JSON is used, these
403 the packed or serialized versions, so if JSON is used, these
401 are uft8 encoded JSON strings.
404 are uft8 encoded JSON strings.
402 """
405 """
403 content = msg.get('content', {})
406 content = msg.get('content', {})
404 if content is None:
407 if content is None:
405 content = self.none
408 content = self.none
406 elif isinstance(content, dict):
409 elif isinstance(content, dict):
407 content = self.pack(content)
410 content = self.pack(content)
408 elif isinstance(content, bytes):
411 elif isinstance(content, bytes):
409 # content is already packed, as in a relayed message
412 # content is already packed, as in a relayed message
410 pass
413 pass
411 elif isinstance(content, unicode):
414 elif isinstance(content, unicode):
412 # should be bytes, but JSON often spits out unicode
415 # should be bytes, but JSON often spits out unicode
413 content = content.encode('utf8')
416 content = content.encode('utf8')
414 else:
417 else:
415 raise TypeError("Content incorrect type: %s"%type(content))
418 raise TypeError("Content incorrect type: %s"%type(content))
416
419
417 real_message = [self.pack(msg['header']),
420 real_message = [self.pack(msg['header']),
418 self.pack(msg['parent_header']),
421 self.pack(msg['parent_header']),
419 content
422 content
420 ]
423 ]
421
424
422 to_send = []
425 to_send = []
423
426
424 if isinstance(ident, list):
427 if isinstance(ident, list):
425 # accept list of idents
428 # accept list of idents
426 to_send.extend(ident)
429 to_send.extend(ident)
427 elif ident is not None:
430 elif ident is not None:
428 to_send.append(ident)
431 to_send.append(ident)
429 to_send.append(DELIM)
432 to_send.append(DELIM)
430
433
431 signature = self.sign(real_message)
434 signature = self.sign(real_message)
432 to_send.append(signature)
435 to_send.append(signature)
433
436
434 to_send.extend(real_message)
437 to_send.extend(real_message)
435
438
436 return to_send
439 return to_send
437
440
438 def send(self, stream, msg_or_type, content=None, parent=None, ident=None,
441 def send(self, stream, msg_or_type, content=None, parent=None, ident=None,
439 buffers=None, subheader=None, track=False, header=None):
442 buffers=None, subheader=None, track=False, header=None):
440 """Build and send a message via stream or socket.
443 """Build and send a message via stream or socket.
441
444
442 The message format used by this function internally is as follows:
445 The message format used by this function internally is as follows:
443
446
444 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
447 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
445 buffer1,buffer2,...]
448 buffer1,buffer2,...]
446
449
447 The serialize/unserialize methods convert the nested message dict into this
450 The serialize/unserialize methods convert the nested message dict into this
448 format.
451 format.
449
452
450 Parameters
453 Parameters
451 ----------
454 ----------
452
455
453 stream : zmq.Socket or ZMQStream
456 stream : zmq.Socket or ZMQStream
454 The socket-like object used to send the data.
457 The socket-like object used to send the data.
455 msg_or_type : str or Message/dict
458 msg_or_type : str or Message/dict
456 Normally, msg_or_type will be a msg_type unless a message is being
459 Normally, msg_or_type will be a msg_type unless a message is being
457 sent more than once. If a header is supplied, this can be set to
460 sent more than once. If a header is supplied, this can be set to
458 None and the msg_type will be pulled from the header.
461 None and the msg_type will be pulled from the header.
459
462
460 content : dict or None
463 content : dict or None
461 The content of the message (ignored if msg_or_type is a message).
464 The content of the message (ignored if msg_or_type is a message).
462 header : dict or None
465 header : dict or None
463 The header dict for the message (ignores if msg_to_type is a message).
466 The header dict for the message (ignores if msg_to_type is a message).
464 parent : Message or dict or None
467 parent : Message or dict or None
465 The parent or parent header describing the parent of this message
468 The parent or parent header describing the parent of this message
466 (ignored if msg_or_type is a message).
469 (ignored if msg_or_type is a message).
467 ident : bytes or list of bytes
470 ident : bytes or list of bytes
468 The zmq.IDENTITY routing path.
471 The zmq.IDENTITY routing path.
469 subheader : dict or None
472 subheader : dict or None
470 Extra header keys for this message's header (ignored if msg_or_type
473 Extra header keys for this message's header (ignored if msg_or_type
471 is a message).
474 is a message).
472 buffers : list or None
475 buffers : list or None
473 The already-serialized buffers to be appended to the message.
476 The already-serialized buffers to be appended to the message.
474 track : bool
477 track : bool
475 Whether to track. Only for use with Sockets, because ZMQStream
478 Whether to track. Only for use with Sockets, because ZMQStream
476 objects cannot track messages.
479 objects cannot track messages.
477
480
478 Returns
481 Returns
479 -------
482 -------
480 msg : dict
483 msg : dict
481 The constructed message.
484 The constructed message.
482 (msg,tracker) : (dict, MessageTracker)
485 (msg,tracker) : (dict, MessageTracker)
483 if track=True, then a 2-tuple will be returned,
486 if track=True, then a 2-tuple will be returned,
484 the first element being the constructed
487 the first element being the constructed
485 message, and the second being the MessageTracker
488 message, and the second being the MessageTracker
486
489
487 """
490 """
488
491
489 if not isinstance(stream, (zmq.Socket, ZMQStream)):
492 if not isinstance(stream, (zmq.Socket, ZMQStream)):
490 raise TypeError("stream must be Socket or ZMQStream, not %r"%type(stream))
493 raise TypeError("stream must be Socket or ZMQStream, not %r"%type(stream))
491 elif track and isinstance(stream, ZMQStream):
494 elif track and isinstance(stream, ZMQStream):
492 raise TypeError("ZMQStream cannot track messages")
495 raise TypeError("ZMQStream cannot track messages")
493
496
494 if isinstance(msg_or_type, (Message, dict)):
497 if isinstance(msg_or_type, (Message, dict)):
495 # We got a Message or message dict, not a msg_type so don't
498 # We got a Message or message dict, not a msg_type so don't
496 # build a new Message.
499 # build a new Message.
497 msg = msg_or_type
500 msg = msg_or_type
498 else:
501 else:
499 msg = self.msg(msg_or_type, content=content, parent=parent,
502 msg = self.msg(msg_or_type, content=content, parent=parent,
500 subheader=subheader, header=header)
503 subheader=subheader, header=header)
501
504
502 buffers = [] if buffers is None else buffers
505 buffers = [] if buffers is None else buffers
503 to_send = self.serialize(msg, ident)
506 to_send = self.serialize(msg, ident)
504 flag = 0
507 flag = 0
505 if buffers:
508 if buffers:
506 flag = zmq.SNDMORE
509 flag = zmq.SNDMORE
507 _track = False
510 _track = False
508 else:
511 else:
509 _track=track
512 _track=track
510 if track:
513 if track:
511 tracker = stream.send_multipart(to_send, flag, copy=False, track=_track)
514 tracker = stream.send_multipart(to_send, flag, copy=False, track=_track)
512 else:
515 else:
513 tracker = stream.send_multipart(to_send, flag, copy=False)
516 tracker = stream.send_multipart(to_send, flag, copy=False)
514 for b in buffers[:-1]:
517 for b in buffers[:-1]:
515 stream.send(b, flag, copy=False)
518 stream.send(b, flag, copy=False)
516 if buffers:
519 if buffers:
517 if track:
520 if track:
518 tracker = stream.send(buffers[-1], copy=False, track=track)
521 tracker = stream.send(buffers[-1], copy=False, track=track)
519 else:
522 else:
520 tracker = stream.send(buffers[-1], copy=False)
523 tracker = stream.send(buffers[-1], copy=False)
521
524
522 # omsg = Message(msg)
525 # omsg = Message(msg)
523 if self.debug:
526 if self.debug:
524 pprint.pprint(msg)
527 pprint.pprint(msg)
525 pprint.pprint(to_send)
528 pprint.pprint(to_send)
526 pprint.pprint(buffers)
529 pprint.pprint(buffers)
527
530
528 msg['tracker'] = tracker
531 msg['tracker'] = tracker
529
532
530 return msg
533 return msg
531
534
532 def send_raw(self, stream, msg_list, flags=0, copy=True, ident=None):
535 def send_raw(self, stream, msg_list, flags=0, copy=True, ident=None):
533 """Send a raw message via ident path.
536 """Send a raw message via ident path.
534
537
535 This method is used to send a already serialized message.
538 This method is used to send a already serialized message.
536
539
537 Parameters
540 Parameters
538 ----------
541 ----------
539 stream : ZMQStream or Socket
542 stream : ZMQStream or Socket
540 The ZMQ stream or socket to use for sending the message.
543 The ZMQ stream or socket to use for sending the message.
541 msg_list : list
544 msg_list : list
542 The serialized list of messages to send. This only includes the
545 The serialized list of messages to send. This only includes the
543 [p_header,p_parent,p_content,buffer1,buffer2,...] portion of
546 [p_header,p_parent,p_content,buffer1,buffer2,...] portion of
544 the message.
547 the message.
545 ident : ident or list
548 ident : ident or list
546 A single ident or a list of idents to use in sending.
549 A single ident or a list of idents to use in sending.
547 """
550 """
548 to_send = []
551 to_send = []
549 if isinstance(ident, bytes):
552 if isinstance(ident, bytes):
550 ident = [ident]
553 ident = [ident]
551 if ident is not None:
554 if ident is not None:
552 to_send.extend(ident)
555 to_send.extend(ident)
553
556
554 to_send.append(DELIM)
557 to_send.append(DELIM)
555 to_send.append(self.sign(msg_list))
558 to_send.append(self.sign(msg_list))
556 to_send.extend(msg_list)
559 to_send.extend(msg_list)
557 stream.send_multipart(msg_list, flags, copy=copy)
560 stream.send_multipart(msg_list, flags, copy=copy)
558
561
559 def recv(self, socket, mode=zmq.NOBLOCK, content=True, copy=True):
562 def recv(self, socket, mode=zmq.NOBLOCK, content=True, copy=True):
560 """Receive and unpack a message.
563 """Receive and unpack a message.
561
564
562 Parameters
565 Parameters
563 ----------
566 ----------
564 socket : ZMQStream or Socket
567 socket : ZMQStream or Socket
565 The socket or stream to use in receiving.
568 The socket or stream to use in receiving.
566
569
567 Returns
570 Returns
568 -------
571 -------
569 [idents], msg
572 [idents], msg
570 [idents] is a list of idents and msg is a nested message dict of
573 [idents] is a list of idents and msg is a nested message dict of
571 same format as self.msg returns.
574 same format as self.msg returns.
572 """
575 """
573 if isinstance(socket, ZMQStream):
576 if isinstance(socket, ZMQStream):
574 socket = socket.socket
577 socket = socket.socket
575 try:
578 try:
576 msg_list = socket.recv_multipart(mode)
579 msg_list = socket.recv_multipart(mode)
577 except zmq.ZMQError as e:
580 except zmq.ZMQError as e:
578 if e.errno == zmq.EAGAIN:
581 if e.errno == zmq.EAGAIN:
579 # We can convert EAGAIN to None as we know in this case
582 # We can convert EAGAIN to None as we know in this case
580 # recv_multipart won't return None.
583 # recv_multipart won't return None.
581 return None,None
584 return None,None
582 else:
585 else:
583 raise
586 raise
584 # split multipart message into identity list and message dict
587 # split multipart message into identity list and message dict
585 # invalid large messages can cause very expensive string comparisons
588 # invalid large messages can cause very expensive string comparisons
586 idents, msg_list = self.feed_identities(msg_list, copy)
589 idents, msg_list = self.feed_identities(msg_list, copy)
587 try:
590 try:
588 return idents, self.unserialize(msg_list, content=content, copy=copy)
591 return idents, self.unserialize(msg_list, content=content, copy=copy)
589 except Exception as e:
592 except Exception as e:
590 # TODO: handle it
593 # TODO: handle it
591 raise e
594 raise e
592
595
593 def feed_identities(self, msg_list, copy=True):
596 def feed_identities(self, msg_list, copy=True):
594 """Split the identities from the rest of the message.
597 """Split the identities from the rest of the message.
595
598
596 Feed until DELIM is reached, then return the prefix as idents and
599 Feed until DELIM is reached, then return the prefix as idents and
597 remainder as msg_list. This is easily broken by setting an IDENT to DELIM,
600 remainder as msg_list. This is easily broken by setting an IDENT to DELIM,
598 but that would be silly.
601 but that would be silly.
599
602
600 Parameters
603 Parameters
601 ----------
604 ----------
602 msg_list : a list of Message or bytes objects
605 msg_list : a list of Message or bytes objects
603 The message to be split.
606 The message to be split.
604 copy : bool
607 copy : bool
605 flag determining whether the arguments are bytes or Messages
608 flag determining whether the arguments are bytes or Messages
606
609
607 Returns
610 Returns
608 -------
611 -------
609 (idents, msg_list) : two lists
612 (idents, msg_list) : two lists
610 idents will always be a list of bytes, each of which is a ZMQ
613 idents will always be a list of bytes, each of which is a ZMQ
611 identity. msg_list will be a list of bytes or zmq.Messages of the
614 identity. msg_list will be a list of bytes or zmq.Messages of the
612 form [HMAC,p_header,p_parent,p_content,buffer1,buffer2,...] and
615 form [HMAC,p_header,p_parent,p_content,buffer1,buffer2,...] and
613 should be unpackable/unserializable via self.unserialize at this
616 should be unpackable/unserializable via self.unserialize at this
614 point.
617 point.
615 """
618 """
616 if copy:
619 if copy:
617 idx = msg_list.index(DELIM)
620 idx = msg_list.index(DELIM)
618 return msg_list[:idx], msg_list[idx+1:]
621 return msg_list[:idx], msg_list[idx+1:]
619 else:
622 else:
620 failed = True
623 failed = True
621 for idx,m in enumerate(msg_list):
624 for idx,m in enumerate(msg_list):
622 if m.bytes == DELIM:
625 if m.bytes == DELIM:
623 failed = False
626 failed = False
624 break
627 break
625 if failed:
628 if failed:
626 raise ValueError("DELIM not in msg_list")
629 raise ValueError("DELIM not in msg_list")
627 idents, msg_list = msg_list[:idx], msg_list[idx+1:]
630 idents, msg_list = msg_list[:idx], msg_list[idx+1:]
628 return [m.bytes for m in idents], msg_list
631 return [m.bytes for m in idents], msg_list
629
632
630 def unserialize(self, msg_list, content=True, copy=True):
633 def unserialize(self, msg_list, content=True, copy=True):
631 """Unserialize a msg_list to a nested message dict.
634 """Unserialize a msg_list to a nested message dict.
632
635
633 This is roughly the inverse of serialize. The serialize/unserialize
636 This is roughly the inverse of serialize. The serialize/unserialize
634 methods work with full message lists, whereas pack/unpack work with
637 methods work with full message lists, whereas pack/unpack work with
635 the individual message parts in the message list.
638 the individual message parts in the message list.
636
639
637 Parameters:
640 Parameters:
638 -----------
641 -----------
639 msg_list : list of bytes or Message objects
642 msg_list : list of bytes or Message objects
640 The list of message parts of the form [HMAC,p_header,p_parent,
643 The list of message parts of the form [HMAC,p_header,p_parent,
641 p_content,buffer1,buffer2,...].
644 p_content,buffer1,buffer2,...].
642 content : bool (True)
645 content : bool (True)
643 Whether to unpack the content dict (True), or leave it packed
646 Whether to unpack the content dict (True), or leave it packed
644 (False).
647 (False).
645 copy : bool (True)
648 copy : bool (True)
646 Whether to return the bytes (True), or the non-copying Message
649 Whether to return the bytes (True), or the non-copying Message
647 object in each place (False).
650 object in each place (False).
648
651
649 Returns
652 Returns
650 -------
653 -------
651 msg : dict
654 msg : dict
652 The nested message dict with top-level keys [header, parent_header,
655 The nested message dict with top-level keys [header, parent_header,
653 content, buffers].
656 content, buffers].
654 """
657 """
655 minlen = 4
658 minlen = 4
656 message = {}
659 message = {}
657 if not copy:
660 if not copy:
658 for i in range(minlen):
661 for i in range(minlen):
659 msg_list[i] = msg_list[i].bytes
662 msg_list[i] = msg_list[i].bytes
660 if self.auth is not None:
663 if self.auth is not None:
661 signature = msg_list[0]
664 signature = msg_list[0]
662 if not signature:
665 if not signature:
663 raise ValueError("Unsigned Message")
666 raise ValueError("Unsigned Message")
664 if signature in self.digest_history:
667 if signature in self.digest_history:
665 raise ValueError("Duplicate Signature: %r"%signature)
668 raise ValueError("Duplicate Signature: %r"%signature)
666 self.digest_history.add(signature)
669 self.digest_history.add(signature)
667 check = self.sign(msg_list[1:4])
670 check = self.sign(msg_list[1:4])
668 if not signature == check:
671 if not signature == check:
669 raise ValueError("Invalid Signature: %r"%signature)
672 raise ValueError("Invalid Signature: %r"%signature)
670 if not len(msg_list) >= minlen:
673 if not len(msg_list) >= minlen:
671 raise TypeError("malformed message, must have at least %i elements"%minlen)
674 raise TypeError("malformed message, must have at least %i elements"%minlen)
672 message['header'] = self.unpack(msg_list[1])
675 header = self.unpack(msg_list[1])
676 message['header'] = header
677 message['msg_id'] = header['msg_id']
678 message['msg_type'] = header['msg_type']
673 message['parent_header'] = self.unpack(msg_list[2])
679 message['parent_header'] = self.unpack(msg_list[2])
674 if content:
680 if content:
675 message['content'] = self.unpack(msg_list[3])
681 message['content'] = self.unpack(msg_list[3])
676 else:
682 else:
677 message['content'] = msg_list[3]
683 message['content'] = msg_list[3]
678
684
679 message['buffers'] = msg_list[4:]
685 message['buffers'] = msg_list[4:]
680 return message
686 return message
681
687
682 def test_msg2obj():
688 def test_msg2obj():
683 am = dict(x=1)
689 am = dict(x=1)
684 ao = Message(am)
690 ao = Message(am)
685 assert ao.x == am['x']
691 assert ao.x == am['x']
686
692
687 am['y'] = dict(z=1)
693 am['y'] = dict(z=1)
688 ao = Message(am)
694 ao = Message(am)
689 assert ao.y.z == am['y']['z']
695 assert ao.y.z == am['y']['z']
690
696
691 k1, k2 = 'y', 'z'
697 k1, k2 = 'y', 'z'
692 assert ao[k1][k2] == am[k1][k2]
698 assert ao[k1][k2] == am[k1][k2]
693
699
694 am2 = dict(ao)
700 am2 = dict(ao)
695 assert am['x'] == am2['x']
701 assert am['x'] == am2['x']
696 assert am['y']['z'] == am2['y']['z']
702 assert am['y']['z'] == am2['y']['z']
697
703
@@ -1,177 +1,188 b''
1 """test building messages with streamsession"""
1 """test building messages with streamsession"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import uuid
15 import uuid
16 import zmq
16 import zmq
17
17
18 from zmq.tests import BaseZMQTestCase
18 from zmq.tests import BaseZMQTestCase
19 from zmq.eventloop.zmqstream import ZMQStream
19 from zmq.eventloop.zmqstream import ZMQStream
20
20
21 from IPython.zmq import session as ss
21 from IPython.zmq import session as ss
22
22
23 class SessionTestCase(BaseZMQTestCase):
23 class SessionTestCase(BaseZMQTestCase):
24
24
25 def setUp(self):
25 def setUp(self):
26 BaseZMQTestCase.setUp(self)
26 BaseZMQTestCase.setUp(self)
27 self.session = ss.Session()
27 self.session = ss.Session()
28
28
29
29
30 class MockSocket(zmq.Socket):
30 class MockSocket(zmq.Socket):
31
31
32 def __init__(self, *args, **kwargs):
32 def __init__(self, *args, **kwargs):
33 super(MockSocket,self).__init__(*args,**kwargs)
33 super(MockSocket,self).__init__(*args,**kwargs)
34 self.data = []
34 self.data = []
35
35
36 def send_multipart(self, msgparts, *args, **kwargs):
36 def send_multipart(self, msgparts, *args, **kwargs):
37 self.data.extend(msgparts)
37 self.data.extend(msgparts)
38
38
39 def send(self, part, *args, **kwargs):
39 def send(self, part, *args, **kwargs):
40 self.data.append(part)
40 self.data.append(part)
41
41
42 def recv_multipart(self, *args, **kwargs):
42 def recv_multipart(self, *args, **kwargs):
43 return self.data
43 return self.data
44
44
45 class TestSession(SessionTestCase):
45 class TestSession(SessionTestCase):
46
46
47 def test_msg(self):
47 def test_msg(self):
48 """message format"""
48 """message format"""
49 msg = self.session.msg('execute')
49 msg = self.session.msg('execute')
50 thekeys = set('header parent_header content'.split())
50 thekeys = set('header parent_header content msg_type msg_id'.split())
51 s = set(msg.keys())
51 s = set(msg.keys())
52 self.assertEquals(s, thekeys)
52 self.assertEquals(s, thekeys)
53 self.assertTrue(isinstance(msg['content'],dict))
53 self.assertTrue(isinstance(msg['content'],dict))
54 self.assertTrue(isinstance(msg['header'],dict))
54 self.assertTrue(isinstance(msg['header'],dict))
55 self.assertTrue(isinstance(msg['parent_header'],dict))
55 self.assertTrue(isinstance(msg['parent_header'],dict))
56 self.assertTrue(isinstance(msg['msg_id'],str))
57 self.assertTrue(isinstance(msg['msg_type'],str))
56 self.assertEquals(msg['header']['msg_type'], 'execute')
58 self.assertEquals(msg['header']['msg_type'], 'execute')
59 self.assertEquals(msg['msg_type'], 'execute')
57
60
58 def test_serialize(self):
61 def test_serialize(self):
59 msg = self.session.msg('execute',content=dict(a=10))
62 msg = self.session.msg('execute',content=dict(a=10))
60 msg_list = self.session.serialize(msg, ident=b'foo')
63 msg_list = self.session.serialize(msg, ident=b'foo')
61 ident, msg_list = self.session.feed_identities(msg_list)
64 ident, msg_list = self.session.feed_identities(msg_list)
62 new_msg = self.session.unserialize(msg_list)
65 new_msg = self.session.unserialize(msg_list)
63 self.assertEquals(ident[0], b'foo')
66 self.assertEquals(ident[0], b'foo')
67 self.assertEquals(new_msg['msg_id'],msg['msg_id'])
68 self.assertEquals(new_msg['msg_type'],msg['msg_type'])
64 self.assertEquals(new_msg['header'],msg['header'])
69 self.assertEquals(new_msg['header'],msg['header'])
65 self.assertEquals(new_msg['content'],msg['content'])
70 self.assertEquals(new_msg['content'],msg['content'])
66 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
71 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
67
72
68 def test_send(self):
73 def test_send(self):
69 socket = MockSocket(zmq.Context.instance(),zmq.PAIR)
74 socket = MockSocket(zmq.Context.instance(),zmq.PAIR)
70
75
71 msg = self.session.msg('execute', content=dict(a=10))
76 msg = self.session.msg('execute', content=dict(a=10))
72 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
77 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
73 ident, msg_list = self.session.feed_identities(socket.data)
78 ident, msg_list = self.session.feed_identities(socket.data)
74 new_msg = self.session.unserialize(msg_list)
79 new_msg = self.session.unserialize(msg_list)
75 self.assertEquals(ident[0], b'foo')
80 self.assertEquals(ident[0], b'foo')
81 self.assertEquals(new_msg['msg_id'],msg['msg_id'])
82 self.assertEquals(new_msg['msg_type'],msg['msg_type'])
76 self.assertEquals(new_msg['header'],msg['header'])
83 self.assertEquals(new_msg['header'],msg['header'])
77 self.assertEquals(new_msg['content'],msg['content'])
84 self.assertEquals(new_msg['content'],msg['content'])
78 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
85 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
79 self.assertEquals(new_msg['buffers'],[b'bar'])
86 self.assertEquals(new_msg['buffers'],[b'bar'])
80
87
81 socket.data = []
88 socket.data = []
82
89
83 content = msg['content']
90 content = msg['content']
84 header = msg['header']
91 header = msg['header']
85 parent = msg['parent_header']
92 parent = msg['parent_header']
86 msg_type = header['msg_type']
93 msg_type = header['msg_type']
87 self.session.send(socket, None, content=content, parent=parent,
94 self.session.send(socket, None, content=content, parent=parent,
88 header=header, ident=b'foo', buffers=[b'bar'])
95 header=header, ident=b'foo', buffers=[b'bar'])
89 ident, msg_list = self.session.feed_identities(socket.data)
96 ident, msg_list = self.session.feed_identities(socket.data)
90 new_msg = self.session.unserialize(msg_list)
97 new_msg = self.session.unserialize(msg_list)
91 self.assertEquals(ident[0], b'foo')
98 self.assertEquals(ident[0], b'foo')
99 self.assertEquals(new_msg['msg_id'],msg['msg_id'])
100 self.assertEquals(new_msg['msg_type'],msg['msg_type'])
92 self.assertEquals(new_msg['header'],msg['header'])
101 self.assertEquals(new_msg['header'],msg['header'])
93 self.assertEquals(new_msg['content'],msg['content'])
102 self.assertEquals(new_msg['content'],msg['content'])
94 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
103 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
95 self.assertEquals(new_msg['buffers'],[b'bar'])
104 self.assertEquals(new_msg['buffers'],[b'bar'])
96
105
97 socket.data = []
106 socket.data = []
98
107
99 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
108 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
100 ident, new_msg = self.session.recv(socket)
109 ident, new_msg = self.session.recv(socket)
101 self.assertEquals(ident[0], b'foo')
110 self.assertEquals(ident[0], b'foo')
111 self.assertEquals(new_msg['msg_id'],msg['msg_id'])
112 self.assertEquals(new_msg['msg_type'],msg['msg_type'])
102 self.assertEquals(new_msg['header'],msg['header'])
113 self.assertEquals(new_msg['header'],msg['header'])
103 self.assertEquals(new_msg['content'],msg['content'])
114 self.assertEquals(new_msg['content'],msg['content'])
104 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
115 self.assertEquals(new_msg['parent_header'],msg['parent_header'])
105 self.assertEquals(new_msg['buffers'],[b'bar'])
116 self.assertEquals(new_msg['buffers'],[b'bar'])
106
117
107 socket.close()
118 socket.close()
108
119
109 def test_args(self):
120 def test_args(self):
110 """initialization arguments for Session"""
121 """initialization arguments for Session"""
111 s = self.session
122 s = self.session
112 self.assertTrue(s.pack is ss.default_packer)
123 self.assertTrue(s.pack is ss.default_packer)
113 self.assertTrue(s.unpack is ss.default_unpacker)
124 self.assertTrue(s.unpack is ss.default_unpacker)
114 self.assertEquals(s.username, os.environ.get('USER', u'username'))
125 self.assertEquals(s.username, os.environ.get('USER', u'username'))
115
126
116 s = ss.Session()
127 s = ss.Session()
117 self.assertEquals(s.username, os.environ.get('USER', u'username'))
128 self.assertEquals(s.username, os.environ.get('USER', u'username'))
118
129
119 self.assertRaises(TypeError, ss.Session, pack='hi')
130 self.assertRaises(TypeError, ss.Session, pack='hi')
120 self.assertRaises(TypeError, ss.Session, unpack='hi')
131 self.assertRaises(TypeError, ss.Session, unpack='hi')
121 u = str(uuid.uuid4())
132 u = str(uuid.uuid4())
122 s = ss.Session(username=u'carrot', session=u)
133 s = ss.Session(username=u'carrot', session=u)
123 self.assertEquals(s.session, u)
134 self.assertEquals(s.session, u)
124 self.assertEquals(s.username, u'carrot')
135 self.assertEquals(s.username, u'carrot')
125
136
126 def test_tracking(self):
137 def test_tracking(self):
127 """test tracking messages"""
138 """test tracking messages"""
128 a,b = self.create_bound_pair(zmq.PAIR, zmq.PAIR)
139 a,b = self.create_bound_pair(zmq.PAIR, zmq.PAIR)
129 s = self.session
140 s = self.session
130 stream = ZMQStream(a)
141 stream = ZMQStream(a)
131 msg = s.send(a, 'hello', track=False)
142 msg = s.send(a, 'hello', track=False)
132 self.assertTrue(msg['tracker'] is None)
143 self.assertTrue(msg['tracker'] is None)
133 msg = s.send(a, 'hello', track=True)
144 msg = s.send(a, 'hello', track=True)
134 self.assertTrue(isinstance(msg['tracker'], zmq.MessageTracker))
145 self.assertTrue(isinstance(msg['tracker'], zmq.MessageTracker))
135 M = zmq.Message(b'hi there', track=True)
146 M = zmq.Message(b'hi there', track=True)
136 msg = s.send(a, 'hello', buffers=[M], track=True)
147 msg = s.send(a, 'hello', buffers=[M], track=True)
137 t = msg['tracker']
148 t = msg['tracker']
138 self.assertTrue(isinstance(t, zmq.MessageTracker))
149 self.assertTrue(isinstance(t, zmq.MessageTracker))
139 self.assertRaises(zmq.NotDone, t.wait, .1)
150 self.assertRaises(zmq.NotDone, t.wait, .1)
140 del M
151 del M
141 t.wait(1) # this will raise
152 t.wait(1) # this will raise
142
153
143
154
144 # def test_rekey(self):
155 # def test_rekey(self):
145 # """rekeying dict around json str keys"""
156 # """rekeying dict around json str keys"""
146 # d = {'0': uuid.uuid4(), 0:uuid.uuid4()}
157 # d = {'0': uuid.uuid4(), 0:uuid.uuid4()}
147 # self.assertRaises(KeyError, ss.rekey, d)
158 # self.assertRaises(KeyError, ss.rekey, d)
148 #
159 #
149 # d = {'0': uuid.uuid4(), 1:uuid.uuid4(), 'asdf':uuid.uuid4()}
160 # d = {'0': uuid.uuid4(), 1:uuid.uuid4(), 'asdf':uuid.uuid4()}
150 # d2 = {0:d['0'],1:d[1],'asdf':d['asdf']}
161 # d2 = {0:d['0'],1:d[1],'asdf':d['asdf']}
151 # rd = ss.rekey(d)
162 # rd = ss.rekey(d)
152 # self.assertEquals(d2,rd)
163 # self.assertEquals(d2,rd)
153 #
164 #
154 # d = {'1.5':uuid.uuid4(),'1':uuid.uuid4()}
165 # d = {'1.5':uuid.uuid4(),'1':uuid.uuid4()}
155 # d2 = {1.5:d['1.5'],1:d['1']}
166 # d2 = {1.5:d['1.5'],1:d['1']}
156 # rd = ss.rekey(d)
167 # rd = ss.rekey(d)
157 # self.assertEquals(d2,rd)
168 # self.assertEquals(d2,rd)
158 #
169 #
159 # d = {'1.0':uuid.uuid4(),'1':uuid.uuid4()}
170 # d = {'1.0':uuid.uuid4(),'1':uuid.uuid4()}
160 # self.assertRaises(KeyError, ss.rekey, d)
171 # self.assertRaises(KeyError, ss.rekey, d)
161 #
172 #
162 def test_unique_msg_ids(self):
173 def test_unique_msg_ids(self):
163 """test that messages receive unique ids"""
174 """test that messages receive unique ids"""
164 ids = set()
175 ids = set()
165 for i in range(2**12):
176 for i in range(2**12):
166 h = self.session.msg_header('test')
177 h = self.session.msg_header('test')
167 msg_id = h['msg_id']
178 msg_id = h['msg_id']
168 self.assertTrue(msg_id not in ids)
179 self.assertTrue(msg_id not in ids)
169 ids.add(msg_id)
180 ids.add(msg_id)
170
181
171 def test_feed_identities(self):
182 def test_feed_identities(self):
172 """scrub the front for zmq IDENTITIES"""
183 """scrub the front for zmq IDENTITIES"""
173 theids = "engine client other".split()
184 theids = "engine client other".split()
174 content = dict(code='whoda',stuff=object())
185 content = dict(code='whoda',stuff=object())
175 themsg = self.session.msg('execute',content=content)
186 themsg = self.session.msg('execute',content=content)
176 pmsg = theids
187 pmsg = theids
177
188
@@ -1,937 +1,941 b''
1 .. _messaging:
1 .. _messaging:
2
2
3 ======================
3 ======================
4 Messaging in IPython
4 Messaging in IPython
5 ======================
5 ======================
6
6
7
7
8 Introduction
8 Introduction
9 ============
9 ============
10
10
11 This document explains the basic communications design and messaging
11 This document explains the basic communications design and messaging
12 specification for how the various IPython objects interact over a network
12 specification for how the various IPython objects interact over a network
13 transport. The current implementation uses the ZeroMQ_ library for messaging
13 transport. The current implementation uses the ZeroMQ_ library for messaging
14 within and between hosts.
14 within and between hosts.
15
15
16 .. Note::
16 .. Note::
17
17
18 This document should be considered the authoritative description of the
18 This document should be considered the authoritative description of the
19 IPython messaging protocol, and all developers are strongly encouraged to
19 IPython messaging protocol, and all developers are strongly encouraged to
20 keep it updated as the implementation evolves, so that we have a single
20 keep it updated as the implementation evolves, so that we have a single
21 common reference for all protocol details.
21 common reference for all protocol details.
22
22
23 The basic design is explained in the following diagram:
23 The basic design is explained in the following diagram:
24
24
25 .. image:: figs/frontend-kernel.png
25 .. image:: figs/frontend-kernel.png
26 :width: 450px
26 :width: 450px
27 :alt: IPython kernel/frontend messaging architecture.
27 :alt: IPython kernel/frontend messaging architecture.
28 :align: center
28 :align: center
29 :target: ../_images/frontend-kernel.png
29 :target: ../_images/frontend-kernel.png
30
30
31 A single kernel can be simultaneously connected to one or more frontends. The
31 A single kernel can be simultaneously connected to one or more frontends. The
32 kernel has three sockets that serve the following functions:
32 kernel has three sockets that serve the following functions:
33
33
34 1. REQ: this socket is connected to a *single* frontend at a time, and it allows
34 1. REQ: this socket is connected to a *single* frontend at a time, and it allows
35 the kernel to request input from a frontend when :func:`raw_input` is called.
35 the kernel to request input from a frontend when :func:`raw_input` is called.
36 The frontend holding the matching REP socket acts as a 'virtual keyboard'
36 The frontend holding the matching REP socket acts as a 'virtual keyboard'
37 for the kernel while this communication is happening (illustrated in the
37 for the kernel while this communication is happening (illustrated in the
38 figure by the black outline around the central keyboard). In practice,
38 figure by the black outline around the central keyboard). In practice,
39 frontends may display such kernel requests using a special input widget or
39 frontends may display such kernel requests using a special input widget or
40 otherwise indicating that the user is to type input for the kernel instead
40 otherwise indicating that the user is to type input for the kernel instead
41 of normal commands in the frontend.
41 of normal commands in the frontend.
42
42
43 2. XREP: this single sockets allows multiple incoming connections from
43 2. XREP: this single sockets allows multiple incoming connections from
44 frontends, and this is the socket where requests for code execution, object
44 frontends, and this is the socket where requests for code execution, object
45 information, prompts, etc. are made to the kernel by any frontend. The
45 information, prompts, etc. are made to the kernel by any frontend. The
46 communication on this socket is a sequence of request/reply actions from
46 communication on this socket is a sequence of request/reply actions from
47 each frontend and the kernel.
47 each frontend and the kernel.
48
48
49 3. PUB: this socket is the 'broadcast channel' where the kernel publishes all
49 3. PUB: this socket is the 'broadcast channel' where the kernel publishes all
50 side effects (stdout, stderr, etc.) as well as the requests coming from any
50 side effects (stdout, stderr, etc.) as well as the requests coming from any
51 client over the XREP socket and its own requests on the REP socket. There
51 client over the XREP socket and its own requests on the REP socket. There
52 are a number of actions in Python which generate side effects: :func:`print`
52 are a number of actions in Python which generate side effects: :func:`print`
53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
54 a multi-client scenario, we want all frontends to be able to know what each
54 a multi-client scenario, we want all frontends to be able to know what each
55 other has sent to the kernel (this can be useful in collaborative scenarios,
55 other has sent to the kernel (this can be useful in collaborative scenarios,
56 for example). This socket allows both side effects and the information
56 for example). This socket allows both side effects and the information
57 about communications taking place with one client over the XREQ/XREP channel
57 about communications taking place with one client over the XREQ/XREP channel
58 to be made available to all clients in a uniform manner.
58 to be made available to all clients in a uniform manner.
59
59
60 All messages are tagged with enough information (details below) for clients
60 All messages are tagged with enough information (details below) for clients
61 to know which messages come from their own interaction with the kernel and
61 to know which messages come from their own interaction with the kernel and
62 which ones are from other clients, so they can display each type
62 which ones are from other clients, so they can display each type
63 appropriately.
63 appropriately.
64
64
65 The actual format of the messages allowed on each of these channels is
65 The actual format of the messages allowed on each of these channels is
66 specified below. Messages are dicts of dicts with string keys and values that
66 specified below. Messages are dicts of dicts with string keys and values that
67 are reasonably representable in JSON. Our current implementation uses JSON
67 are reasonably representable in JSON. Our current implementation uses JSON
68 explicitly as its message format, but this shouldn't be considered a permanent
68 explicitly as its message format, but this shouldn't be considered a permanent
69 feature. As we've discovered that JSON has non-trivial performance issues due
69 feature. As we've discovered that JSON has non-trivial performance issues due
70 to excessive copying, we may in the future move to a pure pickle-based raw
70 to excessive copying, we may in the future move to a pure pickle-based raw
71 message format. However, it should be possible to easily convert from the raw
71 message format. However, it should be possible to easily convert from the raw
72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
73 As long as it's easy to make a JSON version of the objects that is a faithful
73 As long as it's easy to make a JSON version of the objects that is a faithful
74 representation of all the data, we can communicate with such clients.
74 representation of all the data, we can communicate with such clients.
75
75
76 .. Note::
76 .. Note::
77
77
78 Not all of these have yet been fully fleshed out, but the key ones are, see
78 Not all of these have yet been fully fleshed out, but the key ones are, see
79 kernel and frontend files for actual implementation details.
79 kernel and frontend files for actual implementation details.
80
80
81
81
82 Python functional API
82 Python functional API
83 =====================
83 =====================
84
84
85 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
85 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
86 should develop, at a few key points, functional forms of all the requests that
86 should develop, at a few key points, functional forms of all the requests that
87 take arguments in this manner and automatically construct the necessary dict
87 take arguments in this manner and automatically construct the necessary dict
88 for sending.
88 for sending.
89
89
90
90
91 General Message Format
91 General Message Format
92 ======================
92 ======================
93
93
94 All messages send or received by any IPython process should have the following
94 All messages send or received by any IPython process should have the following
95 generic structure::
95 generic structure::
96
96
97 {
97 {
98 # The message header contains a pair of unique identifiers for the
98 # The message header contains a pair of unique identifiers for the
99 # originating session and the actual message id, in addition to the
99 # originating session and the actual message id, in addition to the
100 # username for the process that generated the message. This is useful in
100 # username for the process that generated the message. This is useful in
101 # collaborative settings where multiple users may be interacting with the
101 # collaborative settings where multiple users may be interacting with the
102 # same kernel simultaneously, so that frontends can label the various
102 # same kernel simultaneously, so that frontends can label the various
103 # messages in a meaningful way.
103 # messages in a meaningful way.
104 'header' : {
104 'header' : {
105 'msg_id' : uuid,
105 'msg_id' : uuid,
106 'username' : str,
106 'username' : str,
107 'session' : uuid
107 'session' : uuid
108 # All recognized message type strings are listed below.
108 # All recognized message type strings are listed below.
109 'msg_type' : str,
109 'msg_type' : str,
110 },
110 },
111 # The msg's unique identifier and type are stored in the header, but
112 # are also accessible at the top-level for convenience.
113 'msg_id' : uuid,
114 'msg_type' : str,
111
115
112 # In a chain of messages, the header from the parent is copied so that
116 # In a chain of messages, the header from the parent is copied so that
113 # clients can track where messages come from.
117 # clients can track where messages come from.
114 'parent_header' : dict,
118 'parent_header' : dict,
115
119
116 # The actual content of the message must be a dict, whose structure
120 # The actual content of the message must be a dict, whose structure
117 # depends on the message type.x
121 # depends on the message type.x
118 'content' : dict,
122 'content' : dict,
119 }
123 }
120
124
121 For each message type, the actual content will differ and all existing message
125 For each message type, the actual content will differ and all existing message
122 types are specified in what follows of this document.
126 types are specified in what follows of this document.
123
127
124
128
125 Messages on the XREP/XREQ socket
129 Messages on the XREP/XREQ socket
126 ================================
130 ================================
127
131
128 .. _execute:
132 .. _execute:
129
133
130 Execute
134 Execute
131 -------
135 -------
132
136
133 This message type is used by frontends to ask the kernel to execute code on
137 This message type is used by frontends to ask the kernel to execute code on
134 behalf of the user, in a namespace reserved to the user's variables (and thus
138 behalf of the user, in a namespace reserved to the user's variables (and thus
135 separate from the kernel's own internal code and variables).
139 separate from the kernel's own internal code and variables).
136
140
137 Message type: ``execute_request``::
141 Message type: ``execute_request``::
138
142
139 content = {
143 content = {
140 # Source code to be executed by the kernel, one or more lines.
144 # Source code to be executed by the kernel, one or more lines.
141 'code' : str,
145 'code' : str,
142
146
143 # A boolean flag which, if True, signals the kernel to execute this
147 # A boolean flag which, if True, signals the kernel to execute this
144 # code as quietly as possible. This means that the kernel will compile
148 # code as quietly as possible. This means that the kernel will compile
145 # the code witIPython/core/tests/h 'exec' instead of 'single' (so
149 # the code witIPython/core/tests/h 'exec' instead of 'single' (so
146 # sys.displayhook will not fire), and will *not*:
150 # sys.displayhook will not fire), and will *not*:
147 # - broadcast exceptions on the PUB socket
151 # - broadcast exceptions on the PUB socket
148 # - do any logging
152 # - do any logging
149 # - populate any history
153 # - populate any history
150 #
154 #
151 # The default is False.
155 # The default is False.
152 'silent' : bool,
156 'silent' : bool,
153
157
154 # A list of variable names from the user's namespace to be retrieved. What
158 # A list of variable names from the user's namespace to be retrieved. What
155 # returns is a JSON string of the variable's repr(), not a python object.
159 # returns is a JSON string of the variable's repr(), not a python object.
156 'user_variables' : list,
160 'user_variables' : list,
157
161
158 # Similarly, a dict mapping names to expressions to be evaluated in the
162 # Similarly, a dict mapping names to expressions to be evaluated in the
159 # user's dict.
163 # user's dict.
160 'user_expressions' : dict,
164 'user_expressions' : dict,
161 }
165 }
162
166
163 The ``code`` field contains a single string (possibly multiline). The kernel
167 The ``code`` field contains a single string (possibly multiline). The kernel
164 is responsible for splitting this into one or more independent execution blocks
168 is responsible for splitting this into one or more independent execution blocks
165 and deciding whether to compile these in 'single' or 'exec' mode (see below for
169 and deciding whether to compile these in 'single' or 'exec' mode (see below for
166 detailed execution semantics).
170 detailed execution semantics).
167
171
168 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
172 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
169 the notion of a prompt string that allowed arbitrary code to be evaluated, and
173 the notion of a prompt string that allowed arbitrary code to be evaluated, and
170 this was put to good use by many in creating prompts that displayed system
174 this was put to good use by many in creating prompts that displayed system
171 status, path information, and even more esoteric uses like remote instrument
175 status, path information, and even more esoteric uses like remote instrument
172 status aqcuired over the network. But now that IPython has a clean separation
176 status aqcuired over the network. But now that IPython has a clean separation
173 between the kernel and the clients, the kernel has no prompt knowledge; prompts
177 between the kernel and the clients, the kernel has no prompt knowledge; prompts
174 are a frontend-side feature, and it should be even possible for different
178 are a frontend-side feature, and it should be even possible for different
175 frontends to display different prompts while interacting with the same kernel.
179 frontends to display different prompts while interacting with the same kernel.
176
180
177 The kernel now provides the ability to retrieve data from the user's namespace
181 The kernel now provides the ability to retrieve data from the user's namespace
178 after the execution of the main ``code``, thanks to two fields in the
182 after the execution of the main ``code``, thanks to two fields in the
179 ``execute_request`` message:
183 ``execute_request`` message:
180
184
181 - ``user_variables``: If only variables from the user's namespace are needed, a
185 - ``user_variables``: If only variables from the user's namespace are needed, a
182 list of variable names can be passed and a dict with these names as keys and
186 list of variable names can be passed and a dict with these names as keys and
183 their :func:`repr()` as values will be returned.
187 their :func:`repr()` as values will be returned.
184
188
185 - ``user_expressions``: For more complex expressions that require function
189 - ``user_expressions``: For more complex expressions that require function
186 evaluations, a dict can be provided with string keys and arbitrary python
190 evaluations, a dict can be provided with string keys and arbitrary python
187 expressions as values. The return message will contain also a dict with the
191 expressions as values. The return message will contain also a dict with the
188 same keys and the :func:`repr()` of the evaluated expressions as value.
192 same keys and the :func:`repr()` of the evaluated expressions as value.
189
193
190 With this information, frontends can display any status information they wish
194 With this information, frontends can display any status information they wish
191 in the form that best suits each frontend (a status line, a popup, inline for a
195 in the form that best suits each frontend (a status line, a popup, inline for a
192 terminal, etc).
196 terminal, etc).
193
197
194 .. Note::
198 .. Note::
195
199
196 In order to obtain the current execution counter for the purposes of
200 In order to obtain the current execution counter for the purposes of
197 displaying input prompts, frontends simply make an execution request with an
201 displaying input prompts, frontends simply make an execution request with an
198 empty code string and ``silent=True``.
202 empty code string and ``silent=True``.
199
203
200 Execution semantics
204 Execution semantics
201 ~~~~~~~~~~~~~~~~~~~
205 ~~~~~~~~~~~~~~~~~~~
202
206
203 When the silent flag is false, the execution of use code consists of the
207 When the silent flag is false, the execution of use code consists of the
204 following phases (in silent mode, only the ``code`` field is executed):
208 following phases (in silent mode, only the ``code`` field is executed):
205
209
206 1. Run the ``pre_runcode_hook``.
210 1. Run the ``pre_runcode_hook``.
207
211
208 2. Execute the ``code`` field, see below for details.
212 2. Execute the ``code`` field, see below for details.
209
213
210 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
214 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
211 computed. This ensures that any error in the latter don't harm the main
215 computed. This ensures that any error in the latter don't harm the main
212 code execution.
216 code execution.
213
217
214 4. Call any method registered with :meth:`register_post_execute`.
218 4. Call any method registered with :meth:`register_post_execute`.
215
219
216 .. warning::
220 .. warning::
217
221
218 The API for running code before/after the main code block is likely to
222 The API for running code before/after the main code block is likely to
219 change soon. Both the ``pre_runcode_hook`` and the
223 change soon. Both the ``pre_runcode_hook`` and the
220 :meth:`register_post_execute` are susceptible to modification, as we find a
224 :meth:`register_post_execute` are susceptible to modification, as we find a
221 consistent model for both.
225 consistent model for both.
222
226
223 To understand how the ``code`` field is executed, one must know that Python
227 To understand how the ``code`` field is executed, one must know that Python
224 code can be compiled in one of three modes (controlled by the ``mode`` argument
228 code can be compiled in one of three modes (controlled by the ``mode`` argument
225 to the :func:`compile` builtin):
229 to the :func:`compile` builtin):
226
230
227 *single*
231 *single*
228 Valid for a single interactive statement (though the source can contain
232 Valid for a single interactive statement (though the source can contain
229 multiple lines, such as a for loop). When compiled in this mode, the
233 multiple lines, such as a for loop). When compiled in this mode, the
230 generated bytecode contains special instructions that trigger the calling of
234 generated bytecode contains special instructions that trigger the calling of
231 :func:`sys.displayhook` for any expression in the block that returns a value.
235 :func:`sys.displayhook` for any expression in the block that returns a value.
232 This means that a single statement can actually produce multiple calls to
236 This means that a single statement can actually produce multiple calls to
233 :func:`sys.displayhook`, if for example it contains a loop where each
237 :func:`sys.displayhook`, if for example it contains a loop where each
234 iteration computes an unassigned expression would generate 10 calls::
238 iteration computes an unassigned expression would generate 10 calls::
235
239
236 for i in range(10):
240 for i in range(10):
237 i**2
241 i**2
238
242
239 *exec*
243 *exec*
240 An arbitrary amount of source code, this is how modules are compiled.
244 An arbitrary amount of source code, this is how modules are compiled.
241 :func:`sys.displayhook` is *never* implicitly called.
245 :func:`sys.displayhook` is *never* implicitly called.
242
246
243 *eval*
247 *eval*
244 A single expression that returns a value. :func:`sys.displayhook` is *never*
248 A single expression that returns a value. :func:`sys.displayhook` is *never*
245 implicitly called.
249 implicitly called.
246
250
247
251
248 The ``code`` field is split into individual blocks each of which is valid for
252 The ``code`` field is split into individual blocks each of which is valid for
249 execution in 'single' mode, and then:
253 execution in 'single' mode, and then:
250
254
251 - If there is only a single block: it is executed in 'single' mode.
255 - If there is only a single block: it is executed in 'single' mode.
252
256
253 - If there is more than one block:
257 - If there is more than one block:
254
258
255 * if the last one is a single line long, run all but the last in 'exec' mode
259 * if the last one is a single line long, run all but the last in 'exec' mode
256 and the very last one in 'single' mode. This makes it easy to type simple
260 and the very last one in 'single' mode. This makes it easy to type simple
257 expressions at the end to see computed values.
261 expressions at the end to see computed values.
258
262
259 * if the last one is no more than two lines long, run all but the last in
263 * if the last one is no more than two lines long, run all but the last in
260 'exec' mode and the very last one in 'single' mode. This makes it easy to
264 'exec' mode and the very last one in 'single' mode. This makes it easy to
261 type simple expressions at the end to see computed values. - otherwise
265 type simple expressions at the end to see computed values. - otherwise
262 (last one is also multiline), run all in 'exec' mode
266 (last one is also multiline), run all in 'exec' mode
263
267
264 * otherwise (last one is also multiline), run all in 'exec' mode as a single
268 * otherwise (last one is also multiline), run all in 'exec' mode as a single
265 unit.
269 unit.
266
270
267 Any error in retrieving the ``user_variables`` or evaluating the
271 Any error in retrieving the ``user_variables`` or evaluating the
268 ``user_expressions`` will result in a simple error message in the return fields
272 ``user_expressions`` will result in a simple error message in the return fields
269 of the form::
273 of the form::
270
274
271 [ERROR] ExceptionType: Exception message
275 [ERROR] ExceptionType: Exception message
272
276
273 The user can simply send the same variable name or expression for evaluation to
277 The user can simply send the same variable name or expression for evaluation to
274 see a regular traceback.
278 see a regular traceback.
275
279
276 Errors in any registered post_execute functions are also reported similarly,
280 Errors in any registered post_execute functions are also reported similarly,
277 and the failing function is removed from the post_execution set so that it does
281 and the failing function is removed from the post_execution set so that it does
278 not continue triggering failures.
282 not continue triggering failures.
279
283
280 Upon completion of the execution request, the kernel *always* sends a reply,
284 Upon completion of the execution request, the kernel *always* sends a reply,
281 with a status code indicating what happened and additional data depending on
285 with a status code indicating what happened and additional data depending on
282 the outcome. See :ref:`below <execution_results>` for the possible return
286 the outcome. See :ref:`below <execution_results>` for the possible return
283 codes and associated data.
287 codes and associated data.
284
288
285
289
286 Execution counter (old prompt number)
290 Execution counter (old prompt number)
287 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
291 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288
292
289 The kernel has a single, monotonically increasing counter of all execution
293 The kernel has a single, monotonically increasing counter of all execution
290 requests that are made with ``silent=False``. This counter is used to populate
294 requests that are made with ``silent=False``. This counter is used to populate
291 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
295 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
292 display it in some form to the user, which will typically (but not necessarily)
296 display it in some form to the user, which will typically (but not necessarily)
293 be done in the prompts. The value of this counter will be returned as the
297 be done in the prompts. The value of this counter will be returned as the
294 ``execution_count`` field of all ``execute_reply`` messages.
298 ``execution_count`` field of all ``execute_reply`` messages.
295
299
296 .. _execution_results:
300 .. _execution_results:
297
301
298 Execution results
302 Execution results
299 ~~~~~~~~~~~~~~~~~
303 ~~~~~~~~~~~~~~~~~
300
304
301 Message type: ``execute_reply``::
305 Message type: ``execute_reply``::
302
306
303 content = {
307 content = {
304 # One of: 'ok' OR 'error' OR 'abort'
308 # One of: 'ok' OR 'error' OR 'abort'
305 'status' : str,
309 'status' : str,
306
310
307 # The global kernel counter that increases by one with each non-silent
311 # The global kernel counter that increases by one with each non-silent
308 # executed request. This will typically be used by clients to display
312 # executed request. This will typically be used by clients to display
309 # prompt numbers to the user. If the request was a silent one, this will
313 # prompt numbers to the user. If the request was a silent one, this will
310 # be the current value of the counter in the kernel.
314 # be the current value of the counter in the kernel.
311 'execution_count' : int,
315 'execution_count' : int,
312 }
316 }
313
317
314 When status is 'ok', the following extra fields are present::
318 When status is 'ok', the following extra fields are present::
315
319
316 {
320 {
317 # The execution payload is a dict with string keys that may have been
321 # The execution payload is a dict with string keys that may have been
318 # produced by the code being executed. It is retrieved by the kernel at
322 # produced by the code being executed. It is retrieved by the kernel at
319 # the end of the execution and sent back to the front end, which can take
323 # the end of the execution and sent back to the front end, which can take
320 # action on it as needed. See main text for further details.
324 # action on it as needed. See main text for further details.
321 'payload' : dict,
325 'payload' : dict,
322
326
323 # Results for the user_variables and user_expressions.
327 # Results for the user_variables and user_expressions.
324 'user_variables' : dict,
328 'user_variables' : dict,
325 'user_expressions' : dict,
329 'user_expressions' : dict,
326
330
327 # The kernel will often transform the input provided to it. If the
331 # The kernel will often transform the input provided to it. If the
328 # '---->' transform had been applied, this is filled, otherwise it's the
332 # '---->' transform had been applied, this is filled, otherwise it's the
329 # empty string. So transformations like magics don't appear here, only
333 # empty string. So transformations like magics don't appear here, only
330 # autocall ones.
334 # autocall ones.
331 'transformed_code' : str,
335 'transformed_code' : str,
332 }
336 }
333
337
334 .. admonition:: Execution payloads
338 .. admonition:: Execution payloads
335
339
336 The notion of an 'execution payload' is different from a return value of a
340 The notion of an 'execution payload' is different from a return value of a
337 given set of code, which normally is just displayed on the pyout stream
341 given set of code, which normally is just displayed on the pyout stream
338 through the PUB socket. The idea of a payload is to allow special types of
342 through the PUB socket. The idea of a payload is to allow special types of
339 code, typically magics, to populate a data container in the IPython kernel
343 code, typically magics, to populate a data container in the IPython kernel
340 that will be shipped back to the caller via this channel. The kernel will
344 that will be shipped back to the caller via this channel. The kernel will
341 have an API for this, probably something along the lines of::
345 have an API for this, probably something along the lines of::
342
346
343 ip.exec_payload_add(key, value)
347 ip.exec_payload_add(key, value)
344
348
345 though this API is still in the design stages. The data returned in this
349 though this API is still in the design stages. The data returned in this
346 payload will allow frontends to present special views of what just happened.
350 payload will allow frontends to present special views of what just happened.
347
351
348
352
349 When status is 'error', the following extra fields are present::
353 When status is 'error', the following extra fields are present::
350
354
351 {
355 {
352 'exc_name' : str, # Exception name, as a string
356 'exc_name' : str, # Exception name, as a string
353 'exc_value' : str, # Exception value, as a string
357 'exc_value' : str, # Exception value, as a string
354
358
355 # The traceback will contain a list of frames, represented each as a
359 # The traceback will contain a list of frames, represented each as a
356 # string. For now we'll stick to the existing design of ultraTB, which
360 # string. For now we'll stick to the existing design of ultraTB, which
357 # controls exception level of detail statefully. But eventually we'll
361 # controls exception level of detail statefully. But eventually we'll
358 # want to grow into a model where more information is collected and
362 # want to grow into a model where more information is collected and
359 # packed into the traceback object, with clients deciding how little or
363 # packed into the traceback object, with clients deciding how little or
360 # how much of it to unpack. But for now, let's start with a simple list
364 # how much of it to unpack. But for now, let's start with a simple list
361 # of strings, since that requires only minimal changes to ultratb as
365 # of strings, since that requires only minimal changes to ultratb as
362 # written.
366 # written.
363 'traceback' : list,
367 'traceback' : list,
364 }
368 }
365
369
366
370
367 When status is 'abort', there are for now no additional data fields. This
371 When status is 'abort', there are for now no additional data fields. This
368 happens when the kernel was interrupted by a signal.
372 happens when the kernel was interrupted by a signal.
369
373
370 Kernel attribute access
374 Kernel attribute access
371 -----------------------
375 -----------------------
372
376
373 .. warning::
377 .. warning::
374
378
375 This part of the messaging spec is not actually implemented in the kernel
379 This part of the messaging spec is not actually implemented in the kernel
376 yet.
380 yet.
377
381
378 While this protocol does not specify full RPC access to arbitrary methods of
382 While this protocol does not specify full RPC access to arbitrary methods of
379 the kernel object, the kernel does allow read (and in some cases write) access
383 the kernel object, the kernel does allow read (and in some cases write) access
380 to certain attributes.
384 to certain attributes.
381
385
382 The policy for which attributes can be read is: any attribute of the kernel, or
386 The policy for which attributes can be read is: any attribute of the kernel, or
383 its sub-objects, that belongs to a :class:`Configurable` object and has been
387 its sub-objects, that belongs to a :class:`Configurable` object and has been
384 declared at the class-level with Traits validation, is in principle accessible
388 declared at the class-level with Traits validation, is in principle accessible
385 as long as its name does not begin with a leading underscore. The attribute
389 as long as its name does not begin with a leading underscore. The attribute
386 itself will have metadata indicating whether it allows remote read and/or write
390 itself will have metadata indicating whether it allows remote read and/or write
387 access. The message spec follows for attribute read and write requests.
391 access. The message spec follows for attribute read and write requests.
388
392
389 Message type: ``getattr_request``::
393 Message type: ``getattr_request``::
390
394
391 content = {
395 content = {
392 # The (possibly dotted) name of the attribute
396 # The (possibly dotted) name of the attribute
393 'name' : str,
397 'name' : str,
394 }
398 }
395
399
396 When a ``getattr_request`` fails, there are two possible error types:
400 When a ``getattr_request`` fails, there are two possible error types:
397
401
398 - AttributeError: this type of error was raised when trying to access the
402 - AttributeError: this type of error was raised when trying to access the
399 given name by the kernel itself. This means that the attribute likely
403 given name by the kernel itself. This means that the attribute likely
400 doesn't exist.
404 doesn't exist.
401
405
402 - AccessError: the attribute exists but its value is not readable remotely.
406 - AccessError: the attribute exists but its value is not readable remotely.
403
407
404
408
405 Message type: ``getattr_reply``::
409 Message type: ``getattr_reply``::
406
410
407 content = {
411 content = {
408 # One of ['ok', 'AttributeError', 'AccessError'].
412 # One of ['ok', 'AttributeError', 'AccessError'].
409 'status' : str,
413 'status' : str,
410 # If status is 'ok', a JSON object.
414 # If status is 'ok', a JSON object.
411 'value' : object,
415 'value' : object,
412 }
416 }
413
417
414 Message type: ``setattr_request``::
418 Message type: ``setattr_request``::
415
419
416 content = {
420 content = {
417 # The (possibly dotted) name of the attribute
421 # The (possibly dotted) name of the attribute
418 'name' : str,
422 'name' : str,
419
423
420 # A JSON-encoded object, that will be validated by the Traits
424 # A JSON-encoded object, that will be validated by the Traits
421 # information in the kernel
425 # information in the kernel
422 'value' : object,
426 'value' : object,
423 }
427 }
424
428
425 When a ``setattr_request`` fails, there are also two possible error types with
429 When a ``setattr_request`` fails, there are also two possible error types with
426 similar meanings as those of the ``getattr_request`` case, but for writing.
430 similar meanings as those of the ``getattr_request`` case, but for writing.
427
431
428 Message type: ``setattr_reply``::
432 Message type: ``setattr_reply``::
429
433
430 content = {
434 content = {
431 # One of ['ok', 'AttributeError', 'AccessError'].
435 # One of ['ok', 'AttributeError', 'AccessError'].
432 'status' : str,
436 'status' : str,
433 }
437 }
434
438
435
439
436
440
437 Object information
441 Object information
438 ------------------
442 ------------------
439
443
440 One of IPython's most used capabilities is the introspection of Python objects
444 One of IPython's most used capabilities is the introspection of Python objects
441 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
445 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
442 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
446 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
443 enough that it warrants an explicit message type, especially because frontends
447 enough that it warrants an explicit message type, especially because frontends
444 may want to get object information in response to user keystrokes (like Tab or
448 may want to get object information in response to user keystrokes (like Tab or
445 F1) besides from the user explicitly typing code like ``x??``.
449 F1) besides from the user explicitly typing code like ``x??``.
446
450
447 Message type: ``object_info_request``::
451 Message type: ``object_info_request``::
448
452
449 content = {
453 content = {
450 # The (possibly dotted) name of the object to be searched in all
454 # The (possibly dotted) name of the object to be searched in all
451 # relevant namespaces
455 # relevant namespaces
452 'name' : str,
456 'name' : str,
453
457
454 # The level of detail desired. The default (0) is equivalent to typing
458 # The level of detail desired. The default (0) is equivalent to typing
455 # 'x?' at the prompt, 1 is equivalent to 'x??'.
459 # 'x?' at the prompt, 1 is equivalent to 'x??'.
456 'detail_level' : int,
460 'detail_level' : int,
457 }
461 }
458
462
459 The returned information will be a dictionary with keys very similar to the
463 The returned information will be a dictionary with keys very similar to the
460 field names that IPython prints at the terminal.
464 field names that IPython prints at the terminal.
461
465
462 Message type: ``object_info_reply``::
466 Message type: ``object_info_reply``::
463
467
464 content = {
468 content = {
465 # The name the object was requested under
469 # The name the object was requested under
466 'name' : str,
470 'name' : str,
467
471
468 # Boolean flag indicating whether the named object was found or not. If
472 # Boolean flag indicating whether the named object was found or not. If
469 # it's false, all other fields will be empty.
473 # it's false, all other fields will be empty.
470 'found' : bool,
474 'found' : bool,
471
475
472 # Flags for magics and system aliases
476 # Flags for magics and system aliases
473 'ismagic' : bool,
477 'ismagic' : bool,
474 'isalias' : bool,
478 'isalias' : bool,
475
479
476 # The name of the namespace where the object was found ('builtin',
480 # The name of the namespace where the object was found ('builtin',
477 # 'magics', 'alias', 'interactive', etc.)
481 # 'magics', 'alias', 'interactive', etc.)
478 'namespace' : str,
482 'namespace' : str,
479
483
480 # The type name will be type.__name__ for normal Python objects, but it
484 # The type name will be type.__name__ for normal Python objects, but it
481 # can also be a string like 'Magic function' or 'System alias'
485 # can also be a string like 'Magic function' or 'System alias'
482 'type_name' : str,
486 'type_name' : str,
483
487
484 # The string form of the object, possibly truncated for length if
488 # The string form of the object, possibly truncated for length if
485 # detail_level is 0
489 # detail_level is 0
486 'string_form' : str,
490 'string_form' : str,
487
491
488 # For objects with a __class__ attribute this will be set
492 # For objects with a __class__ attribute this will be set
489 'base_class' : str,
493 'base_class' : str,
490
494
491 # For objects with a __len__ attribute this will be set
495 # For objects with a __len__ attribute this will be set
492 'length' : int,
496 'length' : int,
493
497
494 # If the object is a function, class or method whose file we can find,
498 # If the object is a function, class or method whose file we can find,
495 # we give its full path
499 # we give its full path
496 'file' : str,
500 'file' : str,
497
501
498 # For pure Python callable objects, we can reconstruct the object
502 # For pure Python callable objects, we can reconstruct the object
499 # definition line which provides its call signature. For convenience this
503 # definition line which provides its call signature. For convenience this
500 # is returned as a single 'definition' field, but below the raw parts that
504 # is returned as a single 'definition' field, but below the raw parts that
501 # compose it are also returned as the argspec field.
505 # compose it are also returned as the argspec field.
502 'definition' : str,
506 'definition' : str,
503
507
504 # The individual parts that together form the definition string. Clients
508 # The individual parts that together form the definition string. Clients
505 # with rich display capabilities may use this to provide a richer and more
509 # with rich display capabilities may use this to provide a richer and more
506 # precise representation of the definition line (e.g. by highlighting
510 # precise representation of the definition line (e.g. by highlighting
507 # arguments based on the user's cursor position). For non-callable
511 # arguments based on the user's cursor position). For non-callable
508 # objects, this field is empty.
512 # objects, this field is empty.
509 'argspec' : { # The names of all the arguments
513 'argspec' : { # The names of all the arguments
510 args : list,
514 args : list,
511 # The name of the varargs (*args), if any
515 # The name of the varargs (*args), if any
512 varargs : str,
516 varargs : str,
513 # The name of the varkw (**kw), if any
517 # The name of the varkw (**kw), if any
514 varkw : str,
518 varkw : str,
515 # The values (as strings) of all default arguments. Note
519 # The values (as strings) of all default arguments. Note
516 # that these must be matched *in reverse* with the 'args'
520 # that these must be matched *in reverse* with the 'args'
517 # list above, since the first positional args have no default
521 # list above, since the first positional args have no default
518 # value at all.
522 # value at all.
519 defaults : list,
523 defaults : list,
520 },
524 },
521
525
522 # For instances, provide the constructor signature (the definition of
526 # For instances, provide the constructor signature (the definition of
523 # the __init__ method):
527 # the __init__ method):
524 'init_definition' : str,
528 'init_definition' : str,
525
529
526 # Docstrings: for any object (function, method, module, package) with a
530 # Docstrings: for any object (function, method, module, package) with a
527 # docstring, we show it. But in addition, we may provide additional
531 # docstring, we show it. But in addition, we may provide additional
528 # docstrings. For example, for instances we will show the constructor
532 # docstrings. For example, for instances we will show the constructor
529 # and class docstrings as well, if available.
533 # and class docstrings as well, if available.
530 'docstring' : str,
534 'docstring' : str,
531
535
532 # For instances, provide the constructor and class docstrings
536 # For instances, provide the constructor and class docstrings
533 'init_docstring' : str,
537 'init_docstring' : str,
534 'class_docstring' : str,
538 'class_docstring' : str,
535
539
536 # If it's a callable object whose call method has a separate docstring and
540 # If it's a callable object whose call method has a separate docstring and
537 # definition line:
541 # definition line:
538 'call_def' : str,
542 'call_def' : str,
539 'call_docstring' : str,
543 'call_docstring' : str,
540
544
541 # If detail_level was 1, we also try to find the source code that
545 # If detail_level was 1, we also try to find the source code that
542 # defines the object, if possible. The string 'None' will indicate
546 # defines the object, if possible. The string 'None' will indicate
543 # that no source was found.
547 # that no source was found.
544 'source' : str,
548 'source' : str,
545 }
549 }
546 '
550 '
547
551
548 Complete
552 Complete
549 --------
553 --------
550
554
551 Message type: ``complete_request``::
555 Message type: ``complete_request``::
552
556
553 content = {
557 content = {
554 # The text to be completed, such as 'a.is'
558 # The text to be completed, such as 'a.is'
555 'text' : str,
559 'text' : str,
556
560
557 # The full line, such as 'print a.is'. This allows completers to
561 # The full line, such as 'print a.is'. This allows completers to
558 # make decisions that may require information about more than just the
562 # make decisions that may require information about more than just the
559 # current word.
563 # current word.
560 'line' : str,
564 'line' : str,
561
565
562 # The entire block of text where the line is. This may be useful in the
566 # The entire block of text where the line is. This may be useful in the
563 # case of multiline completions where more context may be needed. Note: if
567 # case of multiline completions where more context may be needed. Note: if
564 # in practice this field proves unnecessary, remove it to lighten the
568 # in practice this field proves unnecessary, remove it to lighten the
565 # messages.
569 # messages.
566
570
567 'block' : str,
571 'block' : str,
568
572
569 # The position of the cursor where the user hit 'TAB' on the line.
573 # The position of the cursor where the user hit 'TAB' on the line.
570 'cursor_pos' : int,
574 'cursor_pos' : int,
571 }
575 }
572
576
573 Message type: ``complete_reply``::
577 Message type: ``complete_reply``::
574
578
575 content = {
579 content = {
576 # The list of all matches to the completion request, such as
580 # The list of all matches to the completion request, such as
577 # ['a.isalnum', 'a.isalpha'] for the above example.
581 # ['a.isalnum', 'a.isalpha'] for the above example.
578 'matches' : list
582 'matches' : list
579 }
583 }
580
584
581
585
582 History
586 History
583 -------
587 -------
584
588
585 For clients to explicitly request history from a kernel. The kernel has all
589 For clients to explicitly request history from a kernel. The kernel has all
586 the actual execution history stored in a single location, so clients can
590 the actual execution history stored in a single location, so clients can
587 request it from the kernel when needed.
591 request it from the kernel when needed.
588
592
589 Message type: ``history_request``::
593 Message type: ``history_request``::
590
594
591 content = {
595 content = {
592
596
593 # If True, also return output history in the resulting dict.
597 # If True, also return output history in the resulting dict.
594 'output' : bool,
598 'output' : bool,
595
599
596 # If True, return the raw input history, else the transformed input.
600 # If True, return the raw input history, else the transformed input.
597 'raw' : bool,
601 'raw' : bool,
598
602
599 # So far, this can be 'range', 'tail' or 'search'.
603 # So far, this can be 'range', 'tail' or 'search'.
600 'hist_access_type' : str,
604 'hist_access_type' : str,
601
605
602 # If hist_access_type is 'range', get a range of input cells. session can
606 # If hist_access_type is 'range', get a range of input cells. session can
603 # be a positive session number, or a negative number to count back from
607 # be a positive session number, or a negative number to count back from
604 # the current session.
608 # the current session.
605 'session' : int,
609 'session' : int,
606 # start and stop are line numbers within that session.
610 # start and stop are line numbers within that session.
607 'start' : int,
611 'start' : int,
608 'stop' : int,
612 'stop' : int,
609
613
610 # If hist_access_type is 'tail', get the last n cells.
614 # If hist_access_type is 'tail', get the last n cells.
611 'n' : int,
615 'n' : int,
612
616
613 # If hist_access_type is 'search', get cells matching the specified glob
617 # If hist_access_type is 'search', get cells matching the specified glob
614 # pattern (with * and ? as wildcards).
618 # pattern (with * and ? as wildcards).
615 'pattern' : str,
619 'pattern' : str,
616
620
617 }
621 }
618
622
619 Message type: ``history_reply``::
623 Message type: ``history_reply``::
620
624
621 content = {
625 content = {
622 # A list of 3 tuples, either:
626 # A list of 3 tuples, either:
623 # (session, line_number, input) or
627 # (session, line_number, input) or
624 # (session, line_number, (input, output)),
628 # (session, line_number, (input, output)),
625 # depending on whether output was False or True, respectively.
629 # depending on whether output was False or True, respectively.
626 'history' : list,
630 'history' : list,
627 }
631 }
628
632
629
633
630 Connect
634 Connect
631 -------
635 -------
632
636
633 When a client connects to the request/reply socket of the kernel, it can issue
637 When a client connects to the request/reply socket of the kernel, it can issue
634 a connect request to get basic information about the kernel, such as the ports
638 a connect request to get basic information about the kernel, such as the ports
635 the other ZeroMQ sockets are listening on. This allows clients to only have
639 the other ZeroMQ sockets are listening on. This allows clients to only have
636 to know about a single port (the XREQ/XREP channel) to connect to a kernel.
640 to know about a single port (the XREQ/XREP channel) to connect to a kernel.
637
641
638 Message type: ``connect_request``::
642 Message type: ``connect_request``::
639
643
640 content = {
644 content = {
641 }
645 }
642
646
643 Message type: ``connect_reply``::
647 Message type: ``connect_reply``::
644
648
645 content = {
649 content = {
646 'xrep_port' : int # The port the XREP socket is listening on.
650 'xrep_port' : int # The port the XREP socket is listening on.
647 'pub_port' : int # The port the PUB socket is listening on.
651 'pub_port' : int # The port the PUB socket is listening on.
648 'req_port' : int # The port the REQ socket is listening on.
652 'req_port' : int # The port the REQ socket is listening on.
649 'hb_port' : int # The port the heartbeat socket is listening on.
653 'hb_port' : int # The port the heartbeat socket is listening on.
650 }
654 }
651
655
652
656
653
657
654 Kernel shutdown
658 Kernel shutdown
655 ---------------
659 ---------------
656
660
657 The clients can request the kernel to shut itself down; this is used in
661 The clients can request the kernel to shut itself down; this is used in
658 multiple cases:
662 multiple cases:
659
663
660 - when the user chooses to close the client application via a menu or window
664 - when the user chooses to close the client application via a menu or window
661 control.
665 control.
662 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
666 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
663 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
667 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
664 IPythonQt client) to force a kernel restart to get a clean kernel without
668 IPythonQt client) to force a kernel restart to get a clean kernel without
665 losing client-side state like history or inlined figures.
669 losing client-side state like history or inlined figures.
666
670
667 The client sends a shutdown request to the kernel, and once it receives the
671 The client sends a shutdown request to the kernel, and once it receives the
668 reply message (which is otherwise empty), it can assume that the kernel has
672 reply message (which is otherwise empty), it can assume that the kernel has
669 completed shutdown safely.
673 completed shutdown safely.
670
674
671 Upon their own shutdown, client applications will typically execute a last
675 Upon their own shutdown, client applications will typically execute a last
672 minute sanity check and forcefully terminate any kernel that is still alive, to
676 minute sanity check and forcefully terminate any kernel that is still alive, to
673 avoid leaving stray processes in the user's machine.
677 avoid leaving stray processes in the user's machine.
674
678
675 For both shutdown request and reply, there is no actual content that needs to
679 For both shutdown request and reply, there is no actual content that needs to
676 be sent, so the content dict is empty.
680 be sent, so the content dict is empty.
677
681
678 Message type: ``shutdown_request``::
682 Message type: ``shutdown_request``::
679
683
680 content = {
684 content = {
681 'restart' : bool # whether the shutdown is final, or precedes a restart
685 'restart' : bool # whether the shutdown is final, or precedes a restart
682 }
686 }
683
687
684 Message type: ``shutdown_reply``::
688 Message type: ``shutdown_reply``::
685
689
686 content = {
690 content = {
687 'restart' : bool # whether the shutdown is final, or precedes a restart
691 'restart' : bool # whether the shutdown is final, or precedes a restart
688 }
692 }
689
693
690 .. Note::
694 .. Note::
691
695
692 When the clients detect a dead kernel thanks to inactivity on the heartbeat
696 When the clients detect a dead kernel thanks to inactivity on the heartbeat
693 socket, they simply send a forceful process termination signal, since a dead
697 socket, they simply send a forceful process termination signal, since a dead
694 process is unlikely to respond in any useful way to messages.
698 process is unlikely to respond in any useful way to messages.
695
699
696
700
697 Messages on the PUB/SUB socket
701 Messages on the PUB/SUB socket
698 ==============================
702 ==============================
699
703
700 Streams (stdout, stderr, etc)
704 Streams (stdout, stderr, etc)
701 ------------------------------
705 ------------------------------
702
706
703 Message type: ``stream``::
707 Message type: ``stream``::
704
708
705 content = {
709 content = {
706 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
710 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
707 'name' : str,
711 'name' : str,
708
712
709 # The data is an arbitrary string to be written to that stream
713 # The data is an arbitrary string to be written to that stream
710 'data' : str,
714 'data' : str,
711 }
715 }
712
716
713 When a kernel receives a raw_input call, it should also broadcast it on the pub
717 When a kernel receives a raw_input call, it should also broadcast it on the pub
714 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
718 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
715 to monitor/display kernel interactions and possibly replay them to their user
719 to monitor/display kernel interactions and possibly replay them to their user
716 or otherwise expose them.
720 or otherwise expose them.
717
721
718 Display Data
722 Display Data
719 ------------
723 ------------
720
724
721 This type of message is used to bring back data that should be diplayed (text,
725 This type of message is used to bring back data that should be diplayed (text,
722 html, svg, etc.) in the frontends. This data is published to all frontends.
726 html, svg, etc.) in the frontends. This data is published to all frontends.
723 Each message can have multiple representations of the data; it is up to the
727 Each message can have multiple representations of the data; it is up to the
724 frontend to decide which to use and how. A single message should contain all
728 frontend to decide which to use and how. A single message should contain all
725 possible representations of the same information. Each representation should
729 possible representations of the same information. Each representation should
726 be a JSON'able data structure, and should be a valid MIME type.
730 be a JSON'able data structure, and should be a valid MIME type.
727
731
728 Some questions remain about this design:
732 Some questions remain about this design:
729
733
730 * Do we use this message type for pyout/displayhook? Probably not, because
734 * Do we use this message type for pyout/displayhook? Probably not, because
731 the displayhook also has to handle the Out prompt display. On the other hand
735 the displayhook also has to handle the Out prompt display. On the other hand
732 we could put that information into the metadata secion.
736 we could put that information into the metadata secion.
733
737
734 Message type: ``display_data``::
738 Message type: ``display_data``::
735
739
736 content = {
740 content = {
737
741
738 # Who create the data
742 # Who create the data
739 'source' : str,
743 'source' : str,
740
744
741 # The data dict contains key/value pairs, where the kids are MIME
745 # The data dict contains key/value pairs, where the kids are MIME
742 # types and the values are the raw data of the representation in that
746 # types and the values are the raw data of the representation in that
743 # format. The data dict must minimally contain the ``text/plain``
747 # format. The data dict must minimally contain the ``text/plain``
744 # MIME type which is used as a backup representation.
748 # MIME type which is used as a backup representation.
745 'data' : dict,
749 'data' : dict,
746
750
747 # Any metadata that describes the data
751 # Any metadata that describes the data
748 'metadata' : dict
752 'metadata' : dict
749 }
753 }
750
754
751 Python inputs
755 Python inputs
752 -------------
756 -------------
753
757
754 These messages are the re-broadcast of the ``execute_request``.
758 These messages are the re-broadcast of the ``execute_request``.
755
759
756 Message type: ``pyin``::
760 Message type: ``pyin``::
757
761
758 content = {
762 content = {
759 'code' : str # Source code to be executed, one or more lines
763 'code' : str # Source code to be executed, one or more lines
760 }
764 }
761
765
762 Python outputs
766 Python outputs
763 --------------
767 --------------
764
768
765 When Python produces output from code that has been compiled in with the
769 When Python produces output from code that has been compiled in with the
766 'single' flag to :func:`compile`, any expression that produces a value (such as
770 'single' flag to :func:`compile`, any expression that produces a value (such as
767 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
771 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
768 this value whatever it wants. The default behavior of ``sys.displayhook`` in
772 this value whatever it wants. The default behavior of ``sys.displayhook`` in
769 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
773 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
770 the value as long as it is not ``None`` (which isn't printed at all). In our
774 the value as long as it is not ``None`` (which isn't printed at all). In our
771 case, the kernel instantiates as ``sys.displayhook`` an object which has
775 case, the kernel instantiates as ``sys.displayhook`` an object which has
772 similar behavior, but which instead of printing to stdout, broadcasts these
776 similar behavior, but which instead of printing to stdout, broadcasts these
773 values as ``pyout`` messages for clients to display appropriately.
777 values as ``pyout`` messages for clients to display appropriately.
774
778
775 IPython's displayhook can handle multiple simultaneous formats depending on its
779 IPython's displayhook can handle multiple simultaneous formats depending on its
776 configuration. The default pretty-printed repr text is always given with the
780 configuration. The default pretty-printed repr text is always given with the
777 ``data`` entry in this message. Any other formats are provided in the
781 ``data`` entry in this message. Any other formats are provided in the
778 ``extra_formats`` list. Frontends are free to display any or all of these
782 ``extra_formats`` list. Frontends are free to display any or all of these
779 according to its capabilities. ``extra_formats`` list contains 3-tuples of an ID
783 according to its capabilities. ``extra_formats`` list contains 3-tuples of an ID
780 string, a type string, and the data. The ID is unique to the formatter
784 string, a type string, and the data. The ID is unique to the formatter
781 implementation that created the data. Frontends will typically ignore the ID
785 implementation that created the data. Frontends will typically ignore the ID
782 unless if it has requested a particular formatter. The type string tells the
786 unless if it has requested a particular formatter. The type string tells the
783 frontend how to interpret the data. It is often, but not always a MIME type.
787 frontend how to interpret the data. It is often, but not always a MIME type.
784 Frontends should ignore types that it does not understand. The data itself is
788 Frontends should ignore types that it does not understand. The data itself is
785 any JSON object and depends on the format. It is often, but not always a string.
789 any JSON object and depends on the format. It is often, but not always a string.
786
790
787 Message type: ``pyout``::
791 Message type: ``pyout``::
788
792
789 content = {
793 content = {
790
794
791 # The counter for this execution is also provided so that clients can
795 # The counter for this execution is also provided so that clients can
792 # display it, since IPython automatically creates variables called _N
796 # display it, since IPython automatically creates variables called _N
793 # (for prompt N).
797 # (for prompt N).
794 'execution_count' : int,
798 'execution_count' : int,
795
799
796 # The data dict contains key/value pairs, where the kids are MIME
800 # The data dict contains key/value pairs, where the kids are MIME
797 # types and the values are the raw data of the representation in that
801 # types and the values are the raw data of the representation in that
798 # format. The data dict must minimally contain the ``text/plain``
802 # format. The data dict must minimally contain the ``text/plain``
799 # MIME type which is used as a backup representation.
803 # MIME type which is used as a backup representation.
800 'data' : dict,
804 'data' : dict,
801
805
802 }
806 }
803
807
804 Python errors
808 Python errors
805 -------------
809 -------------
806
810
807 When an error occurs during code execution
811 When an error occurs during code execution
808
812
809 Message type: ``pyerr``::
813 Message type: ``pyerr``::
810
814
811 content = {
815 content = {
812 # Similar content to the execute_reply messages for the 'error' case,
816 # Similar content to the execute_reply messages for the 'error' case,
813 # except the 'status' field is omitted.
817 # except the 'status' field is omitted.
814 }
818 }
815
819
816 Kernel status
820 Kernel status
817 -------------
821 -------------
818
822
819 This message type is used by frontends to monitor the status of the kernel.
823 This message type is used by frontends to monitor the status of the kernel.
820
824
821 Message type: ``status``::
825 Message type: ``status``::
822
826
823 content = {
827 content = {
824 # When the kernel starts to execute code, it will enter the 'busy'
828 # When the kernel starts to execute code, it will enter the 'busy'
825 # state and when it finishes, it will enter the 'idle' state.
829 # state and when it finishes, it will enter the 'idle' state.
826 execution_state : ('busy', 'idle')
830 execution_state : ('busy', 'idle')
827 }
831 }
828
832
829 Kernel crashes
833 Kernel crashes
830 --------------
834 --------------
831
835
832 When the kernel has an unexpected exception, caught by the last-resort
836 When the kernel has an unexpected exception, caught by the last-resort
833 sys.excepthook, we should broadcast the crash handler's output before exiting.
837 sys.excepthook, we should broadcast the crash handler's output before exiting.
834 This will allow clients to notice that a kernel died, inform the user and
838 This will allow clients to notice that a kernel died, inform the user and
835 propose further actions.
839 propose further actions.
836
840
837 Message type: ``crash``::
841 Message type: ``crash``::
838
842
839 content = {
843 content = {
840 # Similarly to the 'error' case for execute_reply messages, this will
844 # Similarly to the 'error' case for execute_reply messages, this will
841 # contain exc_name, exc_type and traceback fields.
845 # contain exc_name, exc_type and traceback fields.
842
846
843 # An additional field with supplementary information such as where to
847 # An additional field with supplementary information such as where to
844 # send the crash message
848 # send the crash message
845 'info' : str,
849 'info' : str,
846 }
850 }
847
851
848
852
849 Future ideas
853 Future ideas
850 ------------
854 ------------
851
855
852 Other potential message types, currently unimplemented, listed below as ideas.
856 Other potential message types, currently unimplemented, listed below as ideas.
853
857
854 Message type: ``file``::
858 Message type: ``file``::
855
859
856 content = {
860 content = {
857 'path' : 'cool.jpg',
861 'path' : 'cool.jpg',
858 'mimetype' : str,
862 'mimetype' : str,
859 'data' : str,
863 'data' : str,
860 }
864 }
861
865
862
866
863 Messages on the REQ/REP socket
867 Messages on the REQ/REP socket
864 ==============================
868 ==============================
865
869
866 This is a socket that goes in the opposite direction: from the kernel to a
870 This is a socket that goes in the opposite direction: from the kernel to a
867 *single* frontend, and its purpose is to allow ``raw_input`` and similar
871 *single* frontend, and its purpose is to allow ``raw_input`` and similar
868 operations that read from ``sys.stdin`` on the kernel to be fulfilled by the
872 operations that read from ``sys.stdin`` on the kernel to be fulfilled by the
869 client. For now we will keep these messages as simple as possible, since they
873 client. For now we will keep these messages as simple as possible, since they
870 basically only mean to convey the ``raw_input(prompt)`` call.
874 basically only mean to convey the ``raw_input(prompt)`` call.
871
875
872 Message type: ``input_request``::
876 Message type: ``input_request``::
873
877
874 content = { 'prompt' : str }
878 content = { 'prompt' : str }
875
879
876 Message type: ``input_reply``::
880 Message type: ``input_reply``::
877
881
878 content = { 'value' : str }
882 content = { 'value' : str }
879
883
880 .. Note::
884 .. Note::
881
885
882 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
886 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
883 practice the kernel should behave like an interactive program. When a
887 practice the kernel should behave like an interactive program. When a
884 program is opened on the console, the keyboard effectively takes over the
888 program is opened on the console, the keyboard effectively takes over the
885 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
889 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
886 Since the IPython kernel effectively behaves like a console program (albeit
890 Since the IPython kernel effectively behaves like a console program (albeit
887 one whose "keyboard" is actually living in a separate process and
891 one whose "keyboard" is actually living in a separate process and
888 transported over the zmq connection), raw ``stdin`` isn't expected to be
892 transported over the zmq connection), raw ``stdin`` isn't expected to be
889 available.
893 available.
890
894
891
895
892 Heartbeat for kernels
896 Heartbeat for kernels
893 =====================
897 =====================
894
898
895 Initially we had considered using messages like those above over ZMQ for a
899 Initially we had considered using messages like those above over ZMQ for a
896 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
900 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
897 alive at all, even if it may be busy executing user code). But this has the
901 alive at all, even if it may be busy executing user code). But this has the
898 problem that if the kernel is locked inside extension code, it wouldn't execute
902 problem that if the kernel is locked inside extension code, it wouldn't execute
899 the python heartbeat code. But it turns out that we can implement a basic
903 the python heartbeat code. But it turns out that we can implement a basic
900 heartbeat with pure ZMQ, without using any Python messaging at all.
904 heartbeat with pure ZMQ, without using any Python messaging at all.
901
905
902 The monitor sends out a single zmq message (right now, it is a str of the
906 The monitor sends out a single zmq message (right now, it is a str of the
903 monitor's lifetime in seconds), and gets the same message right back, prefixed
907 monitor's lifetime in seconds), and gets the same message right back, prefixed
904 with the zmq identity of the XREQ socket in the heartbeat process. This can be
908 with the zmq identity of the XREQ socket in the heartbeat process. This can be
905 a uuid, or even a full message, but there doesn't seem to be a need for packing
909 a uuid, or even a full message, but there doesn't seem to be a need for packing
906 up a message when the sender and receiver are the exact same Python object.
910 up a message when the sender and receiver are the exact same Python object.
907
911
908 The model is this::
912 The model is this::
909
913
910 monitor.send(str(self.lifetime)) # '1.2345678910'
914 monitor.send(str(self.lifetime)) # '1.2345678910'
911
915
912 and the monitor receives some number of messages of the form::
916 and the monitor receives some number of messages of the form::
913
917
914 ['uuid-abcd-dead-beef', '1.2345678910']
918 ['uuid-abcd-dead-beef', '1.2345678910']
915
919
916 where the first part is the zmq.IDENTITY of the heart's XREQ on the engine, and
920 where the first part is the zmq.IDENTITY of the heart's XREQ on the engine, and
917 the rest is the message sent by the monitor. No Python code ever has any
921 the rest is the message sent by the monitor. No Python code ever has any
918 access to the message between the monitor's send, and the monitor's recv.
922 access to the message between the monitor's send, and the monitor's recv.
919
923
920
924
921 ToDo
925 ToDo
922 ====
926 ====
923
927
924 Missing things include:
928 Missing things include:
925
929
926 * Important: finish thinking through the payload concept and API.
930 * Important: finish thinking through the payload concept and API.
927
931
928 * Important: ensure that we have a good solution for magics like %edit. It's
932 * Important: ensure that we have a good solution for magics like %edit. It's
929 likely that with the payload concept we can build a full solution, but not
933 likely that with the payload concept we can build a full solution, but not
930 100% clear yet.
934 100% clear yet.
931
935
932 * Finishing the details of the heartbeat protocol.
936 * Finishing the details of the heartbeat protocol.
933
937
934 * Signal handling: specify what kind of information kernel should broadcast (or
938 * Signal handling: specify what kind of information kernel should broadcast (or
935 not) when it receives signals.
939 not) when it receives signals.
936
940
937 .. include:: ../links.rst
941 .. include:: ../links.rst
General Comments 0
You need to be logged in to leave comments. Login now