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