##// END OF EJS Templates
displaypub b64 encoding is non-destructive...
MinRK -
Show More
@@ -1,73 +1,75 b''
1 1 import __builtin__
2 2 import sys
3 3 from base64 import encodestring
4 4
5 5 from IPython.core.displayhook import DisplayHook
6 6 from IPython.utils.traitlets import Instance, Dict
7 7 from session import extract_header, Session
8 8
9 9 class ZMQDisplayHook(object):
10 10 """A simple displayhook that publishes the object's repr over a ZeroMQ
11 11 socket."""
12 12 topic=None
13 13
14 14 def __init__(self, session, pub_socket):
15 15 self.session = session
16 16 self.pub_socket = pub_socket
17 17 self.parent_header = {}
18 18
19 19 def __call__(self, obj):
20 20 if obj is None:
21 21 return
22 22
23 23 __builtin__._ = obj
24 24 sys.stdout.flush()
25 25 sys.stderr.flush()
26 26 msg = self.session.send(self.pub_socket, u'pyout', {u'data':repr(obj)},
27 27 parent=self.parent_header, ident=self.topic)
28 28
29 29 def set_parent(self, parent):
30 30 self.parent_header = extract_header(parent)
31 31
32 32
33 33 def _encode_binary(format_dict):
34 encoded = format_dict.copy()
34 35 pngdata = format_dict.get('image/png')
35 if pngdata is not None:
36 format_dict['image/png'] = encodestring(pngdata).decode('ascii')
36 if isinstance(pngdata, bytes):
37 encoded['image/png'] = encodestring(pngdata).decode('ascii')
37 38 jpegdata = format_dict.get('image/jpeg')
38 if jpegdata is not None:
39 format_dict['image/jpeg'] = encodestring(jpegdata).decode('ascii')
39 if isinstance(jpegdata, bytes):
40 encoded['image/jpeg'] = encodestring(jpegdata).decode('ascii')
41
42 return encoded
40 43
41 44
42 45 class ZMQShellDisplayHook(DisplayHook):
43 46 """A displayhook subclass that publishes data using ZeroMQ. This is intended
44 47 to work with an InteractiveShell instance. It sends a dict of different
45 48 representations of the object."""
46 49 topic=None
47 50
48 51 session = Instance(Session)
49 52 pub_socket = Instance('zmq.Socket')
50 53 parent_header = Dict({})
51 54
52 55 def set_parent(self, parent):
53 56 """Set the parent for outbound messages."""
54 57 self.parent_header = extract_header(parent)
55 58
56 59 def start_displayhook(self):
57 60 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
58 61
59 62 def write_output_prompt(self):
60 63 """Write the output prompt."""
61 64 self.msg['content']['execution_count'] = self.prompt_count
62 65
63 66 def write_format_data(self, format_dict):
64 _encode_binary(format_dict)
65 self.msg['content']['data'] = format_dict
67 self.msg['content']['data'] = _encode_binary(format_dict)
66 68
67 69 def finish_displayhook(self):
68 70 """Finish up all displayhook activities."""
69 71 sys.stdout.flush()
70 72 sys.stderr.flush()
71 73 self.session.send(self.pub_socket, self.msg, ident=self.topic)
72 74 self.msg = None
73 75
@@ -1,540 +1,539 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import inspect
20 20 import os
21 21 import sys
22 22 import time
23 23 from subprocess import Popen, PIPE
24 24
25 25 # System library imports
26 26 from zmq.eventloop import ioloop
27 27
28 28 # Our own
29 29 from IPython.core.interactiveshell import (
30 30 InteractiveShell, InteractiveShellABC
31 31 )
32 32 from IPython.core import page, pylabtools
33 33 from IPython.core.autocall import ZMQExitAutocall
34 34 from IPython.core.displaypub import DisplayPublisher
35 35 from IPython.core.macro import Macro
36 36 from IPython.core.magics import MacroToEdit
37 37 from IPython.core.payloadpage import install_payload_page
38 38 from IPython.lib.kernel import (
39 39 get_connection_file, get_connection_info, connect_qtconsole
40 40 )
41 41 from IPython.testing.skipdoctest import skip_doctest
42 42 from IPython.utils import io
43 43 from IPython.utils.jsonutil import json_clean
44 44 from IPython.utils.path import get_py_filename
45 45 from IPython.utils.process import arg_split
46 46 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
47 47 from IPython.utils.warn import warn, error
48 48 from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_binary
49 49 from IPython.zmq.session import extract_header
50 50 from session import Session
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # Functions and classes
54 54 #-----------------------------------------------------------------------------
55 55
56 56 class ZMQDisplayPublisher(DisplayPublisher):
57 57 """A display publisher that publishes data using a ZeroMQ PUB socket."""
58 58
59 59 session = Instance(Session)
60 60 pub_socket = Instance('zmq.Socket')
61 61 parent_header = Dict({})
62 62 topic = CBytes(b'displaypub')
63 63
64 64 def set_parent(self, parent):
65 65 """Set the parent for outbound messages."""
66 66 self.parent_header = extract_header(parent)
67 67
68 68 def _flush_streams(self):
69 69 """flush IO Streams prior to display"""
70 70 sys.stdout.flush()
71 71 sys.stderr.flush()
72 72
73 73 def publish(self, source, data, metadata=None):
74 74 self._flush_streams()
75 75 if metadata is None:
76 76 metadata = {}
77 77 self._validate_data(source, data, metadata)
78 78 content = {}
79 79 content['source'] = source
80 _encode_binary(data)
81 content['data'] = data
80 content['data'] = _encode_binary(data)
82 81 content['metadata'] = metadata
83 82 self.session.send(
84 83 self.pub_socket, u'display_data', json_clean(content),
85 84 parent=self.parent_header, ident=self.topic,
86 85 )
87 86
88 87 def clear_output(self, stdout=True, stderr=True, other=True):
89 88 content = dict(stdout=stdout, stderr=stderr, other=other)
90 89
91 90 if stdout:
92 91 print('\r', file=sys.stdout, end='')
93 92 if stderr:
94 93 print('\r', file=sys.stderr, end='')
95 94
96 95 self._flush_streams()
97 96
98 97 self.session.send(
99 98 self.pub_socket, u'clear_output', content,
100 99 parent=self.parent_header, ident=self.topic,
101 100 )
102 101
103 102 class ZMQInteractiveShell(InteractiveShell):
104 103 """A subclass of InteractiveShell for ZMQ."""
105 104
106 105 displayhook_class = Type(ZMQShellDisplayHook)
107 106 display_pub_class = Type(ZMQDisplayPublisher)
108 107
109 108 # Override the traitlet in the parent class, because there's no point using
110 109 # readline for the kernel. Can be removed when the readline code is moved
111 110 # to the terminal frontend.
112 111 colors_force = CBool(True)
113 112 readline_use = CBool(False)
114 113 # autoindent has no meaning in a zmqshell, and attempting to enable it
115 114 # will print a warning in the absence of readline.
116 115 autoindent = CBool(False)
117 116
118 117 exiter = Instance(ZMQExitAutocall)
119 118 def _exiter_default(self):
120 119 return ZMQExitAutocall(self)
121 120
122 121 def _exit_now_changed(self, name, old, new):
123 122 """stop eventloop when exit_now fires"""
124 123 if new:
125 124 loop = ioloop.IOLoop.instance()
126 125 loop.add_timeout(time.time()+0.1, loop.stop)
127 126
128 127 keepkernel_on_exit = None
129 128
130 129 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
131 130 # interactive input being read; we provide event loop support in ipkernel
132 131 from .eventloops import enable_gui
133 132 enable_gui = staticmethod(enable_gui)
134 133
135 134 def init_environment(self):
136 135 """Configure the user's environment.
137 136
138 137 """
139 138 env = os.environ
140 139 # These two ensure 'ls' produces nice coloring on BSD-derived systems
141 140 env['TERM'] = 'xterm-color'
142 141 env['CLICOLOR'] = '1'
143 142 # Since normal pagers don't work at all (over pexpect we don't have
144 143 # single-key control of the subprocess), try to disable paging in
145 144 # subprocesses as much as possible.
146 145 env['PAGER'] = 'cat'
147 146 env['GIT_PAGER'] = 'cat'
148 147
149 148 # And install the payload version of page.
150 149 install_payload_page()
151 150
152 151 def auto_rewrite_input(self, cmd):
153 152 """Called to show the auto-rewritten input for autocall and friends.
154 153
155 154 FIXME: this payload is currently not correctly processed by the
156 155 frontend.
157 156 """
158 157 new = self.prompt_manager.render('rewrite') + cmd
159 158 payload = dict(
160 159 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
161 160 transformed_input=new,
162 161 )
163 162 self.payload_manager.write_payload(payload)
164 163
165 164 def ask_exit(self):
166 165 """Engage the exit actions."""
167 166 self.exit_now = True
168 167 payload = dict(
169 168 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
170 169 exit=True,
171 170 keepkernel=self.keepkernel_on_exit,
172 171 )
173 172 self.payload_manager.write_payload(payload)
174 173
175 174 def _showtraceback(self, etype, evalue, stb):
176 175
177 176 exc_content = {
178 177 u'traceback' : stb,
179 178 u'ename' : unicode(etype.__name__),
180 179 u'evalue' : unicode(evalue)
181 180 }
182 181
183 182 dh = self.displayhook
184 183 # Send exception info over pub socket for other clients than the caller
185 184 # to pick up
186 185 topic = None
187 186 if dh.topic:
188 187 topic = dh.topic.replace(b'pyout', b'pyerr')
189 188
190 189 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
191 190
192 191 # FIXME - Hack: store exception info in shell object. Right now, the
193 192 # caller is reading this info after the fact, we need to fix this logic
194 193 # to remove this hack. Even uglier, we need to store the error status
195 194 # here, because in the main loop, the logic that sets it is being
196 195 # skipped because runlines swallows the exceptions.
197 196 exc_content[u'status'] = u'error'
198 197 self._reply_content = exc_content
199 198 # /FIXME
200 199
201 200 return exc_content
202 201
203 202 #------------------------------------------------------------------------
204 203 # Magic overrides
205 204 #------------------------------------------------------------------------
206 205 # Once the base class stops inheriting from magic, this code needs to be
207 206 # moved into a separate machinery as well. For now, at least isolate here
208 207 # the magics which this class needs to implement differently from the base
209 208 # class, or that are unique to it.
210 209
211 210 def magic_doctest_mode(self,parameter_s=''):
212 211 """Toggle doctest mode on and off.
213 212
214 213 This mode is intended to make IPython behave as much as possible like a
215 214 plain Python shell, from the perspective of how its prompts, exceptions
216 215 and output look. This makes it easy to copy and paste parts of a
217 216 session into doctests. It does so by:
218 217
219 218 - Changing the prompts to the classic ``>>>`` ones.
220 219 - Changing the exception reporting mode to 'Plain'.
221 220 - Disabling pretty-printing of output.
222 221
223 222 Note that IPython also supports the pasting of code snippets that have
224 223 leading '>>>' and '...' prompts in them. This means that you can paste
225 224 doctests from files or docstrings (even if they have leading
226 225 whitespace), and the code will execute correctly. You can then use
227 226 '%history -t' to see the translated history; this will give you the
228 227 input after removal of all the leading prompts and whitespace, which
229 228 can be pasted back into an editor.
230 229
231 230 With these features, you can switch into this mode easily whenever you
232 231 need to do testing and changes to doctests, without having to leave
233 232 your existing IPython session.
234 233 """
235 234
236 235 from IPython.utils.ipstruct import Struct
237 236
238 237 # Shorthands
239 238 shell = self.shell
240 239 disp_formatter = self.shell.display_formatter
241 240 ptformatter = disp_formatter.formatters['text/plain']
242 241 # dstore is a data store kept in the instance metadata bag to track any
243 242 # changes we make, so we can undo them later.
244 243 dstore = shell.meta.setdefault('doctest_mode', Struct())
245 244 save_dstore = dstore.setdefault
246 245
247 246 # save a few values we'll need to recover later
248 247 mode = save_dstore('mode', False)
249 248 save_dstore('rc_pprint', ptformatter.pprint)
250 249 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
251 250 save_dstore('xmode', shell.InteractiveTB.mode)
252 251
253 252 if mode == False:
254 253 # turn on
255 254 ptformatter.pprint = False
256 255 disp_formatter.plain_text_only = True
257 256 shell.magic_xmode('Plain')
258 257 else:
259 258 # turn off
260 259 ptformatter.pprint = dstore.rc_pprint
261 260 disp_formatter.plain_text_only = dstore.rc_plain_text_only
262 261 shell.magic_xmode(dstore.xmode)
263 262
264 263 # Store new mode and inform on console
265 264 dstore.mode = bool(1-int(mode))
266 265 mode_label = ['OFF','ON'][dstore.mode]
267 266 print('Doctest mode is:', mode_label)
268 267
269 268 # Send the payload back so that clients can modify their prompt display
270 269 payload = dict(
271 270 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
272 271 mode=dstore.mode)
273 272 self.payload_manager.write_payload(payload)
274 273
275 274 @skip_doctest
276 275 def magic_edit(self,parameter_s='',last_call=['','']):
277 276 """Bring up an editor and execute the resulting code.
278 277
279 278 Usage:
280 279 %edit [options] [args]
281 280
282 281 %edit runs an external text editor. You will need to set the command for
283 282 this editor via the ``TerminalInteractiveShell.editor`` option in your
284 283 configuration file before it will work.
285 284
286 285 This command allows you to conveniently edit multi-line code right in
287 286 your IPython session.
288 287
289 288 If called without arguments, %edit opens up an empty editor with a
290 289 temporary file and will execute the contents of this file when you
291 290 close it (don't forget to save it!).
292 291
293 292
294 293 Options:
295 294
296 295 -n <number>: open the editor at a specified line number. By default,
297 296 the IPython editor hook uses the unix syntax 'editor +N filename', but
298 297 you can configure this by providing your own modified hook if your
299 298 favorite editor supports line-number specifications with a different
300 299 syntax.
301 300
302 301 -p: this will call the editor with the same data as the previous time
303 302 it was used, regardless of how long ago (in your current session) it
304 303 was.
305 304
306 305 -r: use 'raw' input. This option only applies to input taken from the
307 306 user's history. By default, the 'processed' history is used, so that
308 307 magics are loaded in their transformed version to valid Python. If
309 308 this option is given, the raw input as typed as the command line is
310 309 used instead. When you exit the editor, it will be executed by
311 310 IPython's own processor.
312 311
313 312 -x: do not execute the edited code immediately upon exit. This is
314 313 mainly useful if you are editing programs which need to be called with
315 314 command line arguments, which you can then do using %run.
316 315
317 316
318 317 Arguments:
319 318
320 319 If arguments are given, the following possibilites exist:
321 320
322 321 - The arguments are numbers or pairs of colon-separated numbers (like
323 322 1 4:8 9). These are interpreted as lines of previous input to be
324 323 loaded into the editor. The syntax is the same of the %macro command.
325 324
326 325 - If the argument doesn't start with a number, it is evaluated as a
327 326 variable and its contents loaded into the editor. You can thus edit
328 327 any string which contains python code (including the result of
329 328 previous edits).
330 329
331 330 - If the argument is the name of an object (other than a string),
332 331 IPython will try to locate the file where it was defined and open the
333 332 editor at the point where it is defined. You can use `%edit function`
334 333 to load an editor exactly at the point where 'function' is defined,
335 334 edit it and have the file be executed automatically.
336 335
337 336 If the object is a macro (see %macro for details), this opens up your
338 337 specified editor with a temporary file containing the macro's data.
339 338 Upon exit, the macro is reloaded with the contents of the file.
340 339
341 340 Note: opening at an exact line is only supported under Unix, and some
342 341 editors (like kedit and gedit up to Gnome 2.8) do not understand the
343 342 '+NUMBER' parameter necessary for this feature. Good editors like
344 343 (X)Emacs, vi, jed, pico and joe all do.
345 344
346 345 - If the argument is not found as a variable, IPython will look for a
347 346 file with that name (adding .py if necessary) and load it into the
348 347 editor. It will execute its contents with execfile() when you exit,
349 348 loading any code in the file into your interactive namespace.
350 349
351 350 After executing your code, %edit will return as output the code you
352 351 typed in the editor (except when it was an existing file). This way
353 352 you can reload the code in further invocations of %edit as a variable,
354 353 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
355 354 the output.
356 355
357 356 Note that %edit is also available through the alias %ed.
358 357
359 358 This is an example of creating a simple function inside the editor and
360 359 then modifying it. First, start up the editor:
361 360
362 361 In [1]: ed
363 362 Editing... done. Executing edited code...
364 363 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
365 364
366 365 We can then call the function foo():
367 366
368 367 In [2]: foo()
369 368 foo() was defined in an editing session
370 369
371 370 Now we edit foo. IPython automatically loads the editor with the
372 371 (temporary) file where foo() was previously defined:
373 372
374 373 In [3]: ed foo
375 374 Editing... done. Executing edited code...
376 375
377 376 And if we call foo() again we get the modified version:
378 377
379 378 In [4]: foo()
380 379 foo() has now been changed!
381 380
382 381 Here is an example of how to edit a code snippet successive
383 382 times. First we call the editor:
384 383
385 384 In [5]: ed
386 385 Editing... done. Executing edited code...
387 386 hello
388 387 Out[5]: "print 'hello'n"
389 388
390 389 Now we call it again with the previous output (stored in _):
391 390
392 391 In [6]: ed _
393 392 Editing... done. Executing edited code...
394 393 hello world
395 394 Out[6]: "print 'hello world'n"
396 395
397 396 Now we call it with the output #8 (stored in _8, also as Out[8]):
398 397
399 398 In [7]: ed _8
400 399 Editing... done. Executing edited code...
401 400 hello again
402 401 Out[7]: "print 'hello again'n"
403 402 """
404 403
405 404 opts,args = self.parse_options(parameter_s,'prn:')
406 405
407 406 try:
408 407 filename, lineno, _ = self._find_edit_target(args, opts, last_call)
409 408 except MacroToEdit as e:
410 409 # TODO: Implement macro editing over 2 processes.
411 410 print("Macro editing not yet implemented in 2-process model.")
412 411 return
413 412
414 413 # Make sure we send to the client an absolute path, in case the working
415 414 # directory of client and kernel don't match
416 415 filename = os.path.abspath(filename)
417 416
418 417 payload = {
419 418 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
420 419 'filename' : filename,
421 420 'line_number' : lineno
422 421 }
423 422 self.payload_manager.write_payload(payload)
424 423
425 424 # A few magics that are adapted to the specifics of using pexpect and a
426 425 # remote terminal
427 426
428 427 def magic_clear(self, arg_s):
429 428 """Clear the terminal."""
430 429 if os.name == 'posix':
431 430 self.shell.system("clear")
432 431 else:
433 432 self.shell.system("cls")
434 433
435 434 if os.name == 'nt':
436 435 # This is the usual name in windows
437 436 magic_cls = magic_clear
438 437
439 438 # Terminal pagers won't work over pexpect, but we do have our own pager
440 439
441 440 def magic_less(self, arg_s):
442 441 """Show a file through the pager.
443 442
444 443 Files ending in .py are syntax-highlighted."""
445 444 cont = open(arg_s).read()
446 445 if arg_s.endswith('.py'):
447 446 cont = self.shell.pycolorize(cont)
448 447 page.page(cont)
449 448
450 449 magic_more = magic_less
451 450
452 451 # Man calls a pager, so we also need to redefine it
453 452 if os.name == 'posix':
454 453 def magic_man(self, arg_s):
455 454 """Find the man page for the given command and display in pager."""
456 455 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
457 456 split=False))
458 457
459 458 # FIXME: this is specific to the GUI, so we should let the gui app load
460 459 # magics at startup that are only for the gui. Once the gui app has proper
461 460 # profile and configuration management, we can have it initialize a kernel
462 461 # with a special config file that provides these.
463 462 def magic_guiref(self, arg_s):
464 463 """Show a basic reference about the GUI console."""
465 464 from IPython.core.usage import gui_reference
466 465 page.page(gui_reference, auto_html=True)
467 466
468 467 def magic_connect_info(self, arg_s):
469 468 """Print information for connecting other clients to this kernel
470 469
471 470 It will print the contents of this session's connection file, as well as
472 471 shortcuts for local clients.
473 472
474 473 In the simplest case, when called from the most recently launched kernel,
475 474 secondary clients can be connected, simply with:
476 475
477 476 $> ipython <app> --existing
478 477
479 478 """
480 479
481 480 from IPython.core.application import BaseIPythonApplication as BaseIPApp
482 481
483 482 if BaseIPApp.initialized():
484 483 app = BaseIPApp.instance()
485 484 security_dir = app.profile_dir.security_dir
486 485 profile = app.profile
487 486 else:
488 487 profile = 'default'
489 488 security_dir = ''
490 489
491 490 try:
492 491 connection_file = get_connection_file()
493 492 info = get_connection_info(unpack=False)
494 493 except Exception as e:
495 494 error("Could not get connection info: %r" % e)
496 495 return
497 496
498 497 # add profile flag for non-default profile
499 498 profile_flag = "--profile %s" % profile if profile != 'default' else ""
500 499
501 500 # if it's in the security dir, truncate to basename
502 501 if security_dir == os.path.dirname(connection_file):
503 502 connection_file = os.path.basename(connection_file)
504 503
505 504
506 505 print (info + '\n')
507 506 print ("Paste the above JSON into a file, and connect with:\n"
508 507 " $> ipython <app> --existing <file>\n"
509 508 "or, if you are local, you can connect with just:\n"
510 509 " $> ipython <app> --existing {0} {1}\n"
511 510 "or even just:\n"
512 511 " $> ipython <app> --existing {1}\n"
513 512 "if this is the most recent IPython session you have started.".format(
514 513 connection_file, profile_flag
515 514 )
516 515 )
517 516
518 517 def magic_qtconsole(self, arg_s):
519 518 """Open a qtconsole connected to this kernel.
520 519
521 520 Useful for connecting a qtconsole to running notebooks, for better
522 521 debugging.
523 522 """
524 523 try:
525 524 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
526 525 except Exception as e:
527 526 error("Could not start qtconsole: %r" % e)
528 527 return
529 528
530 529 def set_next_input(self, text):
531 530 """Send the specified text to the frontend to be presented at the next
532 531 input cell."""
533 532 payload = dict(
534 533 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
535 534 text=text
536 535 )
537 536 self.payload_manager.write_payload(payload)
538 537
539 538
540 539 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now