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