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