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