##// END OF EJS Templates
Merge pull request #5253 from minrk/console-async...
Min RK -
r15739:9338c65f merge
parent child Browse files
Show More
@@ -1,506 +1,503 b''
1 1 # -*- coding: utf-8 -*-
2 2 """terminal client to the IPython kernel
3 3
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2013 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from __future__ import print_function
16 16
17 17 import bdb
18 18 import signal
19 19 import os
20 20 import sys
21 21 import time
22 22 import subprocess
23 23 from io import BytesIO
24 24 import base64
25 25
26 26 try:
27 27 from queue import Empty # Py 3
28 28 except ImportError:
29 29 from Queue import Empty # Py 2
30 30
31 31 from IPython.core import page
32 32 from IPython.utils.warn import warn, error
33 33 from IPython.utils import io
34 34 from IPython.utils.py3compat import string_types, input
35 35 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float
36 36 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
37 37
38 38 from IPython.terminal.interactiveshell import TerminalInteractiveShell
39 39 from IPython.terminal.console.completer import ZMQCompleter
40 40
41 41
42 42 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
43 43 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
44 44 _executing = False
45 45 _execution_state = Unicode('')
46 46 kernel_timeout = Float(60, config=True,
47 47 help="""Timeout for giving up on a kernel (in seconds).
48 48
49 49 On first connect and restart, the console tests whether the
50 50 kernel is running and responsive by sending kernel_info_requests.
51 51 This sets the timeout in seconds for how long the kernel can take
52 52 before being presumed dead.
53 53 """
54 54 )
55 55
56 56 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
57 57 config=True, help=
58 58 """
59 59 Handler for image type output. This is useful, for example,
60 60 when connecting to the kernel in which pylab inline backend is
61 61 activated. There are four handlers defined. 'PIL': Use
62 62 Python Imaging Library to popup image; 'stream': Use an
63 63 external program to show the image. Image will be fed into
64 64 the STDIN of the program. You will need to configure
65 65 `stream_image_handler`; 'tempfile': Use an external program to
66 66 show the image. Image will be saved in a temporally file and
67 67 the program is called with the temporally file. You will need
68 68 to configure `tempfile_image_handler`; 'callable': You can set
69 69 any Python callable which is called with the image data. You
70 70 will need to configure `callable_image_handler`.
71 71 """
72 72 )
73 73
74 74 stream_image_handler = List(config=True, help=
75 75 """
76 76 Command to invoke an image viewer program when you are using
77 77 'stream' image handler. This option is a list of string where
78 78 the first element is the command itself and reminders are the
79 79 options for the command. Raw image data is given as STDIN to
80 80 the program.
81 81 """
82 82 )
83 83
84 84 tempfile_image_handler = List(config=True, help=
85 85 """
86 86 Command to invoke an image viewer program when you are using
87 87 'tempfile' image handler. This option is a list of string
88 88 where the first element is the command itself and reminders
89 89 are the options for the command. You can use {file} and
90 90 {format} in the string to represent the location of the
91 91 generated image file and image format.
92 92 """
93 93 )
94 94
95 95 callable_image_handler = Any(config=True, help=
96 96 """
97 97 Callable object called via 'callable' image handler with one
98 98 argument, `data`, which is `msg["content"]["data"]` where
99 99 `msg` is the message from iopub channel. For exmaple, you can
100 100 find base64 encoded PNG data as `data['image/png']`.
101 101 """
102 102 )
103 103
104 104 mime_preference = List(
105 105 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
106 106 config=True, allow_none=False, help=
107 107 """
108 108 Preferred object representation MIME type in order. First
109 109 matched MIME type will be used.
110 110 """
111 111 )
112 112
113 113 manager = Instance('IPython.kernel.KernelManager')
114 114 client = Instance('IPython.kernel.KernelClient')
115 115 def _client_changed(self, name, old, new):
116 116 self.session_id = new.session.session
117 117 session_id = Unicode()
118 118
119 119 def init_completer(self):
120 120 """Initialize the completion machinery.
121 121
122 122 This creates completion machinery that can be used by client code,
123 123 either interactively in-process (typically triggered by the readline
124 124 library), programmatically (such as in test suites) or out-of-process
125 125 (typically over the network by remote frontends).
126 126 """
127 127 from IPython.core.completerlib import (module_completer,
128 128 magic_run_completer, cd_completer)
129 129
130 130 self.Completer = ZMQCompleter(self, self.client, config=self.config)
131 131
132 132
133 133 self.set_hook('complete_command', module_completer, str_key = 'import')
134 134 self.set_hook('complete_command', module_completer, str_key = 'from')
135 135 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
136 136 self.set_hook('complete_command', cd_completer, str_key = '%cd')
137 137
138 138 # Only configure readline if we truly are using readline. IPython can
139 139 # do tab-completion over the network, in GUIs, etc, where readline
140 140 # itself may be absent
141 141 if self.has_readline:
142 142 self.set_readline_completer()
143 143
144 144 def ask_exit(self):
145 145 super(ZMQTerminalInteractiveShell, self).ask_exit()
146 146 if self.exit_now and self.manager:
147 147 self.client.shutdown()
148 148
149 149 def run_cell(self, cell, store_history=True):
150 150 """Run a complete IPython cell.
151 151
152 152 Parameters
153 153 ----------
154 154 cell : str
155 155 The code (including IPython code such as %magic functions) to run.
156 156 store_history : bool
157 157 If True, the raw and translated cell will be stored in IPython's
158 158 history. For user code calling back into IPython's machinery, this
159 159 should be set to False.
160 160 """
161 161 if (not cell) or cell.isspace():
162 # pressing enter flushes any pending display
163 self.handle_iopub()
162 164 return
163 165
164 166 if cell.strip() == 'exit':
165 167 # explicitly handle 'exit' command
166 168 return self.ask_exit()
167 169
168 170 # flush stale replies, which could have been ignored, due to missed heartbeats
169 171 while self.client.shell_channel.msg_ready():
170 172 self.client.shell_channel.get_msg()
171 173 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
172 174 msg_id = self.client.shell_channel.execute(cell, not store_history)
173 175
174 176 # first thing is wait for any side effects (output, stdin, etc.)
175 177 self._executing = True
176 178 self._execution_state = "busy"
177 179 while self._execution_state != 'idle' and self.client.is_alive():
178 180 try:
179 181 self.handle_stdin_request(msg_id, timeout=0.05)
180 182 except Empty:
181 183 # display intermediate print statements, etc.
182 184 self.handle_iopub(msg_id)
183 pass
184 185
185 186 # after all of that is done, wait for the execute reply
186 187 while self.client.is_alive():
187 188 try:
188 189 self.handle_execute_reply(msg_id, timeout=0.05)
189 190 except Empty:
190 191 pass
191 192 else:
192 193 break
193 194 self._executing = False
194 195
195 196 #-----------------
196 197 # message handlers
197 198 #-----------------
198 199
199 200 def handle_execute_reply(self, msg_id, timeout=None):
200 201 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
201 202 if msg["parent_header"].get("msg_id", None) == msg_id:
202 203
203 204 self.handle_iopub(msg_id)
204 205
205 206 content = msg["content"]
206 207 status = content['status']
207 208
208 209 if status == 'aborted':
209 210 self.write('Aborted\n')
210 211 return
211 212 elif status == 'ok':
212 213 # print execution payloads as well:
213 214 for item in content["payload"]:
214 215 text = item.get('text', None)
215 216 if text:
216 217 page.page(text)
217 218
218 219 elif status == 'error':
219 220 for frame in content["traceback"]:
220 221 print(frame, file=io.stderr)
221 222
222 223 self.execution_count = int(content["execution_count"] + 1)
223 224
224 225
225 def handle_iopub(self, msg_id):
226 """ Method to process subscribe channel's messages
226 def handle_iopub(self, msg_id=''):
227 """Process messages on the IOPub channel
227 228
228 229 This method consumes and processes messages on the IOPub channel,
229 230 such as stdout, stderr, pyout and status.
230 231
231 It only displays output that is caused by the given msg_id
232 It only displays output that is caused by this session.
232 233 """
233 234 while self.client.iopub_channel.msg_ready():
234 235 sub_msg = self.client.iopub_channel.get_msg()
235 236 msg_type = sub_msg['header']['msg_type']
236 237 parent = sub_msg["parent_header"]
237 if (not parent) or msg_id == parent['msg_id']:
238
239 if parent.get("session", self.session_id) == self.session_id:
238 240 if msg_type == 'status':
239 state = self._execution_state = sub_msg["content"]["execution_state"]
240 # idle messages mean an individual sequence is complete,
241 # so break out of consumption to allow other things to take over.
242 if state == 'idle':
243 break
244
241 self._execution_state = sub_msg["content"]["execution_state"]
245 242 elif msg_type == 'stream':
246 243 if sub_msg["content"]["name"] == "stdout":
247 244 print(sub_msg["content"]["data"], file=io.stdout, end="")
248 245 io.stdout.flush()
249 246 elif sub_msg["content"]["name"] == "stderr" :
250 247 print(sub_msg["content"]["data"], file=io.stderr, end="")
251 248 io.stderr.flush()
252 249
253 250 elif msg_type == 'pyout':
254 251 self.execution_count = int(sub_msg["content"]["execution_count"])
255 252 format_dict = sub_msg["content"]["data"]
256 253 self.handle_rich_data(format_dict)
257 254 # taken from DisplayHook.__call__:
258 255 hook = self.displayhook
259 256 hook.start_displayhook()
260 257 hook.write_output_prompt()
261 258 hook.write_format_data(format_dict)
262 259 hook.log_output(format_dict)
263 260 hook.finish_displayhook()
264 261
265 262 elif msg_type == 'display_data':
266 263 data = sub_msg["content"]["data"]
267 264 handled = self.handle_rich_data(data)
268 265 if not handled:
269 266 # if it was an image, we handled it by now
270 267 if 'text/plain' in data:
271 268 print(data['text/plain'])
272 269
273 270 _imagemime = {
274 271 'image/png': 'png',
275 272 'image/jpeg': 'jpeg',
276 273 'image/svg+xml': 'svg',
277 274 }
278 275
279 276 def handle_rich_data(self, data):
280 277 for mime in self.mime_preference:
281 278 if mime in data and mime in self._imagemime:
282 279 self.handle_image(data, mime)
283 280 return True
284 281
285 282 def handle_image(self, data, mime):
286 283 handler = getattr(
287 284 self, 'handle_image_{0}'.format(self.image_handler), None)
288 285 if handler:
289 286 handler(data, mime)
290 287
291 288 def handle_image_PIL(self, data, mime):
292 289 if mime not in ('image/png', 'image/jpeg'):
293 290 return
294 291 import PIL.Image
295 292 raw = base64.decodestring(data[mime].encode('ascii'))
296 293 img = PIL.Image.open(BytesIO(raw))
297 294 img.show()
298 295
299 296 def handle_image_stream(self, data, mime):
300 297 raw = base64.decodestring(data[mime].encode('ascii'))
301 298 imageformat = self._imagemime[mime]
302 299 fmt = dict(format=imageformat)
303 300 args = [s.format(**fmt) for s in self.stream_image_handler]
304 301 with open(os.devnull, 'w') as devnull:
305 302 proc = subprocess.Popen(
306 303 args, stdin=subprocess.PIPE,
307 304 stdout=devnull, stderr=devnull)
308 305 proc.communicate(raw)
309 306
310 307 def handle_image_tempfile(self, data, mime):
311 308 raw = base64.decodestring(data[mime].encode('ascii'))
312 309 imageformat = self._imagemime[mime]
313 310 filename = 'tmp.{0}'.format(imageformat)
314 311 with NamedFileInTemporaryDirectory(filename) as f, \
315 312 open(os.devnull, 'w') as devnull:
316 313 f.write(raw)
317 314 f.flush()
318 315 fmt = dict(file=f.name, format=imageformat)
319 316 args = [s.format(**fmt) for s in self.tempfile_image_handler]
320 317 subprocess.call(args, stdout=devnull, stderr=devnull)
321 318
322 319 def handle_image_callable(self, data, mime):
323 320 self.callable_image_handler(data)
324 321
325 322 def handle_stdin_request(self, msg_id, timeout=0.1):
326 323 """ Method to capture raw_input
327 324 """
328 325 msg_rep = self.client.stdin_channel.get_msg(timeout=timeout)
329 326 # in case any iopub came while we were waiting:
330 327 self.handle_iopub(msg_id)
331 328 if msg_id == msg_rep["parent_header"].get("msg_id"):
332 329 # wrap SIGINT handler
333 330 real_handler = signal.getsignal(signal.SIGINT)
334 331 def double_int(sig,frame):
335 332 # call real handler (forwards sigint to kernel),
336 333 # then raise local interrupt, stopping local raw_input
337 334 real_handler(sig,frame)
338 335 raise KeyboardInterrupt
339 336 signal.signal(signal.SIGINT, double_int)
340 337
341 338 try:
342 339 raw_data = input(msg_rep["content"]["prompt"])
343 340 except EOFError:
344 341 # turn EOFError into EOF character
345 342 raw_data = '\x04'
346 343 except KeyboardInterrupt:
347 344 sys.stdout.write('\n')
348 345 return
349 346 finally:
350 347 # restore SIGINT handler
351 348 signal.signal(signal.SIGINT, real_handler)
352 349
353 350 # only send stdin reply if there *was not* another request
354 351 # or execution finished while we were reading.
355 352 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
356 353 self.client.stdin_channel.input(raw_data)
357 354
358 355 def mainloop(self, display_banner=False):
359 356 while True:
360 357 try:
361 358 self.interact(display_banner=display_banner)
362 359 #self.interact_with_readline()
363 360 # XXX for testing of a readline-decoupled repl loop, call
364 361 # interact_with_readline above
365 362 break
366 363 except KeyboardInterrupt:
367 364 # this should not be necessary, but KeyboardInterrupt
368 365 # handling seems rather unpredictable...
369 366 self.write("\nKeyboardInterrupt in interact()\n")
370 367
371 368 def wait_for_kernel(self, timeout=None):
372 369 """method to wait for a kernel to be ready"""
373 370 tic = time.time()
374 371 self.client.hb_channel.unpause()
375 372 while True:
376 373 msg_id = self.client.kernel_info()
377 374 reply = None
378 375 while True:
379 376 try:
380 377 reply = self.client.get_shell_msg(timeout=1)
381 378 except Empty:
382 379 break
383 380 else:
384 381 if reply['parent_header'].get('msg_id') == msg_id:
385 382 return True
386 383 if timeout is not None \
387 384 and (time.time() - tic) > timeout \
388 385 and not self.client.hb_channel.is_beating():
389 386 # heart failed
390 387 return False
391 388 return True
392 389
393 390 def interact(self, display_banner=None):
394 391 """Closely emulate the interactive Python console."""
395 392
396 393 # batch run -> do not interact
397 394 if self.exit_now:
398 395 return
399 396
400 397 if display_banner is None:
401 398 display_banner = self.display_banner
402 399
403 400 if isinstance(display_banner, string_types):
404 401 self.show_banner(display_banner)
405 402 elif display_banner:
406 403 self.show_banner()
407 404
408 405 more = False
409 406
410 407 # run a non-empty no-op, so that we don't get a prompt until
411 408 # we know the kernel is ready. This keeps the connection
412 409 # message above the first prompt.
413 410 if not self.wait_for_kernel(self.kernel_timeout):
414 411 error("Kernel did not respond\n")
415 412 return
416 413
417 414 if self.has_readline:
418 415 self.readline_startup_hook(self.pre_readline)
419 416 hlen_b4_cell = self.readline.get_current_history_length()
420 417 else:
421 418 hlen_b4_cell = 0
422 419 # exit_now is set by a call to %Exit or %Quit, through the
423 420 # ask_exit callback.
424 421
425 422 while not self.exit_now:
426 423 if not self.client.is_alive():
427 424 # kernel died, prompt for action or exit
428 425
429 426 action = "restart" if self.manager else "wait for restart"
430 427 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
431 428 if ans:
432 429 if self.manager:
433 430 self.manager.restart_kernel(True)
434 431 self.wait_for_kernel(self.kernel_timeout)
435 432 else:
436 433 self.exit_now = True
437 434 continue
438 435 try:
439 436 # protect prompt block from KeyboardInterrupt
440 437 # when sitting on ctrl-C
441 438 self.hooks.pre_prompt_hook()
442 439 if more:
443 440 try:
444 441 prompt = self.prompt_manager.render('in2')
445 442 except Exception:
446 443 self.showtraceback()
447 444 if self.autoindent:
448 445 self.rl_do_indent = True
449 446
450 447 else:
451 448 try:
452 449 prompt = self.separate_in + self.prompt_manager.render('in')
453 450 except Exception:
454 451 self.showtraceback()
455 452
456 453 line = self.raw_input(prompt)
457 454 if self.exit_now:
458 455 # quick exit on sys.std[in|out] close
459 456 break
460 457 if self.autoindent:
461 458 self.rl_do_indent = False
462 459
463 460 except KeyboardInterrupt:
464 461 #double-guard against keyboardinterrupts during kbdint handling
465 462 try:
466 463 self.write('\nKeyboardInterrupt\n')
467 464 source_raw = self.input_splitter.raw_reset()
468 465 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
469 466 more = False
470 467 except KeyboardInterrupt:
471 468 pass
472 469 except EOFError:
473 470 if self.autoindent:
474 471 self.rl_do_indent = False
475 472 if self.has_readline:
476 473 self.readline_startup_hook(None)
477 474 self.write('\n')
478 475 self.exit()
479 476 except bdb.BdbQuit:
480 477 warn('The Python debugger has exited with a BdbQuit exception.\n'
481 478 'Because of how pdb handles the stack, it is impossible\n'
482 479 'for IPython to properly format this particular exception.\n'
483 480 'IPython will resume normal operation.')
484 481 except:
485 482 # exceptions here are VERY RARE, but they can be triggered
486 483 # asynchronously by signal handlers, for example.
487 484 self.showtraceback()
488 485 else:
489 486 try:
490 487 self.input_splitter.push(line)
491 488 more = self.input_splitter.push_accepts_more()
492 489 except SyntaxError:
493 490 # Run the code directly - run_cell takes care of displaying
494 491 # the exception.
495 492 more = False
496 493 if (self.SyntaxTB.last_syntax_error and
497 494 self.autoedit_syntax):
498 495 self.edit_syntax_error()
499 496 if not more:
500 497 source_raw = self.input_splitter.raw_reset()
501 498 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
502 499 self.run_cell(source_raw)
503 500
504 501
505 502 # Turn off the exit flag, so the mainloop can be restarted if desired
506 503 self.exit_now = False
General Comments 0
You need to be logged in to leave comments. Login now