##// END OF EJS Templates
Merge pull request #4678 from ivanov/console-display-text...
Min RK -
r13867:edb38312 merge
parent child Browse files
Show More
@@ -1,497 +1,501 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 data = sub_msg["content"]["data"]
267
267 handled = self.handle_rich_data(data)
268 if not handled:
269 # if it was an image, we handled it by now
270 if 'text/plain' in data:
271 print(data['text/plain'])
268
272
269 _imagemime = {
273 _imagemime = {
270 'image/png': 'png',
274 'image/png': 'png',
271 'image/jpeg': 'jpeg',
275 'image/jpeg': 'jpeg',
272 'image/svg+xml': 'svg',
276 'image/svg+xml': 'svg',
273 }
277 }
274
278
275 def handle_rich_data(self, data):
279 def handle_rich_data(self, data):
276 for mime in self.mime_preference:
280 for mime in self.mime_preference:
277 if mime in data and mime in self._imagemime:
281 if mime in data and mime in self._imagemime:
278 self.handle_image(data, mime)
282 self.handle_image(data, mime)
279 return
283 return True
280
284
281 def handle_image(self, data, mime):
285 def handle_image(self, data, mime):
282 handler = getattr(
286 handler = getattr(
283 self, 'handle_image_{0}'.format(self.image_handler), None)
287 self, 'handle_image_{0}'.format(self.image_handler), None)
284 if handler:
288 if handler:
285 handler(data, mime)
289 handler(data, mime)
286
290
287 def handle_image_PIL(self, data, mime):
291 def handle_image_PIL(self, data, mime):
288 if mime not in ('image/png', 'image/jpeg'):
292 if mime not in ('image/png', 'image/jpeg'):
289 return
293 return
290 import PIL.Image
294 import PIL.Image
291 raw = base64.decodestring(data[mime].encode('ascii'))
295 raw = base64.decodestring(data[mime].encode('ascii'))
292 img = PIL.Image.open(BytesIO(raw))
296 img = PIL.Image.open(BytesIO(raw))
293 img.show()
297 img.show()
294
298
295 def handle_image_stream(self, data, mime):
299 def handle_image_stream(self, data, mime):
296 raw = base64.decodestring(data[mime].encode('ascii'))
300 raw = base64.decodestring(data[mime].encode('ascii'))
297 imageformat = self._imagemime[mime]
301 imageformat = self._imagemime[mime]
298 fmt = dict(format=imageformat)
302 fmt = dict(format=imageformat)
299 args = [s.format(**fmt) for s in self.stream_image_handler]
303 args = [s.format(**fmt) for s in self.stream_image_handler]
300 with open(os.devnull, 'w') as devnull:
304 with open(os.devnull, 'w') as devnull:
301 proc = subprocess.Popen(
305 proc = subprocess.Popen(
302 args, stdin=subprocess.PIPE,
306 args, stdin=subprocess.PIPE,
303 stdout=devnull, stderr=devnull)
307 stdout=devnull, stderr=devnull)
304 proc.communicate(raw)
308 proc.communicate(raw)
305
309
306 def handle_image_tempfile(self, data, mime):
310 def handle_image_tempfile(self, data, mime):
307 raw = base64.decodestring(data[mime].encode('ascii'))
311 raw = base64.decodestring(data[mime].encode('ascii'))
308 imageformat = self._imagemime[mime]
312 imageformat = self._imagemime[mime]
309 filename = 'tmp.{0}'.format(imageformat)
313 filename = 'tmp.{0}'.format(imageformat)
310 with NamedFileInTemporaryDirectory(filename) as f, \
314 with NamedFileInTemporaryDirectory(filename) as f, \
311 open(os.devnull, 'w') as devnull:
315 open(os.devnull, 'w') as devnull:
312 f.write(raw)
316 f.write(raw)
313 f.flush()
317 f.flush()
314 fmt = dict(file=f.name, format=imageformat)
318 fmt = dict(file=f.name, format=imageformat)
315 args = [s.format(**fmt) for s in self.tempfile_image_handler]
319 args = [s.format(**fmt) for s in self.tempfile_image_handler]
316 subprocess.call(args, stdout=devnull, stderr=devnull)
320 subprocess.call(args, stdout=devnull, stderr=devnull)
317
321
318 def handle_image_callable(self, data, mime):
322 def handle_image_callable(self, data, mime):
319 self.callable_image_handler(data)
323 self.callable_image_handler(data)
320
324
321 def handle_stdin_request(self, msg_id, timeout=0.1):
325 def handle_stdin_request(self, msg_id, timeout=0.1):
322 """ Method to capture raw_input
326 """ Method to capture raw_input
323 """
327 """
324 msg_rep = self.client.stdin_channel.get_msg(timeout=timeout)
328 msg_rep = self.client.stdin_channel.get_msg(timeout=timeout)
325 # in case any iopub came while we were waiting:
329 # in case any iopub came while we were waiting:
326 self.handle_iopub(msg_id)
330 self.handle_iopub(msg_id)
327 if msg_id == msg_rep["parent_header"].get("msg_id"):
331 if msg_id == msg_rep["parent_header"].get("msg_id"):
328 # wrap SIGINT handler
332 # wrap SIGINT handler
329 real_handler = signal.getsignal(signal.SIGINT)
333 real_handler = signal.getsignal(signal.SIGINT)
330 def double_int(sig,frame):
334 def double_int(sig,frame):
331 # call real handler (forwards sigint to kernel),
335 # call real handler (forwards sigint to kernel),
332 # then raise local interrupt, stopping local raw_input
336 # then raise local interrupt, stopping local raw_input
333 real_handler(sig,frame)
337 real_handler(sig,frame)
334 raise KeyboardInterrupt
338 raise KeyboardInterrupt
335 signal.signal(signal.SIGINT, double_int)
339 signal.signal(signal.SIGINT, double_int)
336
340
337 try:
341 try:
338 raw_data = input(msg_rep["content"]["prompt"])
342 raw_data = input(msg_rep["content"]["prompt"])
339 except EOFError:
343 except EOFError:
340 # turn EOFError into EOF character
344 # turn EOFError into EOF character
341 raw_data = '\x04'
345 raw_data = '\x04'
342 except KeyboardInterrupt:
346 except KeyboardInterrupt:
343 sys.stdout.write('\n')
347 sys.stdout.write('\n')
344 return
348 return
345 finally:
349 finally:
346 # restore SIGINT handler
350 # restore SIGINT handler
347 signal.signal(signal.SIGINT, real_handler)
351 signal.signal(signal.SIGINT, real_handler)
348
352
349 # only send stdin reply if there *was not* another request
353 # only send stdin reply if there *was not* another request
350 # or execution finished while we were reading.
354 # or execution finished while we were reading.
351 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
355 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
352 self.client.stdin_channel.input(raw_data)
356 self.client.stdin_channel.input(raw_data)
353
357
354 def mainloop(self, display_banner=False):
358 def mainloop(self, display_banner=False):
355 while True:
359 while True:
356 try:
360 try:
357 self.interact(display_banner=display_banner)
361 self.interact(display_banner=display_banner)
358 #self.interact_with_readline()
362 #self.interact_with_readline()
359 # XXX for testing of a readline-decoupled repl loop, call
363 # XXX for testing of a readline-decoupled repl loop, call
360 # interact_with_readline above
364 # interact_with_readline above
361 break
365 break
362 except KeyboardInterrupt:
366 except KeyboardInterrupt:
363 # this should not be necessary, but KeyboardInterrupt
367 # this should not be necessary, but KeyboardInterrupt
364 # handling seems rather unpredictable...
368 # handling seems rather unpredictable...
365 self.write("\nKeyboardInterrupt in interact()\n")
369 self.write("\nKeyboardInterrupt in interact()\n")
366
370
367 def wait_for_kernel(self, timeout=None):
371 def wait_for_kernel(self, timeout=None):
368 """method to wait for a kernel to be ready"""
372 """method to wait for a kernel to be ready"""
369 tic = time.time()
373 tic = time.time()
370 self.client.hb_channel.unpause()
374 self.client.hb_channel.unpause()
371 while True:
375 while True:
372 msg_id = self.client.kernel_info()
376 msg_id = self.client.kernel_info()
373 reply = None
377 reply = None
374 while True:
378 while True:
375 try:
379 try:
376 reply = self.client.get_shell_msg(timeout=1)
380 reply = self.client.get_shell_msg(timeout=1)
377 except Empty:
381 except Empty:
378 break
382 break
379 else:
383 else:
380 if reply['parent_header'].get('msg_id') == msg_id:
384 if reply['parent_header'].get('msg_id') == msg_id:
381 return True
385 return True
382 if timeout is not None \
386 if timeout is not None \
383 and (time.time() - tic) > timeout \
387 and (time.time() - tic) > timeout \
384 and not self.client.hb_channel.is_beating():
388 and not self.client.hb_channel.is_beating():
385 # heart failed
389 # heart failed
386 return False
390 return False
387 return True
391 return True
388
392
389 def interact(self, display_banner=None):
393 def interact(self, display_banner=None):
390 """Closely emulate the interactive Python console."""
394 """Closely emulate the interactive Python console."""
391
395
392 # batch run -> do not interact
396 # batch run -> do not interact
393 if self.exit_now:
397 if self.exit_now:
394 return
398 return
395
399
396 if display_banner is None:
400 if display_banner is None:
397 display_banner = self.display_banner
401 display_banner = self.display_banner
398
402
399 if isinstance(display_banner, string_types):
403 if isinstance(display_banner, string_types):
400 self.show_banner(display_banner)
404 self.show_banner(display_banner)
401 elif display_banner:
405 elif display_banner:
402 self.show_banner()
406 self.show_banner()
403
407
404 more = False
408 more = False
405
409
406 # run a non-empty no-op, so that we don't get a prompt until
410 # 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
411 # we know the kernel is ready. This keeps the connection
408 # message above the first prompt.
412 # message above the first prompt.
409 if not self.wait_for_kernel(self.kernel_timeout):
413 if not self.wait_for_kernel(self.kernel_timeout):
410 error("Kernel did not respond\n")
414 error("Kernel did not respond\n")
411 return
415 return
412
416
413 if self.has_readline:
417 if self.has_readline:
414 self.readline_startup_hook(self.pre_readline)
418 self.readline_startup_hook(self.pre_readline)
415 hlen_b4_cell = self.readline.get_current_history_length()
419 hlen_b4_cell = self.readline.get_current_history_length()
416 else:
420 else:
417 hlen_b4_cell = 0
421 hlen_b4_cell = 0
418 # exit_now is set by a call to %Exit or %Quit, through the
422 # exit_now is set by a call to %Exit or %Quit, through the
419 # ask_exit callback.
423 # ask_exit callback.
420
424
421 while not self.exit_now:
425 while not self.exit_now:
422 if not self.client.is_alive():
426 if not self.client.is_alive():
423 # kernel died, prompt for action or exit
427 # kernel died, prompt for action or exit
424
428
425 action = "restart" if self.manager else "wait for restart"
429 action = "restart" if self.manager else "wait for restart"
426 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
430 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
427 if ans:
431 if ans:
428 if self.manager:
432 if self.manager:
429 self.manager.restart_kernel(True)
433 self.manager.restart_kernel(True)
430 self.wait_for_kernel(self.kernel_timeout)
434 self.wait_for_kernel(self.kernel_timeout)
431 else:
435 else:
432 self.exit_now = True
436 self.exit_now = True
433 continue
437 continue
434 try:
438 try:
435 # protect prompt block from KeyboardInterrupt
439 # protect prompt block from KeyboardInterrupt
436 # when sitting on ctrl-C
440 # when sitting on ctrl-C
437 self.hooks.pre_prompt_hook()
441 self.hooks.pre_prompt_hook()
438 if more:
442 if more:
439 try:
443 try:
440 prompt = self.prompt_manager.render('in2')
444 prompt = self.prompt_manager.render('in2')
441 except Exception:
445 except Exception:
442 self.showtraceback()
446 self.showtraceback()
443 if self.autoindent:
447 if self.autoindent:
444 self.rl_do_indent = True
448 self.rl_do_indent = True
445
449
446 else:
450 else:
447 try:
451 try:
448 prompt = self.separate_in + self.prompt_manager.render('in')
452 prompt = self.separate_in + self.prompt_manager.render('in')
449 except Exception:
453 except Exception:
450 self.showtraceback()
454 self.showtraceback()
451
455
452 line = self.raw_input(prompt)
456 line = self.raw_input(prompt)
453 if self.exit_now:
457 if self.exit_now:
454 # quick exit on sys.std[in|out] close
458 # quick exit on sys.std[in|out] close
455 break
459 break
456 if self.autoindent:
460 if self.autoindent:
457 self.rl_do_indent = False
461 self.rl_do_indent = False
458
462
459 except KeyboardInterrupt:
463 except KeyboardInterrupt:
460 #double-guard against keyboardinterrupts during kbdint handling
464 #double-guard against keyboardinterrupts during kbdint handling
461 try:
465 try:
462 self.write('\nKeyboardInterrupt\n')
466 self.write('\nKeyboardInterrupt\n')
463 source_raw = self.input_splitter.source_raw_reset()[1]
467 source_raw = self.input_splitter.source_raw_reset()[1]
464 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
468 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
465 more = False
469 more = False
466 except KeyboardInterrupt:
470 except KeyboardInterrupt:
467 pass
471 pass
468 except EOFError:
472 except EOFError:
469 if self.autoindent:
473 if self.autoindent:
470 self.rl_do_indent = False
474 self.rl_do_indent = False
471 if self.has_readline:
475 if self.has_readline:
472 self.readline_startup_hook(None)
476 self.readline_startup_hook(None)
473 self.write('\n')
477 self.write('\n')
474 self.exit()
478 self.exit()
475 except bdb.BdbQuit:
479 except bdb.BdbQuit:
476 warn('The Python debugger has exited with a BdbQuit exception.\n'
480 warn('The Python debugger has exited with a BdbQuit exception.\n'
477 'Because of how pdb handles the stack, it is impossible\n'
481 'Because of how pdb handles the stack, it is impossible\n'
478 'for IPython to properly format this particular exception.\n'
482 'for IPython to properly format this particular exception.\n'
479 'IPython will resume normal operation.')
483 'IPython will resume normal operation.')
480 except:
484 except:
481 # exceptions here are VERY RARE, but they can be triggered
485 # exceptions here are VERY RARE, but they can be triggered
482 # asynchronously by signal handlers, for example.
486 # asynchronously by signal handlers, for example.
483 self.showtraceback()
487 self.showtraceback()
484 else:
488 else:
485 self.input_splitter.push(line)
489 self.input_splitter.push(line)
486 more = self.input_splitter.push_accepts_more()
490 more = self.input_splitter.push_accepts_more()
487 if (self.SyntaxTB.last_syntax_error and
491 if (self.SyntaxTB.last_syntax_error and
488 self.autoedit_syntax):
492 self.autoedit_syntax):
489 self.edit_syntax_error()
493 self.edit_syntax_error()
490 if not more:
494 if not more:
491 source_raw = self.input_splitter.source_raw_reset()[1]
495 source_raw = self.input_splitter.source_raw_reset()[1]
492 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
496 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
493 self.run_cell(source_raw)
497 self.run_cell(source_raw)
494
498
495
499
496 # Turn off the exit flag, so the mainloop can be restarted if desired
500 # Turn off the exit flag, so the mainloop can be restarted if desired
497 self.exit_now = False
501 self.exit_now = False
@@ -1,63 +1,83 b''
1 """Tests for two-process terminal frontend
1 """Tests for two-process terminal frontend
2
2
3 Currently only has the most simple test possible, starting a console and running
3 Currently only has the most simple test possible, starting a console and running
4 a single command.
4 a single command.
5
5
6 Authors:
6 Authors:
7
7
8 * Min RK
8 * Min RK
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import sys
15 import sys
16 import time
17
16
18 import nose.tools as nt
19 from nose import SkipTest
17 from nose import SkipTest
20
18
21 import IPython.testing.tools as tt
19 import IPython.testing.tools as tt
22 from IPython.testing import decorators as dec
20 from IPython.testing import decorators as dec
23 from IPython.utils import py3compat
24
21
25 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
26 # Tests
23 # Tests
27 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
28
25
29 @dec.skip_win32
26 @dec.skip_win32
30 def test_console_starts():
27 def test_console_starts():
31 """test that `ipython console` starts a terminal"""
28 """test that `ipython console` starts a terminal"""
29 p, pexpect, t = start_console()
30 p.sendline('5')
31 idx = p.expect([r'Out\[\d+\]: 5', pexpect.EOF], timeout=t)
32 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
33 stop_console(p, pexpect, t)
34
35 def test_help_output():
36 """ipython console --help-all works"""
37 tt.help_all_output_test('console')
38
39
40 def test_display_text():
41 "Ensure display protocol plain/text key is supported"
42 # equivalent of:
43 #
44 # x = %lsmagic
45 # from IPython.display import display; display(x);
46 p, pexpect, t = start_console()
47 p.sendline('x = %lsmagic')
48 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
49 p.sendline('from IPython.display import display; display(x);')
50 p.expect([r'Available line magics:', pexpect.EOF], timeout=t)
51 stop_console(p, pexpect, t)
52
53 def stop_console(p, pexpect, t):
54 "Stop a running `ipython console` running via pexpect"
55 # send ctrl-D;ctrl-D to exit
56 p.sendeof()
57 p.sendeof()
58 p.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=t)
59 if p.isalive():
60 p.terminate()
61
62
63 def start_console():
64 "Start `ipython console` using pexpect"
32 from IPython.external import pexpect
65 from IPython.external import pexpect
33
66
34 args = ['console', '--colors=NoColor']
67 args = ['console', '--colors=NoColor']
35 # FIXME: remove workaround for 2.6 support
68 # FIXME: remove workaround for 2.6 support
36 if sys.version_info[:2] > (2,6):
69 if sys.version_info[:2] > (2,6):
37 args = ['-m', 'IPython'] + args
70 args = ['-m', 'IPython'] + args
38 cmd = sys.executable
71 cmd = sys.executable
39 else:
72 else:
40 cmd = 'ipython'
73 cmd = 'ipython'
41
74
42 try:
75 try:
43 p = pexpect.spawn(cmd, args=args)
76 p = pexpect.spawn(cmd, args=args)
44 except IOError:
77 except IOError:
45 raise SkipTest("Couldn't find command %s" % cmd)
78 raise SkipTest("Couldn't find command %s" % cmd)
46
79
47 # timeout after one minute
80 # timeout after one minute
48 t = 60
81 t = 60
49 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
82 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
50 p.sendline('5')
83 return p, pexpect, t
51 idx = p.expect([r'Out\[\d+\]: 5', pexpect.EOF], timeout=t)
52 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
53 # send ctrl-D;ctrl-D to exit
54 p.sendeof()
55 p.sendeof()
56 p.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=t)
57 if p.isalive():
58 p.terminate()
59
60 def test_help_output():
61 """ipython console --help-all works"""
62 tt.help_all_output_test('console')
63
General Comments 0
You need to be logged in to leave comments. Login now