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