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