##// END OF EJS Templates
Fixing minor bug in ipkernel.py.
Brian Granger -
Show More
@@ -1,516 +1,517 b''
1 1 #!/usr/bin/env python
2 2 """A simple interactive kernel that talks to a frontend over 0MQ.
3 3
4 4 Things to do:
5 5
6 6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 7 call set_parent on all the PUB objects with the message about to be executed.
8 8 * Implement random port and security key logic.
9 9 * Implement control messages.
10 10 * Implement event loop and poll version.
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Standard library imports.
19 19 import __builtin__
20 20 import sys
21 21 import time
22 22 import traceback
23 23
24 24 # System library imports.
25 25 import zmq
26 26
27 27 # Local imports.
28 28 from IPython.config.configurable import Configurable
29 29 from IPython.utils import io
30 30 from IPython.lib import pylabtools
31 31 from IPython.utils.traitlets import Instance, Float
32 32 from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \
33 33 start_kernel
34 34 from iostream import OutStream
35 35 from session import Session, Message
36 36 from zmqshell import ZMQInteractiveShell
37 37
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Main kernel class
41 41 #-----------------------------------------------------------------------------
42 42
43 43 class Kernel(Configurable):
44 44
45 45 #---------------------------------------------------------------------------
46 46 # Kernel interface
47 47 #---------------------------------------------------------------------------
48 48
49 49 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
50 50 session = Instance(Session)
51 51 reply_socket = Instance('zmq.Socket')
52 52 pub_socket = Instance('zmq.Socket')
53 53 req_socket = Instance('zmq.Socket')
54 54
55 55 # Private interface
56 56
57 57 # Time to sleep after flushing the stdout/err buffers in each execute
58 58 # cycle. While this introduces a hard limit on the minimal latency of the
59 59 # execute cycle, it helps prevent output synchronization problems for
60 60 # clients.
61 61 # Units are in seconds. The minimum zmq latency on local host is probably
62 62 # ~150 microseconds, set this to 500us for now. We may need to increase it
63 63 # a little if it's not enough after more interactive testing.
64 64 _execute_sleep = Float(0.0005, config=True)
65 65
66 66 # Frequency of the kernel's event loop.
67 67 # Units are in seconds, kernel subclasses for GUI toolkits may need to
68 68 # adapt to milliseconds.
69 69 _poll_interval = Float(0.05, config=True)
70 70
71 71 def __init__(self, **kwargs):
72 72 super(Kernel, self).__init__(**kwargs)
73 73
74 74 # Initialize the InteractiveShell subclass
75 75 self.shell = ZMQInteractiveShell.instance()
76 76 self.shell.displayhook.session = self.session
77 77 self.shell.displayhook.pub_socket = self.pub_socket
78 78
79 79 # TMP - hack while developing
80 80 self.shell._reply_content = None
81 81
82 82 # Build dict of handlers for message types
83 83 msg_types = [ 'execute_request', 'complete_request',
84 84 'object_info_request', 'history_request' ]
85 85 self.handlers = {}
86 86 for msg_type in msg_types:
87 87 self.handlers[msg_type] = getattr(self, msg_type)
88 88
89 89 def do_one_iteration(self):
90 90 try:
91 91 ident = self.reply_socket.recv(zmq.NOBLOCK)
92 92 except zmq.ZMQError, e:
93 93 if e.errno == zmq.EAGAIN:
94 94 return
95 95 else:
96 96 raise
97 97 # FIXME: Bug in pyzmq/zmq?
98 98 # assert self.reply_socket.rcvmore(), "Missing message part."
99 99 msg = self.reply_socket.recv_json()
100 100
101 101 # Print some info about this message and leave a '--->' marker, so it's
102 102 # easier to trace visually the message chain when debugging. Each
103 103 # handler prints its message at the end.
104 104 # Eventually we'll move these from stdout to a logger.
105 105 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
106 106 io.raw_print(' Content: ', msg['content'],
107 107 '\n --->\n ', sep='', end='')
108 108
109 109 # Find and call actual handler for message
110 110 handler = self.handlers.get(msg['msg_type'], None)
111 111 if handler is None:
112 112 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
113 113 else:
114 114 handler(ident, msg)
115 115
116 116 def start(self):
117 117 """ Start the kernel main loop.
118 118 """
119 119 while True:
120 120 time.sleep(self._poll_interval)
121 121 self.do_one_iteration()
122 122
123 123 #---------------------------------------------------------------------------
124 124 # Kernel request handlers
125 125 #---------------------------------------------------------------------------
126 126
127 127 def _publish_pyin(self, code, parent):
128 128 """Publish the code request on the pyin stream."""
129 129
130 130 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
131 131 self.pub_socket.send_json(pyin_msg)
132 132
133 133 def execute_request(self, ident, parent):
134 134 try:
135 135 content = parent[u'content']
136 136 code = content[u'code']
137 137 silent = content[u'silent']
138 138 except:
139 139 io.raw_print_err("Got bad msg: ")
140 140 io.raw_print_err(Message(parent))
141 141 return
142 142
143 143 shell = self.shell # we'll need this a lot here
144 144
145 145 # Replace raw_input. Note that is not sufficient to replace
146 146 # raw_input in the user namespace.
147 147 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
148 148 __builtin__.raw_input = raw_input
149 149
150 150 # Set the parent message of the display hook and out streams.
151 151 shell.displayhook.set_parent(parent)
152 152 sys.stdout.set_parent(parent)
153 153 sys.stderr.set_parent(parent)
154 154
155 155 # Re-broadcast our input for the benefit of listening clients, and
156 156 # start computing output
157 157 if not silent:
158 158 self._publish_pyin(code, parent)
159 159
160 160 reply_content = {}
161 161 try:
162 162 if silent:
163 163 # runcode uses 'exec' mode, so no displayhook will fire, and it
164 164 # doesn't call logging or history manipulations. Print
165 165 # statements in that code will obviously still execute.
166 166 shell.runcode(code)
167 167 else:
168 168 # FIXME: runlines calls the exception handler itself.
169 169 shell._reply_content = None
170 170 shell.runlines(code)
171 171 except:
172 172 status = u'error'
173 173 # FIXME: this code right now isn't being used yet by default,
174 174 # because the runlines() call above directly fires off exception
175 175 # reporting. This code, therefore, is only active in the scenario
176 176 # where runlines itself has an unhandled exception. We need to
177 177 # uniformize this, for all exception construction to come from a
178 178 # single location in the codbase.
179 179 etype, evalue, tb = sys.exc_info()
180 180 tb_list = traceback.format_exception(etype, evalue, tb)
181 181 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
182 182 else:
183 183 status = u'ok'
184 184 reply_content[u'payload'] = shell.payload_manager.read_payload()
185 185 # Be agressive about clearing the payload because we don't want
186 186 # it to sit in memory until the next execute_request comes in.
187 187 shell.payload_manager.clear_payload()
188 188
189 189 reply_content[u'status'] = status
190 190 # Compute the execution counter so clients can display prompts
191 191 reply_content['execution_count'] = shell.displayhook.prompt_count
192 192
193 193 # FIXME - fish exception info out of shell, possibly left there by
194 194 # runlines. We'll need to clean up this logic later.
195 195 if shell._reply_content is not None:
196 196 reply_content.update(shell._reply_content)
197 197
198 198 # At this point, we can tell whether the main code execution succeeded
199 199 # or not. If it did, we proceed to evaluate user_variables/expressions
200 200 if reply_content['status'] == 'ok':
201 201 reply_content[u'user_variables'] = \
202 202 shell.get_user_variables(content[u'user_variables'])
203 203 reply_content[u'user_expressions'] = \
204 204 shell.eval_expressions(content[u'user_expressions'])
205 205 else:
206 206 # If there was an error, don't even try to compute variables or
207 207 # expressions
208 208 reply_content[u'user_variables'] = {}
209 209 reply_content[u'user_expressions'] = {}
210 210
211 211 # Send the reply.
212 212 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
213 213 io.raw_print(reply_msg)
214 214
215 215 # Flush output before sending the reply.
216 216 sys.stdout.flush()
217 217 sys.stderr.flush()
218 218 # FIXME: on rare occasions, the flush doesn't seem to make it to the
219 219 # clients... This seems to mitigate the problem, but we definitely need
220 220 # to better understand what's going on.
221 221 if self._execute_sleep:
222 222 time.sleep(self._execute_sleep)
223 223
224 224 self.reply_socket.send(ident, zmq.SNDMORE)
225 225 self.reply_socket.send_json(reply_msg)
226 226 if reply_msg['content']['status'] == u'error':
227 227 self._abort_queue()
228 228
229 229 def complete_request(self, ident, parent):
230 230 txt, matches = self._complete(parent)
231 231 matches = {'matches' : matches,
232 232 'matched_text' : txt,
233 233 'status' : 'ok'}
234 234 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
235 235 matches, parent, ident)
236 236 io.raw_print(completion_msg)
237 237
238 238 def object_info_request(self, ident, parent):
239 239 ##context = parent['content']['oname'].split('.')
240 240 ##object_info = self._object_info(context)
241 241 object_info = self.shell.object_inspect(parent['content']['oname'])
242 242 msg = self.session.send(self.reply_socket, 'object_info_reply',
243 243 object_info._asdict(), parent, ident)
244 244 io.raw_print(msg)
245 245
246 246 def history_request(self, ident, parent):
247 247 output = parent['content']['output']
248 248 index = parent['content']['index']
249 249 raw = parent['content']['raw']
250 250 hist = self.shell.get_history(index=index, raw=raw, output=output)
251 251 content = {'history' : hist}
252 252 msg = self.session.send(self.reply_socket, 'history_reply',
253 253 content, parent, ident)
254 254 io.raw_print(msg)
255 255
256 256 #---------------------------------------------------------------------------
257 257 # Protected interface
258 258 #---------------------------------------------------------------------------
259 259
260 260 def _abort_queue(self):
261 261 while True:
262 262 try:
263 263 ident = self.reply_socket.recv(zmq.NOBLOCK)
264 264 except zmq.ZMQError, e:
265 265 if e.errno == zmq.EAGAIN:
266 266 break
267 267 else:
268 268 assert self.reply_socket.rcvmore(), \
269 269 "Unexpected missing message part."
270 270 msg = self.reply_socket.recv_json()
271 271 io.raw_print("Aborting:\n", Message(msg))
272 272 msg_type = msg['msg_type']
273 273 reply_type = msg_type.split('_')[0] + '_reply'
274 274 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
275 275 io.raw_print(reply_msg)
276 276 self.reply_socket.send(ident,zmq.SNDMORE)
277 277 self.reply_socket.send_json(reply_msg)
278 278 # We need to wait a bit for requests to come in. This can probably
279 279 # be set shorter for true asynchronous clients.
280 280 time.sleep(0.1)
281 281
282 282 def _raw_input(self, prompt, ident, parent):
283 283 # Flush output before making the request.
284 284 sys.stderr.flush()
285 285 sys.stdout.flush()
286 286
287 287 # Send the input request.
288 288 content = dict(prompt=prompt)
289 289 msg = self.session.msg(u'input_request', content, parent)
290 290 self.req_socket.send_json(msg)
291 291
292 292 # Await a response.
293 293 reply = self.req_socket.recv_json()
294 294 try:
295 295 value = reply['content']['value']
296 296 except:
297 297 io.raw_print_err("Got bad raw_input reply: ")
298 298 io.raw_print_err(Message(parent))
299 299 value = ''
300 300 return value
301 301
302 302 def _complete(self, msg):
303 303 c = msg['content']
304 304 try:
305 305 cpos = int(c['cursor_pos'])
306 306 except:
307 307 # If we don't get something that we can convert to an integer, at
308 308 # least attempt the completion guessing the cursor is at the end of
309 309 # the text, if there's any, and otherwise of the line
310 310 cpos = len(c['text'])
311 311 if cpos==0:
312 312 cpos = len(c['line'])
313 313 return self.shell.complete(c['text'], c['line'], cpos)
314 314
315 315 def _object_info(self, context):
316 316 symbol, leftover = self._symbol_from_context(context)
317 317 if symbol is not None and not leftover:
318 318 doc = getattr(symbol, '__doc__', '')
319 319 else:
320 320 doc = ''
321 321 object_info = dict(docstring = doc)
322 322 return object_info
323 323
324 324 def _symbol_from_context(self, context):
325 325 if not context:
326 326 return None, context
327 327
328 328 base_symbol_string = context[0]
329 329 symbol = self.shell.user_ns.get(base_symbol_string, None)
330 330 if symbol is None:
331 331 symbol = __builtin__.__dict__.get(base_symbol_string, None)
332 332 if symbol is None:
333 333 return None, context
334 334
335 335 context = context[1:]
336 336 for i, name in enumerate(context):
337 337 new_symbol = getattr(symbol, name, None)
338 338 if new_symbol is None:
339 339 return symbol, context[i:]
340 340 else:
341 341 symbol = new_symbol
342 342
343 343 return symbol, []
344 344
345 345
346 346 class QtKernel(Kernel):
347 347 """A Kernel subclass with Qt support."""
348 348
349 349 def start(self):
350 350 """Start a kernel with QtPy4 event loop integration."""
351 351
352 352 from PyQt4 import QtGui, QtCore
353 353 from IPython.lib.guisupport import (
354 354 get_app_qt4, start_event_loop_qt4
355 355 )
356 356 self.app = get_app_qt4([" "])
357 357 self.app.setQuitOnLastWindowClosed(False)
358 358 self.timer = QtCore.QTimer()
359 359 self.timer.timeout.connect(self.do_one_iteration)
360 360 # Units for the timer are in milliseconds
361 361 self.timer.start(1000*self._poll_interval)
362 362 start_event_loop_qt4(self.app)
363 363
364 364
365 365 class WxKernel(Kernel):
366 366 """A Kernel subclass with Wx support."""
367 367
368 368 def start(self):
369 369 """Start a kernel with wx event loop support."""
370 370
371 371 import wx
372 372 from IPython.lib.guisupport import start_event_loop_wx
373 373 doi = self.do_one_iteration
374 _poll_interval = self._poll_interval
374 375
375 376 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
376 377 # We make the Frame hidden when we create it in the main app below.
377 378 class TimerFrame(wx.Frame):
378 379 def __init__(self, func):
379 380 wx.Frame.__init__(self, None, -1)
380 381 self.timer = wx.Timer(self)
381 382 # Units for the timer are in milliseconds
382 self.timer.Start(1000*self._poll_interval)
383 self.timer.Start(1000*_poll_interval)
383 384 self.Bind(wx.EVT_TIMER, self.on_timer)
384 385 self.func = func
385 386 def on_timer(self, event):
386 387 self.func()
387 388
388 389 # We need a custom wx.App to create our Frame subclass that has the
389 390 # wx.Timer to drive the ZMQ event loop.
390 391 class IPWxApp(wx.App):
391 392 def OnInit(self):
392 393 self.frame = TimerFrame(doi)
393 394 self.frame.Show(False)
394 395 return True
395 396
396 397 # The redirect=False here makes sure that wx doesn't replace
397 398 # sys.stdout/stderr with its own classes.
398 399 self.app = IPWxApp(redirect=False)
399 400 start_event_loop_wx(self.app)
400 401
401 402
402 403 class TkKernel(Kernel):
403 404 """A Kernel subclass with Tk support."""
404 405
405 406 def start(self):
406 407 """Start a Tk enabled event loop."""
407 408
408 409 import Tkinter
409 410 doi = self.do_one_iteration
410 411
411 412 # For Tkinter, we create a Tk object and call its withdraw method.
412 413 class Timer(object):
413 414 def __init__(self, func):
414 415 self.app = Tkinter.Tk()
415 416 self.app.withdraw()
416 417 self.func = func
417 418 def on_timer(self):
418 419 self.func()
419 420 # Units for the timer are in milliseconds
420 421 self.app.after(1000*self._poll_interval, self.on_timer)
421 422 def start(self):
422 423 self.on_timer() # Call it once to get things going.
423 424 self.app.mainloop()
424 425
425 426 self.timer = Timer(doi)
426 427 self.timer.start()
427 428
428 429 #-----------------------------------------------------------------------------
429 430 # Kernel main and launch functions
430 431 #-----------------------------------------------------------------------------
431 432
432 433 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
433 434 independent=False, pylab=False):
434 435 """ Launches a localhost kernel, binding to the specified ports.
435 436
436 437 Parameters
437 438 ----------
438 439 xrep_port : int, optional
439 440 The port to use for XREP channel.
440 441
441 442 pub_port : int, optional
442 443 The port to use for the SUB channel.
443 444
444 445 req_port : int, optional
445 446 The port to use for the REQ (raw input) channel.
446 447
447 448 hb_port : int, optional
448 449 The port to use for the hearbeat REP channel.
449 450
450 451 independent : bool, optional (default False)
451 452 If set, the kernel process is guaranteed to survive if this process
452 453 dies. If not set, an effort is made to ensure that the kernel is killed
453 454 when this process dies. Note that in this case it is still good practice
454 455 to kill kernels manually before exiting.
455 456
456 457 pylab : bool or string, optional (default False)
457 458 If not False, the kernel will be launched with pylab enabled. If a
458 459 string is passed, matplotlib will use the specified backend. Otherwise,
459 460 matplotlib's default backend will be used.
460 461
461 462 Returns
462 463 -------
463 464 A tuple of form:
464 465 (kernel_process, xrep_port, pub_port, req_port)
465 466 where kernel_process is a Popen object and the ports are integers.
466 467 """
467 468 extra_arguments = []
468 469 if pylab:
469 470 extra_arguments.append('--pylab')
470 471 if isinstance(pylab, basestring):
471 472 extra_arguments.append(pylab)
472 473 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
473 474 xrep_port, pub_port, req_port, hb_port,
474 475 independent, extra_arguments)
475 476
476 477
477 478 def main():
478 479 """ The IPython kernel main entry point.
479 480 """
480 481 parser = make_argument_parser()
481 482 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
482 483 const='auto', help = \
483 484 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
484 485 given, the GUI backend is matplotlib's, otherwise use one of: \
485 486 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
486 487 namespace = parser.parse_args()
487 488
488 489 kernel_class = Kernel
489 490
490 491 _kernel_classes = {
491 492 'qt' : QtKernel,
492 493 'qt4' : QtKernel,
493 494 'payload-svg': Kernel,
494 495 'wx' : WxKernel,
495 496 'tk' : TkKernel
496 497 }
497 498 if namespace.pylab:
498 499 if namespace.pylab == 'auto':
499 500 gui, backend = pylabtools.find_gui_and_backend()
500 501 else:
501 502 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
502 503 kernel_class = _kernel_classes.get(gui)
503 504 if kernel_class is None:
504 505 raise ValueError('GUI is not supported: %r' % gui)
505 506 pylabtools.activate_matplotlib(backend)
506 507
507 508 kernel = make_kernel(namespace, kernel_class, OutStream)
508 509
509 510 if namespace.pylab:
510 511 pylabtools.import_pylab(kernel.shell.user_ns)
511 512
512 513 start_kernel(namespace, kernel)
513 514
514 515
515 516 if __name__ == '__main__':
516 517 main()
General Comments 0
You need to be logged in to leave comments. Login now