##// END OF EJS Templates
allow ipython console to handle text/plain display
Paul Ivanov -
Show More
@@ -1,497 +1,500 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 162 return
163 163
164 164 if cell.strip() == 'exit':
165 165 # explicitly handle 'exit' command
166 166 return self.ask_exit()
167 167
168 168 # flush stale replies, which could have been ignored, due to missed heartbeats
169 169 while self.client.shell_channel.msg_ready():
170 170 self.client.shell_channel.get_msg()
171 171 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
172 172 msg_id = self.client.shell_channel.execute(cell, not store_history)
173 173
174 174 # first thing is wait for any side effects (output, stdin, etc.)
175 175 self._executing = True
176 176 self._execution_state = "busy"
177 177 while self._execution_state != 'idle' and self.client.is_alive():
178 178 try:
179 179 self.handle_stdin_request(msg_id, timeout=0.05)
180 180 except Empty:
181 181 # display intermediate print statements, etc.
182 182 self.handle_iopub(msg_id)
183 183 pass
184 184
185 185 # after all of that is done, wait for the execute reply
186 186 while self.client.is_alive():
187 187 try:
188 188 self.handle_execute_reply(msg_id, timeout=0.05)
189 189 except Empty:
190 190 pass
191 191 else:
192 192 break
193 193 self._executing = False
194 194
195 195 #-----------------
196 196 # message handlers
197 197 #-----------------
198 198
199 199 def handle_execute_reply(self, msg_id, timeout=None):
200 200 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
201 201 if msg["parent_header"].get("msg_id", None) == msg_id:
202 202
203 203 self.handle_iopub(msg_id)
204 204
205 205 content = msg["content"]
206 206 status = content['status']
207 207
208 208 if status == 'aborted':
209 209 self.write('Aborted\n')
210 210 return
211 211 elif status == 'ok':
212 212 # print execution payloads as well:
213 213 for item in content["payload"]:
214 214 text = item.get('text', None)
215 215 if text:
216 216 page.page(text)
217 217
218 218 elif status == 'error':
219 219 for frame in content["traceback"]:
220 220 print(frame, file=io.stderr)
221 221
222 222 self.execution_count = int(content["execution_count"] + 1)
223 223
224 224
225 225 def handle_iopub(self, msg_id):
226 226 """ Method to process subscribe channel's messages
227 227
228 228 This method consumes and processes messages on the IOPub channel,
229 229 such as stdout, stderr, pyout and status.
230 230
231 231 It only displays output that is caused by the given msg_id
232 232 """
233 233 while self.client.iopub_channel.msg_ready():
234 234 sub_msg = self.client.iopub_channel.get_msg()
235 235 msg_type = sub_msg['header']['msg_type']
236 236 parent = sub_msg["parent_header"]
237 237 if (not parent) or msg_id == parent['msg_id']:
238 238 if msg_type == 'status':
239 239 state = self._execution_state = sub_msg["content"]["execution_state"]
240 240 # idle messages mean an individual sequence is complete,
241 241 # so break out of consumption to allow other things to take over.
242 242 if state == 'idle':
243 243 break
244 244
245 245 elif msg_type == 'stream':
246 246 if sub_msg["content"]["name"] == "stdout":
247 247 print(sub_msg["content"]["data"], file=io.stdout, end="")
248 248 io.stdout.flush()
249 249 elif sub_msg["content"]["name"] == "stderr" :
250 250 print(sub_msg["content"]["data"], file=io.stderr, end="")
251 251 io.stderr.flush()
252 252
253 253 elif msg_type == 'pyout':
254 254 self.execution_count = int(sub_msg["content"]["execution_count"])
255 255 format_dict = sub_msg["content"]["data"]
256 256 self.handle_rich_data(format_dict)
257 257 # taken from DisplayHook.__call__:
258 258 hook = self.displayhook
259 259 hook.start_displayhook()
260 260 hook.write_output_prompt()
261 261 hook.write_format_data(format_dict)
262 262 hook.log_output(format_dict)
263 263 hook.finish_displayhook()
264 264
265 265 elif msg_type == 'display_data':
266 266 self.handle_rich_data(sub_msg["content"]["data"])
267 267
268 268
269 269 _imagemime = {
270 270 'image/png': 'png',
271 271 'image/jpeg': 'jpeg',
272 272 'image/svg+xml': 'svg',
273 273 }
274 274
275 275 def handle_rich_data(self, data):
276 276 for mime in self.mime_preference:
277 277 if mime in data and mime in self._imagemime:
278 278 self.handle_image(data, mime)
279 279 return
280 # if it was an image, we handled it by now and returned
281 if 'text/plain' in data:
282 print(data['text/plain'])
280 283
281 284 def handle_image(self, data, mime):
282 285 handler = getattr(
283 286 self, 'handle_image_{0}'.format(self.image_handler), None)
284 287 if handler:
285 288 handler(data, mime)
286 289
287 290 def handle_image_PIL(self, data, mime):
288 291 if mime not in ('image/png', 'image/jpeg'):
289 292 return
290 293 import PIL.Image
291 294 raw = base64.decodestring(data[mime].encode('ascii'))
292 295 img = PIL.Image.open(BytesIO(raw))
293 296 img.show()
294 297
295 298 def handle_image_stream(self, data, mime):
296 299 raw = base64.decodestring(data[mime].encode('ascii'))
297 300 imageformat = self._imagemime[mime]
298 301 fmt = dict(format=imageformat)
299 302 args = [s.format(**fmt) for s in self.stream_image_handler]
300 303 with open(os.devnull, 'w') as devnull:
301 304 proc = subprocess.Popen(
302 305 args, stdin=subprocess.PIPE,
303 306 stdout=devnull, stderr=devnull)
304 307 proc.communicate(raw)
305 308
306 309 def handle_image_tempfile(self, data, mime):
307 310 raw = base64.decodestring(data[mime].encode('ascii'))
308 311 imageformat = self._imagemime[mime]
309 312 filename = 'tmp.{0}'.format(imageformat)
310 313 with NamedFileInTemporaryDirectory(filename) as f, \
311 314 open(os.devnull, 'w') as devnull:
312 315 f.write(raw)
313 316 f.flush()
314 317 fmt = dict(file=f.name, format=imageformat)
315 318 args = [s.format(**fmt) for s in self.tempfile_image_handler]
316 319 subprocess.call(args, stdout=devnull, stderr=devnull)
317 320
318 321 def handle_image_callable(self, data, mime):
319 322 self.callable_image_handler(data)
320 323
321 324 def handle_stdin_request(self, msg_id, timeout=0.1):
322 325 """ Method to capture raw_input
323 326 """
324 327 msg_rep = self.client.stdin_channel.get_msg(timeout=timeout)
325 328 # in case any iopub came while we were waiting:
326 329 self.handle_iopub(msg_id)
327 330 if msg_id == msg_rep["parent_header"].get("msg_id"):
328 331 # wrap SIGINT handler
329 332 real_handler = signal.getsignal(signal.SIGINT)
330 333 def double_int(sig,frame):
331 334 # call real handler (forwards sigint to kernel),
332 335 # then raise local interrupt, stopping local raw_input
333 336 real_handler(sig,frame)
334 337 raise KeyboardInterrupt
335 338 signal.signal(signal.SIGINT, double_int)
336 339
337 340 try:
338 341 raw_data = input(msg_rep["content"]["prompt"])
339 342 except EOFError:
340 343 # turn EOFError into EOF character
341 344 raw_data = '\x04'
342 345 except KeyboardInterrupt:
343 346 sys.stdout.write('\n')
344 347 return
345 348 finally:
346 349 # restore SIGINT handler
347 350 signal.signal(signal.SIGINT, real_handler)
348 351
349 352 # only send stdin reply if there *was not* another request
350 353 # or execution finished while we were reading.
351 354 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
352 355 self.client.stdin_channel.input(raw_data)
353 356
354 357 def mainloop(self, display_banner=False):
355 358 while True:
356 359 try:
357 360 self.interact(display_banner=display_banner)
358 361 #self.interact_with_readline()
359 362 # XXX for testing of a readline-decoupled repl loop, call
360 363 # interact_with_readline above
361 364 break
362 365 except KeyboardInterrupt:
363 366 # this should not be necessary, but KeyboardInterrupt
364 367 # handling seems rather unpredictable...
365 368 self.write("\nKeyboardInterrupt in interact()\n")
366 369
367 370 def wait_for_kernel(self, timeout=None):
368 371 """method to wait for a kernel to be ready"""
369 372 tic = time.time()
370 373 self.client.hb_channel.unpause()
371 374 while True:
372 375 msg_id = self.client.kernel_info()
373 376 reply = None
374 377 while True:
375 378 try:
376 379 reply = self.client.get_shell_msg(timeout=1)
377 380 except Empty:
378 381 break
379 382 else:
380 383 if reply['parent_header'].get('msg_id') == msg_id:
381 384 return True
382 385 if timeout is not None \
383 386 and (time.time() - tic) > timeout \
384 387 and not self.client.hb_channel.is_beating():
385 388 # heart failed
386 389 return False
387 390 return True
388 391
389 392 def interact(self, display_banner=None):
390 393 """Closely emulate the interactive Python console."""
391 394
392 395 # batch run -> do not interact
393 396 if self.exit_now:
394 397 return
395 398
396 399 if display_banner is None:
397 400 display_banner = self.display_banner
398 401
399 402 if isinstance(display_banner, string_types):
400 403 self.show_banner(display_banner)
401 404 elif display_banner:
402 405 self.show_banner()
403 406
404 407 more = False
405 408
406 409 # run a non-empty no-op, so that we don't get a prompt until
407 410 # we know the kernel is ready. This keeps the connection
408 411 # message above the first prompt.
409 412 if not self.wait_for_kernel(self.kernel_timeout):
410 413 error("Kernel did not respond\n")
411 414 return
412 415
413 416 if self.has_readline:
414 417 self.readline_startup_hook(self.pre_readline)
415 418 hlen_b4_cell = self.readline.get_current_history_length()
416 419 else:
417 420 hlen_b4_cell = 0
418 421 # exit_now is set by a call to %Exit or %Quit, through the
419 422 # ask_exit callback.
420 423
421 424 while not self.exit_now:
422 425 if not self.client.is_alive():
423 426 # kernel died, prompt for action or exit
424 427
425 428 action = "restart" if self.manager else "wait for restart"
426 429 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
427 430 if ans:
428 431 if self.manager:
429 432 self.manager.restart_kernel(True)
430 433 self.wait_for_kernel(self.kernel_timeout)
431 434 else:
432 435 self.exit_now = True
433 436 continue
434 437 try:
435 438 # protect prompt block from KeyboardInterrupt
436 439 # when sitting on ctrl-C
437 440 self.hooks.pre_prompt_hook()
438 441 if more:
439 442 try:
440 443 prompt = self.prompt_manager.render('in2')
441 444 except Exception:
442 445 self.showtraceback()
443 446 if self.autoindent:
444 447 self.rl_do_indent = True
445 448
446 449 else:
447 450 try:
448 451 prompt = self.separate_in + self.prompt_manager.render('in')
449 452 except Exception:
450 453 self.showtraceback()
451 454
452 455 line = self.raw_input(prompt)
453 456 if self.exit_now:
454 457 # quick exit on sys.std[in|out] close
455 458 break
456 459 if self.autoindent:
457 460 self.rl_do_indent = False
458 461
459 462 except KeyboardInterrupt:
460 463 #double-guard against keyboardinterrupts during kbdint handling
461 464 try:
462 465 self.write('\nKeyboardInterrupt\n')
463 466 source_raw = self.input_splitter.source_raw_reset()[1]
464 467 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
465 468 more = False
466 469 except KeyboardInterrupt:
467 470 pass
468 471 except EOFError:
469 472 if self.autoindent:
470 473 self.rl_do_indent = False
471 474 if self.has_readline:
472 475 self.readline_startup_hook(None)
473 476 self.write('\n')
474 477 self.exit()
475 478 except bdb.BdbQuit:
476 479 warn('The Python debugger has exited with a BdbQuit exception.\n'
477 480 'Because of how pdb handles the stack, it is impossible\n'
478 481 'for IPython to properly format this particular exception.\n'
479 482 'IPython will resume normal operation.')
480 483 except:
481 484 # exceptions here are VERY RARE, but they can be triggered
482 485 # asynchronously by signal handlers, for example.
483 486 self.showtraceback()
484 487 else:
485 488 self.input_splitter.push(line)
486 489 more = self.input_splitter.push_accepts_more()
487 490 if (self.SyntaxTB.last_syntax_error and
488 491 self.autoedit_syntax):
489 492 self.edit_syntax_error()
490 493 if not more:
491 494 source_raw = self.input_splitter.source_raw_reset()[1]
492 495 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
493 496 self.run_cell(source_raw)
494 497
495 498
496 499 # Turn off the exit flag, so the mainloop can be restarted if desired
497 500 self.exit_now = False
General Comments 0
You need to be logged in to leave comments. Login now