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