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