##// END OF EJS Templates
Merge pull request #2211 from minrk/datapub...
Min RK -
r8123:dfcd243b merge
parent child Browse files
Show More
@@ -0,0 +1,71
1 """Publishing
2 """
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2012 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 from IPython.config import Configurable
16
17 from IPython.utils.jsonutil import json_clean
18 from IPython.utils.traitlets import Instance, Dict, CBytes
19
20 from IPython.zmq.serialize import serialize_object
21 from IPython.zmq.session import Session, extract_header
22
23 #-----------------------------------------------------------------------------
24 # Code
25 #-----------------------------------------------------------------------------
26
27
28 class ZMQDataPublisher(Configurable):
29
30 topic = topic = CBytes(b'datapub')
31 session = Instance(Session)
32 pub_socket = Instance('zmq.Socket')
33 parent_header = Dict({})
34
35 def set_parent(self, parent):
36 """Set the parent for outbound messages."""
37 self.parent_header = extract_header(parent)
38
39 def publish_data(self, data):
40 """publish a data_message on the IOPub channel
41
42 Parameters
43 ----------
44
45 data : dict
46 The data to be published. Think of it as a namespace.
47 """
48 session = self.session
49 buffers = serialize_object(data,
50 buffer_threshold=session.buffer_threshold,
51 item_threshold=session.item_threshold,
52 )
53 content = json_clean(dict(keys=data.keys()))
54 session.send(self.pub_socket, 'data_message', content=content,
55 parent=self.parent_header,
56 buffers=buffers,
57 ident=self.topic,
58 )
59
60
61 def publish_data(data):
62 """publish a data_message on the IOPub channel
63
64 Parameters
65 ----------
66
67 data : dict
68 The data to be published. Think of it as a namespace.
69 """
70 from IPython.zmq.zmqshell import ZMQInteractiveShell
71 ZMQInteractiveShell.instance().data_pub.publish_data(data)
@@ -1,3018 +1,3027
1 1 # -*- coding: utf-8 -*-
2 2 """Main IPython class."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 from __future__ import with_statement
18 18 from __future__ import absolute_import
19 19 from __future__ import print_function
20 20
21 21 import __builtin__ as builtin_mod
22 22 import __future__
23 23 import abc
24 24 import ast
25 25 import atexit
26 26 import os
27 27 import re
28 28 import runpy
29 29 import sys
30 30 import tempfile
31 31 import types
32 32
33 33 # We need to use nested to support python 2.6, once we move to >=2.7, we can
34 34 # use the with keyword's new builtin support for nested managers
35 35 try:
36 36 from contextlib import nested
37 37 except:
38 38 from IPython.utils.nested_context import nested
39 39
40 40 from IPython.config.configurable import SingletonConfigurable
41 41 from IPython.core import debugger, oinspect
42 42 from IPython.core import history as ipcorehist
43 43 from IPython.core import magic
44 44 from IPython.core import page
45 45 from IPython.core import prefilter
46 46 from IPython.core import shadowns
47 47 from IPython.core import ultratb
48 48 from IPython.core.alias import AliasManager, AliasError
49 49 from IPython.core.autocall import ExitAutocall
50 50 from IPython.core.builtin_trap import BuiltinTrap
51 51 from IPython.core.compilerop import CachingCompiler
52 52 from IPython.core.display_trap import DisplayTrap
53 53 from IPython.core.displayhook import DisplayHook
54 54 from IPython.core.displaypub import DisplayPublisher
55 55 from IPython.core.error import UsageError
56 56 from IPython.core.extensions import ExtensionManager
57 57 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
58 58 from IPython.core.formatters import DisplayFormatter
59 59 from IPython.core.history import HistoryManager
60 60 from IPython.core.inputsplitter import IPythonInputSplitter, ESC_MAGIC, ESC_MAGIC2
61 61 from IPython.core.logger import Logger
62 62 from IPython.core.macro import Macro
63 63 from IPython.core.payload import PayloadManager
64 64 from IPython.core.plugin import PluginManager
65 65 from IPython.core.prefilter import PrefilterManager
66 66 from IPython.core.profiledir import ProfileDir
67 67 from IPython.core.pylabtools import pylab_activate
68 68 from IPython.core.prompts import PromptManager
69 69 from IPython.lib.latextools import LaTeXTool
70 70 from IPython.utils import PyColorize
71 71 from IPython.utils import io
72 72 from IPython.utils import py3compat
73 73 from IPython.utils import openpy
74 74 from IPython.utils.doctestreload import doctest_reload
75 75 from IPython.utils.io import ask_yes_no
76 76 from IPython.utils.ipstruct import Struct
77 77 from IPython.utils.path import get_home_dir, get_ipython_dir, get_py_filename, unquote_filename
78 78 from IPython.utils.pickleshare import PickleShareDB
79 79 from IPython.utils.process import system, getoutput
80 80 from IPython.utils.strdispatch import StrDispatch
81 81 from IPython.utils.syspathcontext import prepended_to_syspath
82 82 from IPython.utils.text import (format_screen, LSString, SList,
83 83 DollarFormatter)
84 84 from IPython.utils.traitlets import (Integer, CBool, CaselessStrEnum, Enum,
85 85 List, Unicode, Instance, Type)
86 86 from IPython.utils.warn import warn, error
87 87 import IPython.core.hooks
88 88
89 89 #-----------------------------------------------------------------------------
90 90 # Globals
91 91 #-----------------------------------------------------------------------------
92 92
93 93 # compiled regexps for autoindent management
94 94 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
95 95
96 96 #-----------------------------------------------------------------------------
97 97 # Utilities
98 98 #-----------------------------------------------------------------------------
99 99
100 100 def softspace(file, newvalue):
101 101 """Copied from code.py, to remove the dependency"""
102 102
103 103 oldvalue = 0
104 104 try:
105 105 oldvalue = file.softspace
106 106 except AttributeError:
107 107 pass
108 108 try:
109 109 file.softspace = newvalue
110 110 except (AttributeError, TypeError):
111 111 # "attribute-less object" or "read-only attributes"
112 112 pass
113 113 return oldvalue
114 114
115 115
116 116 def no_op(*a, **kw): pass
117 117
118 118 class NoOpContext(object):
119 119 def __enter__(self): pass
120 120 def __exit__(self, type, value, traceback): pass
121 121 no_op_context = NoOpContext()
122 122
123 123 class SpaceInInput(Exception): pass
124 124
125 125 class Bunch: pass
126 126
127 127
128 128 def get_default_colors():
129 129 if sys.platform=='darwin':
130 130 return "LightBG"
131 131 elif os.name=='nt':
132 132 return 'Linux'
133 133 else:
134 134 return 'Linux'
135 135
136 136
137 137 class SeparateUnicode(Unicode):
138 138 """A Unicode subclass to validate separate_in, separate_out, etc.
139 139
140 140 This is a Unicode based trait that converts '0'->'' and '\\n'->'\n'.
141 141 """
142 142
143 143 def validate(self, obj, value):
144 144 if value == '0': value = ''
145 145 value = value.replace('\\n','\n')
146 146 return super(SeparateUnicode, self).validate(obj, value)
147 147
148 148
149 149 class ReadlineNoRecord(object):
150 150 """Context manager to execute some code, then reload readline history
151 151 so that interactive input to the code doesn't appear when pressing up."""
152 152 def __init__(self, shell):
153 153 self.shell = shell
154 154 self._nested_level = 0
155 155
156 156 def __enter__(self):
157 157 if self._nested_level == 0:
158 158 try:
159 159 self.orig_length = self.current_length()
160 160 self.readline_tail = self.get_readline_tail()
161 161 except (AttributeError, IndexError): # Can fail with pyreadline
162 162 self.orig_length, self.readline_tail = 999999, []
163 163 self._nested_level += 1
164 164
165 165 def __exit__(self, type, value, traceback):
166 166 self._nested_level -= 1
167 167 if self._nested_level == 0:
168 168 # Try clipping the end if it's got longer
169 169 try:
170 170 e = self.current_length() - self.orig_length
171 171 if e > 0:
172 172 for _ in range(e):
173 173 self.shell.readline.remove_history_item(self.orig_length)
174 174
175 175 # If it still doesn't match, just reload readline history.
176 176 if self.current_length() != self.orig_length \
177 177 or self.get_readline_tail() != self.readline_tail:
178 178 self.shell.refill_readline_hist()
179 179 except (AttributeError, IndexError):
180 180 pass
181 181 # Returning False will cause exceptions to propagate
182 182 return False
183 183
184 184 def current_length(self):
185 185 return self.shell.readline.get_current_history_length()
186 186
187 187 def get_readline_tail(self, n=10):
188 188 """Get the last n items in readline history."""
189 189 end = self.shell.readline.get_current_history_length() + 1
190 190 start = max(end-n, 1)
191 191 ghi = self.shell.readline.get_history_item
192 192 return [ghi(x) for x in range(start, end)]
193 193
194 194 #-----------------------------------------------------------------------------
195 195 # Main IPython class
196 196 #-----------------------------------------------------------------------------
197 197
198 198 class InteractiveShell(SingletonConfigurable):
199 199 """An enhanced, interactive shell for Python."""
200 200
201 201 _instance = None
202 202
203 203 autocall = Enum((0,1,2), default_value=0, config=True, help=
204 204 """
205 205 Make IPython automatically call any callable object even if you didn't
206 206 type explicit parentheses. For example, 'str 43' becomes 'str(43)'
207 207 automatically. The value can be '0' to disable the feature, '1' for
208 208 'smart' autocall, where it is not applied if there are no more
209 209 arguments on the line, and '2' for 'full' autocall, where all callable
210 210 objects are automatically called (even if no arguments are present).
211 211 """
212 212 )
213 213 # TODO: remove all autoindent logic and put into frontends.
214 214 # We can't do this yet because even runlines uses the autoindent.
215 215 autoindent = CBool(True, config=True, help=
216 216 """
217 217 Autoindent IPython code entered interactively.
218 218 """
219 219 )
220 220 automagic = CBool(True, config=True, help=
221 221 """
222 222 Enable magic commands to be called without the leading %.
223 223 """
224 224 )
225 225 cache_size = Integer(1000, config=True, help=
226 226 """
227 227 Set the size of the output cache. The default is 1000, you can
228 228 change it permanently in your config file. Setting it to 0 completely
229 229 disables the caching system, and the minimum value accepted is 20 (if
230 230 you provide a value less than 20, it is reset to 0 and a warning is
231 231 issued). This limit is defined because otherwise you'll spend more
232 232 time re-flushing a too small cache than working
233 233 """
234 234 )
235 235 color_info = CBool(True, config=True, help=
236 236 """
237 237 Use colors for displaying information about objects. Because this
238 238 information is passed through a pager (like 'less'), and some pagers
239 239 get confused with color codes, this capability can be turned off.
240 240 """
241 241 )
242 242 colors = CaselessStrEnum(('NoColor','LightBG','Linux'),
243 243 default_value=get_default_colors(), config=True,
244 244 help="Set the color scheme (NoColor, Linux, or LightBG)."
245 245 )
246 246 colors_force = CBool(False, help=
247 247 """
248 248 Force use of ANSI color codes, regardless of OS and readline
249 249 availability.
250 250 """
251 251 # FIXME: This is essentially a hack to allow ZMQShell to show colors
252 252 # without readline on Win32. When the ZMQ formatting system is
253 253 # refactored, this should be removed.
254 254 )
255 255 debug = CBool(False, config=True)
256 256 deep_reload = CBool(False, config=True, help=
257 257 """
258 258 Enable deep (recursive) reloading by default. IPython can use the
259 259 deep_reload module which reloads changes in modules recursively (it
260 260 replaces the reload() function, so you don't need to change anything to
261 261 use it). deep_reload() forces a full reload of modules whose code may
262 262 have changed, which the default reload() function does not. When
263 263 deep_reload is off, IPython will use the normal reload(), but
264 264 deep_reload will still be available as dreload().
265 265 """
266 266 )
267 267 disable_failing_post_execute = CBool(False, config=True,
268 268 help="Don't call post-execute functions that have failed in the past."
269 269 )
270 270 display_formatter = Instance(DisplayFormatter)
271 271 displayhook_class = Type(DisplayHook)
272 272 display_pub_class = Type(DisplayPublisher)
273 data_pub_class = None
273 274
274 275 exit_now = CBool(False)
275 276 exiter = Instance(ExitAutocall)
276 277 def _exiter_default(self):
277 278 return ExitAutocall(self)
278 279 # Monotonically increasing execution counter
279 280 execution_count = Integer(1)
280 281 filename = Unicode("<ipython console>")
281 282 ipython_dir= Unicode('', config=True) # Set to get_ipython_dir() in __init__
282 283
283 284 # Input splitter, to split entire cells of input into either individual
284 285 # interactive statements or whole blocks.
285 286 input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
286 287 (), {})
287 288 logstart = CBool(False, config=True, help=
288 289 """
289 290 Start logging to the default log file.
290 291 """
291 292 )
292 293 logfile = Unicode('', config=True, help=
293 294 """
294 295 The name of the logfile to use.
295 296 """
296 297 )
297 298 logappend = Unicode('', config=True, help=
298 299 """
299 300 Start logging to the given file in append mode.
300 301 """
301 302 )
302 303 object_info_string_level = Enum((0,1,2), default_value=0,
303 304 config=True)
304 305 pdb = CBool(False, config=True, help=
305 306 """
306 307 Automatically call the pdb debugger after every exception.
307 308 """
308 309 )
309 310 multiline_history = CBool(sys.platform != 'win32', config=True,
310 311 help="Save multi-line entries as one entry in readline history"
311 312 )
312 313
313 314 # deprecated prompt traits:
314 315
315 316 prompt_in1 = Unicode('In [\\#]: ', config=True,
316 317 help="Deprecated, use PromptManager.in_template")
317 318 prompt_in2 = Unicode(' .\\D.: ', config=True,
318 319 help="Deprecated, use PromptManager.in2_template")
319 320 prompt_out = Unicode('Out[\\#]: ', config=True,
320 321 help="Deprecated, use PromptManager.out_template")
321 322 prompts_pad_left = CBool(True, config=True,
322 323 help="Deprecated, use PromptManager.justify")
323 324
324 325 def _prompt_trait_changed(self, name, old, new):
325 326 table = {
326 327 'prompt_in1' : 'in_template',
327 328 'prompt_in2' : 'in2_template',
328 329 'prompt_out' : 'out_template',
329 330 'prompts_pad_left' : 'justify',
330 331 }
331 332 warn("InteractiveShell.{name} is deprecated, use PromptManager.{newname}\n".format(
332 333 name=name, newname=table[name])
333 334 )
334 335 # protect against weird cases where self.config may not exist:
335 336 if self.config is not None:
336 337 # propagate to corresponding PromptManager trait
337 338 setattr(self.config.PromptManager, table[name], new)
338 339
339 340 _prompt_in1_changed = _prompt_trait_changed
340 341 _prompt_in2_changed = _prompt_trait_changed
341 342 _prompt_out_changed = _prompt_trait_changed
342 343 _prompt_pad_left_changed = _prompt_trait_changed
343 344
344 345 show_rewritten_input = CBool(True, config=True,
345 346 help="Show rewritten input, e.g. for autocall."
346 347 )
347 348
348 349 quiet = CBool(False, config=True)
349 350
350 351 history_length = Integer(10000, config=True)
351 352
352 353 # The readline stuff will eventually be moved to the terminal subclass
353 354 # but for now, we can't do that as readline is welded in everywhere.
354 355 readline_use = CBool(True, config=True)
355 356 readline_remove_delims = Unicode('-/~', config=True)
356 357 # don't use \M- bindings by default, because they
357 358 # conflict with 8-bit encodings. See gh-58,gh-88
358 359 readline_parse_and_bind = List([
359 360 'tab: complete',
360 361 '"\C-l": clear-screen',
361 362 'set show-all-if-ambiguous on',
362 363 '"\C-o": tab-insert',
363 364 '"\C-r": reverse-search-history',
364 365 '"\C-s": forward-search-history',
365 366 '"\C-p": history-search-backward',
366 367 '"\C-n": history-search-forward',
367 368 '"\e[A": history-search-backward',
368 369 '"\e[B": history-search-forward',
369 370 '"\C-k": kill-line',
370 371 '"\C-u": unix-line-discard',
371 372 ], allow_none=False, config=True)
372 373
373 374 ast_node_interactivity = Enum(['all', 'last', 'last_expr', 'none'],
374 375 default_value='last_expr', config=True,
375 376 help="""
376 377 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
377 378 run interactively (displaying output from expressions).""")
378 379
379 380 # TODO: this part of prompt management should be moved to the frontends.
380 381 # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
381 382 separate_in = SeparateUnicode('\n', config=True)
382 383 separate_out = SeparateUnicode('', config=True)
383 384 separate_out2 = SeparateUnicode('', config=True)
384 385 wildcards_case_sensitive = CBool(True, config=True)
385 386 xmode = CaselessStrEnum(('Context','Plain', 'Verbose'),
386 387 default_value='Context', config=True)
387 388
388 389 # Subcomponents of InteractiveShell
389 390 alias_manager = Instance('IPython.core.alias.AliasManager')
390 391 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
391 392 builtin_trap = Instance('IPython.core.builtin_trap.BuiltinTrap')
392 393 display_trap = Instance('IPython.core.display_trap.DisplayTrap')
393 394 extension_manager = Instance('IPython.core.extensions.ExtensionManager')
394 395 plugin_manager = Instance('IPython.core.plugin.PluginManager')
395 396 payload_manager = Instance('IPython.core.payload.PayloadManager')
396 397 history_manager = Instance('IPython.core.history.HistoryManager')
397 398 magics_manager = Instance('IPython.core.magic.MagicsManager')
398 399
399 400 profile_dir = Instance('IPython.core.application.ProfileDir')
400 401 @property
401 402 def profile(self):
402 403 if self.profile_dir is not None:
403 404 name = os.path.basename(self.profile_dir.location)
404 405 return name.replace('profile_','')
405 406
406 407
407 408 # Private interface
408 409 _post_execute = Instance(dict)
409 410
410 411 # Tracks any GUI loop loaded for pylab
411 412 pylab_gui_select = None
412 413
413 414 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
414 415 user_module=None, user_ns=None,
415 416 custom_exceptions=((), None)):
416 417
417 418 # This is where traits with a config_key argument are updated
418 419 # from the values on config.
419 420 super(InteractiveShell, self).__init__(config=config)
420 421 self.configurables = [self]
421 422
422 423 # These are relatively independent and stateless
423 424 self.init_ipython_dir(ipython_dir)
424 425 self.init_profile_dir(profile_dir)
425 426 self.init_instance_attrs()
426 427 self.init_environment()
427 428
428 429 # Check if we're in a virtualenv, and set up sys.path.
429 430 self.init_virtualenv()
430 431
431 432 # Create namespaces (user_ns, user_global_ns, etc.)
432 433 self.init_create_namespaces(user_module, user_ns)
433 434 # This has to be done after init_create_namespaces because it uses
434 435 # something in self.user_ns, but before init_sys_modules, which
435 436 # is the first thing to modify sys.
436 437 # TODO: When we override sys.stdout and sys.stderr before this class
437 438 # is created, we are saving the overridden ones here. Not sure if this
438 439 # is what we want to do.
439 440 self.save_sys_module_state()
440 441 self.init_sys_modules()
441 442
442 443 # While we're trying to have each part of the code directly access what
443 444 # it needs without keeping redundant references to objects, we have too
444 445 # much legacy code that expects ip.db to exist.
445 446 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
446 447
447 448 self.init_history()
448 449 self.init_encoding()
449 450 self.init_prefilter()
450 451
451 452 self.init_syntax_highlighting()
452 453 self.init_hooks()
453 454 self.init_pushd_popd_magic()
454 455 # self.init_traceback_handlers use to be here, but we moved it below
455 456 # because it and init_io have to come after init_readline.
456 457 self.init_user_ns()
457 458 self.init_logger()
458 459 self.init_alias()
459 460 self.init_builtins()
460 461
461 462 # The following was in post_config_initialization
462 463 self.init_inspector()
463 464 # init_readline() must come before init_io(), because init_io uses
464 465 # readline related things.
465 466 self.init_readline()
466 467 # We save this here in case user code replaces raw_input, but it needs
467 468 # to be after init_readline(), because PyPy's readline works by replacing
468 469 # raw_input.
469 470 if py3compat.PY3:
470 471 self.raw_input_original = input
471 472 else:
472 473 self.raw_input_original = raw_input
473 474 # init_completer must come after init_readline, because it needs to
474 475 # know whether readline is present or not system-wide to configure the
475 476 # completers, since the completion machinery can now operate
476 477 # independently of readline (e.g. over the network)
477 478 self.init_completer()
478 479 # TODO: init_io() needs to happen before init_traceback handlers
479 480 # because the traceback handlers hardcode the stdout/stderr streams.
480 481 # This logic in in debugger.Pdb and should eventually be changed.
481 482 self.init_io()
482 483 self.init_traceback_handlers(custom_exceptions)
483 484 self.init_prompts()
484 485 self.init_display_formatter()
485 486 self.init_display_pub()
487 self.init_data_pub()
486 488 self.init_displayhook()
487 489 self.init_reload_doctest()
488 490 self.init_latextool()
489 491 self.init_magics()
490 492 self.init_logstart()
491 493 self.init_pdb()
492 494 self.init_extension_manager()
493 495 self.init_plugin_manager()
494 496 self.init_payload()
495 497 self.hooks.late_startup_hook()
496 498 atexit.register(self.atexit_operations)
497 499
498 500 def get_ipython(self):
499 501 """Return the currently running IPython instance."""
500 502 return self
501 503
502 504 #-------------------------------------------------------------------------
503 505 # Trait changed handlers
504 506 #-------------------------------------------------------------------------
505 507
506 508 def _ipython_dir_changed(self, name, new):
507 509 if not os.path.isdir(new):
508 510 os.makedirs(new, mode = 0777)
509 511
510 512 def set_autoindent(self,value=None):
511 513 """Set the autoindent flag, checking for readline support.
512 514
513 515 If called with no arguments, it acts as a toggle."""
514 516
515 517 if value != 0 and not self.has_readline:
516 518 if os.name == 'posix':
517 519 warn("The auto-indent feature requires the readline library")
518 520 self.autoindent = 0
519 521 return
520 522 if value is None:
521 523 self.autoindent = not self.autoindent
522 524 else:
523 525 self.autoindent = value
524 526
525 527 #-------------------------------------------------------------------------
526 528 # init_* methods called by __init__
527 529 #-------------------------------------------------------------------------
528 530
529 531 def init_ipython_dir(self, ipython_dir):
530 532 if ipython_dir is not None:
531 533 self.ipython_dir = ipython_dir
532 534 return
533 535
534 536 self.ipython_dir = get_ipython_dir()
535 537
536 538 def init_profile_dir(self, profile_dir):
537 539 if profile_dir is not None:
538 540 self.profile_dir = profile_dir
539 541 return
540 542 self.profile_dir =\
541 543 ProfileDir.create_profile_dir_by_name(self.ipython_dir, 'default')
542 544
543 545 def init_instance_attrs(self):
544 546 self.more = False
545 547
546 548 # command compiler
547 549 self.compile = CachingCompiler()
548 550
549 551 # Make an empty namespace, which extension writers can rely on both
550 552 # existing and NEVER being used by ipython itself. This gives them a
551 553 # convenient location for storing additional information and state
552 554 # their extensions may require, without fear of collisions with other
553 555 # ipython names that may develop later.
554 556 self.meta = Struct()
555 557
556 558 # Temporary files used for various purposes. Deleted at exit.
557 559 self.tempfiles = []
558 560
559 561 # Keep track of readline usage (later set by init_readline)
560 562 self.has_readline = False
561 563
562 564 # keep track of where we started running (mainly for crash post-mortem)
563 565 # This is not being used anywhere currently.
564 566 self.starting_dir = os.getcwdu()
565 567
566 568 # Indentation management
567 569 self.indent_current_nsp = 0
568 570
569 571 # Dict to track post-execution functions that have been registered
570 572 self._post_execute = {}
571 573
572 574 def init_environment(self):
573 575 """Any changes we need to make to the user's environment."""
574 576 pass
575 577
576 578 def init_encoding(self):
577 579 # Get system encoding at startup time. Certain terminals (like Emacs
578 580 # under Win32 have it set to None, and we need to have a known valid
579 581 # encoding to use in the raw_input() method
580 582 try:
581 583 self.stdin_encoding = sys.stdin.encoding or 'ascii'
582 584 except AttributeError:
583 585 self.stdin_encoding = 'ascii'
584 586
585 587 def init_syntax_highlighting(self):
586 588 # Python source parser/formatter for syntax highlighting
587 589 pyformat = PyColorize.Parser().format
588 590 self.pycolorize = lambda src: pyformat(src,'str',self.colors)
589 591
590 592 def init_pushd_popd_magic(self):
591 593 # for pushd/popd management
592 594 self.home_dir = get_home_dir()
593 595
594 596 self.dir_stack = []
595 597
596 598 def init_logger(self):
597 599 self.logger = Logger(self.home_dir, logfname='ipython_log.py',
598 600 logmode='rotate')
599 601
600 602 def init_logstart(self):
601 603 """Initialize logging in case it was requested at the command line.
602 604 """
603 605 if self.logappend:
604 606 self.magic('logstart %s append' % self.logappend)
605 607 elif self.logfile:
606 608 self.magic('logstart %s' % self.logfile)
607 609 elif self.logstart:
608 610 self.magic('logstart')
609 611
610 612 def init_builtins(self):
611 613 # A single, static flag that we set to True. Its presence indicates
612 614 # that an IPython shell has been created, and we make no attempts at
613 615 # removing on exit or representing the existence of more than one
614 616 # IPython at a time.
615 617 builtin_mod.__dict__['__IPYTHON__'] = True
616 618
617 619 # In 0.11 we introduced '__IPYTHON__active' as an integer we'd try to
618 620 # manage on enter/exit, but with all our shells it's virtually
619 621 # impossible to get all the cases right. We're leaving the name in for
620 622 # those who adapted their codes to check for this flag, but will
621 623 # eventually remove it after a few more releases.
622 624 builtin_mod.__dict__['__IPYTHON__active'] = \
623 625 'Deprecated, check for __IPYTHON__'
624 626
625 627 self.builtin_trap = BuiltinTrap(shell=self)
626 628
627 629 def init_inspector(self):
628 630 # Object inspector
629 631 self.inspector = oinspect.Inspector(oinspect.InspectColors,
630 632 PyColorize.ANSICodeColors,
631 633 'NoColor',
632 634 self.object_info_string_level)
633 635
634 636 def init_io(self):
635 637 # This will just use sys.stdout and sys.stderr. If you want to
636 638 # override sys.stdout and sys.stderr themselves, you need to do that
637 639 # *before* instantiating this class, because io holds onto
638 640 # references to the underlying streams.
639 641 if sys.platform == 'win32' and self.has_readline:
640 642 io.stdout = io.stderr = io.IOStream(self.readline._outputfile)
641 643 else:
642 644 io.stdout = io.IOStream(sys.stdout)
643 645 io.stderr = io.IOStream(sys.stderr)
644 646
645 647 def init_prompts(self):
646 648 self.prompt_manager = PromptManager(shell=self, config=self.config)
647 649 self.configurables.append(self.prompt_manager)
648 650 # Set system prompts, so that scripts can decide if they are running
649 651 # interactively.
650 652 sys.ps1 = 'In : '
651 653 sys.ps2 = '...: '
652 654 sys.ps3 = 'Out: '
653 655
654 656 def init_display_formatter(self):
655 657 self.display_formatter = DisplayFormatter(config=self.config)
656 658 self.configurables.append(self.display_formatter)
657 659
658 660 def init_display_pub(self):
659 661 self.display_pub = self.display_pub_class(config=self.config)
660 662 self.configurables.append(self.display_pub)
661 663
664 def init_data_pub(self):
665 if not self.data_pub_class:
666 self.data_pub = None
667 return
668 self.data_pub = self.data_pub_class(config=self.config)
669 self.configurables.append(self.data_pub)
670
662 671 def init_displayhook(self):
663 672 # Initialize displayhook, set in/out prompts and printing system
664 673 self.displayhook = self.displayhook_class(
665 674 config=self.config,
666 675 shell=self,
667 676 cache_size=self.cache_size,
668 677 )
669 678 self.configurables.append(self.displayhook)
670 679 # This is a context manager that installs/revmoes the displayhook at
671 680 # the appropriate time.
672 681 self.display_trap = DisplayTrap(hook=self.displayhook)
673 682
674 683 def init_reload_doctest(self):
675 684 # Do a proper resetting of doctest, including the necessary displayhook
676 685 # monkeypatching
677 686 try:
678 687 doctest_reload()
679 688 except ImportError:
680 689 warn("doctest module does not exist.")
681 690
682 691 def init_latextool(self):
683 692 """Configure LaTeXTool."""
684 693 cfg = LaTeXTool.instance(config=self.config)
685 694 if cfg not in self.configurables:
686 695 self.configurables.append(cfg)
687 696
688 697 def init_virtualenv(self):
689 698 """Add a virtualenv to sys.path so the user can import modules from it.
690 699 This isn't perfect: it doesn't use the Python interpreter with which the
691 700 virtualenv was built, and it ignores the --no-site-packages option. A
692 701 warning will appear suggesting the user installs IPython in the
693 702 virtualenv, but for many cases, it probably works well enough.
694 703
695 704 Adapted from code snippets online.
696 705
697 706 http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv
698 707 """
699 708 if 'VIRTUAL_ENV' not in os.environ:
700 709 # Not in a virtualenv
701 710 return
702 711
703 712 if sys.executable.startswith(os.environ['VIRTUAL_ENV']):
704 713 # Running properly in the virtualenv, don't need to do anything
705 714 return
706 715
707 716 warn("Attempting to work in a virtualenv. If you encounter problems, please "
708 717 "install IPython inside the virtualenv.\n")
709 718 if sys.platform == "win32":
710 719 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'Lib', 'site-packages')
711 720 else:
712 721 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'lib',
713 722 'python%d.%d' % sys.version_info[:2], 'site-packages')
714 723
715 724 import site
716 725 sys.path.insert(0, virtual_env)
717 726 site.addsitedir(virtual_env)
718 727
719 728 #-------------------------------------------------------------------------
720 729 # Things related to injections into the sys module
721 730 #-------------------------------------------------------------------------
722 731
723 732 def save_sys_module_state(self):
724 733 """Save the state of hooks in the sys module.
725 734
726 735 This has to be called after self.user_module is created.
727 736 """
728 737 self._orig_sys_module_state = {}
729 738 self._orig_sys_module_state['stdin'] = sys.stdin
730 739 self._orig_sys_module_state['stdout'] = sys.stdout
731 740 self._orig_sys_module_state['stderr'] = sys.stderr
732 741 self._orig_sys_module_state['excepthook'] = sys.excepthook
733 742 self._orig_sys_modules_main_name = self.user_module.__name__
734 743 self._orig_sys_modules_main_mod = sys.modules.get(self.user_module.__name__)
735 744
736 745 def restore_sys_module_state(self):
737 746 """Restore the state of the sys module."""
738 747 try:
739 748 for k, v in self._orig_sys_module_state.iteritems():
740 749 setattr(sys, k, v)
741 750 except AttributeError:
742 751 pass
743 752 # Reset what what done in self.init_sys_modules
744 753 if self._orig_sys_modules_main_mod is not None:
745 754 sys.modules[self._orig_sys_modules_main_name] = self._orig_sys_modules_main_mod
746 755
747 756 #-------------------------------------------------------------------------
748 757 # Things related to hooks
749 758 #-------------------------------------------------------------------------
750 759
751 760 def init_hooks(self):
752 761 # hooks holds pointers used for user-side customizations
753 762 self.hooks = Struct()
754 763
755 764 self.strdispatchers = {}
756 765
757 766 # Set all default hooks, defined in the IPython.hooks module.
758 767 hooks = IPython.core.hooks
759 768 for hook_name in hooks.__all__:
760 769 # default hooks have priority 100, i.e. low; user hooks should have
761 770 # 0-100 priority
762 771 self.set_hook(hook_name,getattr(hooks,hook_name), 100)
763 772
764 773 def set_hook(self,name,hook, priority = 50, str_key = None, re_key = None):
765 774 """set_hook(name,hook) -> sets an internal IPython hook.
766 775
767 776 IPython exposes some of its internal API as user-modifiable hooks. By
768 777 adding your function to one of these hooks, you can modify IPython's
769 778 behavior to call at runtime your own routines."""
770 779
771 780 # At some point in the future, this should validate the hook before it
772 781 # accepts it. Probably at least check that the hook takes the number
773 782 # of args it's supposed to.
774 783
775 784 f = types.MethodType(hook,self)
776 785
777 786 # check if the hook is for strdispatcher first
778 787 if str_key is not None:
779 788 sdp = self.strdispatchers.get(name, StrDispatch())
780 789 sdp.add_s(str_key, f, priority )
781 790 self.strdispatchers[name] = sdp
782 791 return
783 792 if re_key is not None:
784 793 sdp = self.strdispatchers.get(name, StrDispatch())
785 794 sdp.add_re(re.compile(re_key), f, priority )
786 795 self.strdispatchers[name] = sdp
787 796 return
788 797
789 798 dp = getattr(self.hooks, name, None)
790 799 if name not in IPython.core.hooks.__all__:
791 800 print("Warning! Hook '%s' is not one of %s" % \
792 801 (name, IPython.core.hooks.__all__ ))
793 802 if not dp:
794 803 dp = IPython.core.hooks.CommandChainDispatcher()
795 804
796 805 try:
797 806 dp.add(f,priority)
798 807 except AttributeError:
799 808 # it was not commandchain, plain old func - replace
800 809 dp = f
801 810
802 811 setattr(self.hooks,name, dp)
803 812
804 813 def register_post_execute(self, func):
805 814 """Register a function for calling after code execution.
806 815 """
807 816 if not callable(func):
808 817 raise ValueError('argument %s must be callable' % func)
809 818 self._post_execute[func] = True
810 819
811 820 #-------------------------------------------------------------------------
812 821 # Things related to the "main" module
813 822 #-------------------------------------------------------------------------
814 823
815 824 def new_main_mod(self,ns=None):
816 825 """Return a new 'main' module object for user code execution.
817 826 """
818 827 main_mod = self._user_main_module
819 828 init_fakemod_dict(main_mod,ns)
820 829 return main_mod
821 830
822 831 def cache_main_mod(self,ns,fname):
823 832 """Cache a main module's namespace.
824 833
825 834 When scripts are executed via %run, we must keep a reference to the
826 835 namespace of their __main__ module (a FakeModule instance) around so
827 836 that Python doesn't clear it, rendering objects defined therein
828 837 useless.
829 838
830 839 This method keeps said reference in a private dict, keyed by the
831 840 absolute path of the module object (which corresponds to the script
832 841 path). This way, for multiple executions of the same script we only
833 842 keep one copy of the namespace (the last one), thus preventing memory
834 843 leaks from old references while allowing the objects from the last
835 844 execution to be accessible.
836 845
837 846 Note: we can not allow the actual FakeModule instances to be deleted,
838 847 because of how Python tears down modules (it hard-sets all their
839 848 references to None without regard for reference counts). This method
840 849 must therefore make a *copy* of the given namespace, to allow the
841 850 original module's __dict__ to be cleared and reused.
842 851
843 852
844 853 Parameters
845 854 ----------
846 855 ns : a namespace (a dict, typically)
847 856
848 857 fname : str
849 858 Filename associated with the namespace.
850 859
851 860 Examples
852 861 --------
853 862
854 863 In [10]: import IPython
855 864
856 865 In [11]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__)
857 866
858 867 In [12]: IPython.__file__ in _ip._main_ns_cache
859 868 Out[12]: True
860 869 """
861 870 self._main_ns_cache[os.path.abspath(fname)] = ns.copy()
862 871
863 872 def clear_main_mod_cache(self):
864 873 """Clear the cache of main modules.
865 874
866 875 Mainly for use by utilities like %reset.
867 876
868 877 Examples
869 878 --------
870 879
871 880 In [15]: import IPython
872 881
873 882 In [16]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__)
874 883
875 884 In [17]: len(_ip._main_ns_cache) > 0
876 885 Out[17]: True
877 886
878 887 In [18]: _ip.clear_main_mod_cache()
879 888
880 889 In [19]: len(_ip._main_ns_cache) == 0
881 890 Out[19]: True
882 891 """
883 892 self._main_ns_cache.clear()
884 893
885 894 #-------------------------------------------------------------------------
886 895 # Things related to debugging
887 896 #-------------------------------------------------------------------------
888 897
889 898 def init_pdb(self):
890 899 # Set calling of pdb on exceptions
891 900 # self.call_pdb is a property
892 901 self.call_pdb = self.pdb
893 902
894 903 def _get_call_pdb(self):
895 904 return self._call_pdb
896 905
897 906 def _set_call_pdb(self,val):
898 907
899 908 if val not in (0,1,False,True):
900 909 raise ValueError('new call_pdb value must be boolean')
901 910
902 911 # store value in instance
903 912 self._call_pdb = val
904 913
905 914 # notify the actual exception handlers
906 915 self.InteractiveTB.call_pdb = val
907 916
908 917 call_pdb = property(_get_call_pdb,_set_call_pdb,None,
909 918 'Control auto-activation of pdb at exceptions')
910 919
911 920 def debugger(self,force=False):
912 921 """Call the pydb/pdb debugger.
913 922
914 923 Keywords:
915 924
916 925 - force(False): by default, this routine checks the instance call_pdb
917 926 flag and does not actually invoke the debugger if the flag is false.
918 927 The 'force' option forces the debugger to activate even if the flag
919 928 is false.
920 929 """
921 930
922 931 if not (force or self.call_pdb):
923 932 return
924 933
925 934 if not hasattr(sys,'last_traceback'):
926 935 error('No traceback has been produced, nothing to debug.')
927 936 return
928 937
929 938 # use pydb if available
930 939 if debugger.has_pydb:
931 940 from pydb import pm
932 941 else:
933 942 # fallback to our internal debugger
934 943 pm = lambda : self.InteractiveTB.debugger(force=True)
935 944
936 945 with self.readline_no_record:
937 946 pm()
938 947
939 948 #-------------------------------------------------------------------------
940 949 # Things related to IPython's various namespaces
941 950 #-------------------------------------------------------------------------
942 951 default_user_namespaces = True
943 952
944 953 def init_create_namespaces(self, user_module=None, user_ns=None):
945 954 # Create the namespace where the user will operate. user_ns is
946 955 # normally the only one used, and it is passed to the exec calls as
947 956 # the locals argument. But we do carry a user_global_ns namespace
948 957 # given as the exec 'globals' argument, This is useful in embedding
949 958 # situations where the ipython shell opens in a context where the
950 959 # distinction between locals and globals is meaningful. For
951 960 # non-embedded contexts, it is just the same object as the user_ns dict.
952 961
953 962 # FIXME. For some strange reason, __builtins__ is showing up at user
954 963 # level as a dict instead of a module. This is a manual fix, but I
955 964 # should really track down where the problem is coming from. Alex
956 965 # Schmolck reported this problem first.
957 966
958 967 # A useful post by Alex Martelli on this topic:
959 968 # Re: inconsistent value from __builtins__
960 969 # Von: Alex Martelli <aleaxit@yahoo.com>
961 970 # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends
962 971 # Gruppen: comp.lang.python
963 972
964 973 # Michael Hohn <hohn@hooknose.lbl.gov> wrote:
965 974 # > >>> print type(builtin_check.get_global_binding('__builtins__'))
966 975 # > <type 'dict'>
967 976 # > >>> print type(__builtins__)
968 977 # > <type 'module'>
969 978 # > Is this difference in return value intentional?
970 979
971 980 # Well, it's documented that '__builtins__' can be either a dictionary
972 981 # or a module, and it's been that way for a long time. Whether it's
973 982 # intentional (or sensible), I don't know. In any case, the idea is
974 983 # that if you need to access the built-in namespace directly, you
975 984 # should start with "import __builtin__" (note, no 's') which will
976 985 # definitely give you a module. Yeah, it's somewhat confusing:-(.
977 986
978 987 # These routines return a properly built module and dict as needed by
979 988 # the rest of the code, and can also be used by extension writers to
980 989 # generate properly initialized namespaces.
981 990 if (user_ns is not None) or (user_module is not None):
982 991 self.default_user_namespaces = False
983 992 self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
984 993
985 994 # A record of hidden variables we have added to the user namespace, so
986 995 # we can list later only variables defined in actual interactive use.
987 996 self.user_ns_hidden = set()
988 997
989 998 # Now that FakeModule produces a real module, we've run into a nasty
990 999 # problem: after script execution (via %run), the module where the user
991 1000 # code ran is deleted. Now that this object is a true module (needed
992 1001 # so docetst and other tools work correctly), the Python module
993 1002 # teardown mechanism runs over it, and sets to None every variable
994 1003 # present in that module. Top-level references to objects from the
995 1004 # script survive, because the user_ns is updated with them. However,
996 1005 # calling functions defined in the script that use other things from
997 1006 # the script will fail, because the function's closure had references
998 1007 # to the original objects, which are now all None. So we must protect
999 1008 # these modules from deletion by keeping a cache.
1000 1009 #
1001 1010 # To avoid keeping stale modules around (we only need the one from the
1002 1011 # last run), we use a dict keyed with the full path to the script, so
1003 1012 # only the last version of the module is held in the cache. Note,
1004 1013 # however, that we must cache the module *namespace contents* (their
1005 1014 # __dict__). Because if we try to cache the actual modules, old ones
1006 1015 # (uncached) could be destroyed while still holding references (such as
1007 1016 # those held by GUI objects that tend to be long-lived)>
1008 1017 #
1009 1018 # The %reset command will flush this cache. See the cache_main_mod()
1010 1019 # and clear_main_mod_cache() methods for details on use.
1011 1020
1012 1021 # This is the cache used for 'main' namespaces
1013 1022 self._main_ns_cache = {}
1014 1023 # And this is the single instance of FakeModule whose __dict__ we keep
1015 1024 # copying and clearing for reuse on each %run
1016 1025 self._user_main_module = FakeModule()
1017 1026
1018 1027 # A table holding all the namespaces IPython deals with, so that
1019 1028 # introspection facilities can search easily.
1020 1029 self.ns_table = {'user_global':self.user_module.__dict__,
1021 1030 'user_local':self.user_ns,
1022 1031 'builtin':builtin_mod.__dict__
1023 1032 }
1024 1033
1025 1034 @property
1026 1035 def user_global_ns(self):
1027 1036 return self.user_module.__dict__
1028 1037
1029 1038 def prepare_user_module(self, user_module=None, user_ns=None):
1030 1039 """Prepare the module and namespace in which user code will be run.
1031 1040
1032 1041 When IPython is started normally, both parameters are None: a new module
1033 1042 is created automatically, and its __dict__ used as the namespace.
1034 1043
1035 1044 If only user_module is provided, its __dict__ is used as the namespace.
1036 1045 If only user_ns is provided, a dummy module is created, and user_ns
1037 1046 becomes the global namespace. If both are provided (as they may be
1038 1047 when embedding), user_ns is the local namespace, and user_module
1039 1048 provides the global namespace.
1040 1049
1041 1050 Parameters
1042 1051 ----------
1043 1052 user_module : module, optional
1044 1053 The current user module in which IPython is being run. If None,
1045 1054 a clean module will be created.
1046 1055 user_ns : dict, optional
1047 1056 A namespace in which to run interactive commands.
1048 1057
1049 1058 Returns
1050 1059 -------
1051 1060 A tuple of user_module and user_ns, each properly initialised.
1052 1061 """
1053 1062 if user_module is None and user_ns is not None:
1054 1063 user_ns.setdefault("__name__", "__main__")
1055 1064 class DummyMod(object):
1056 1065 "A dummy module used for IPython's interactive namespace."
1057 1066 pass
1058 1067 user_module = DummyMod()
1059 1068 user_module.__dict__ = user_ns
1060 1069
1061 1070 if user_module is None:
1062 1071 user_module = types.ModuleType("__main__",
1063 1072 doc="Automatically created module for IPython interactive environment")
1064 1073
1065 1074 # We must ensure that __builtin__ (without the final 's') is always
1066 1075 # available and pointing to the __builtin__ *module*. For more details:
1067 1076 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1068 1077 user_module.__dict__.setdefault('__builtin__', builtin_mod)
1069 1078 user_module.__dict__.setdefault('__builtins__', builtin_mod)
1070 1079
1071 1080 if user_ns is None:
1072 1081 user_ns = user_module.__dict__
1073 1082
1074 1083 return user_module, user_ns
1075 1084
1076 1085 def init_sys_modules(self):
1077 1086 # We need to insert into sys.modules something that looks like a
1078 1087 # module but which accesses the IPython namespace, for shelve and
1079 1088 # pickle to work interactively. Normally they rely on getting
1080 1089 # everything out of __main__, but for embedding purposes each IPython
1081 1090 # instance has its own private namespace, so we can't go shoving
1082 1091 # everything into __main__.
1083 1092
1084 1093 # note, however, that we should only do this for non-embedded
1085 1094 # ipythons, which really mimic the __main__.__dict__ with their own
1086 1095 # namespace. Embedded instances, on the other hand, should not do
1087 1096 # this because they need to manage the user local/global namespaces
1088 1097 # only, but they live within a 'normal' __main__ (meaning, they
1089 1098 # shouldn't overtake the execution environment of the script they're
1090 1099 # embedded in).
1091 1100
1092 1101 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
1093 1102 main_name = self.user_module.__name__
1094 1103 sys.modules[main_name] = self.user_module
1095 1104
1096 1105 def init_user_ns(self):
1097 1106 """Initialize all user-visible namespaces to their minimum defaults.
1098 1107
1099 1108 Certain history lists are also initialized here, as they effectively
1100 1109 act as user namespaces.
1101 1110
1102 1111 Notes
1103 1112 -----
1104 1113 All data structures here are only filled in, they are NOT reset by this
1105 1114 method. If they were not empty before, data will simply be added to
1106 1115 therm.
1107 1116 """
1108 1117 # This function works in two parts: first we put a few things in
1109 1118 # user_ns, and we sync that contents into user_ns_hidden so that these
1110 1119 # initial variables aren't shown by %who. After the sync, we add the
1111 1120 # rest of what we *do* want the user to see with %who even on a new
1112 1121 # session (probably nothing, so theye really only see their own stuff)
1113 1122
1114 1123 # The user dict must *always* have a __builtin__ reference to the
1115 1124 # Python standard __builtin__ namespace, which must be imported.
1116 1125 # This is so that certain operations in prompt evaluation can be
1117 1126 # reliably executed with builtins. Note that we can NOT use
1118 1127 # __builtins__ (note the 's'), because that can either be a dict or a
1119 1128 # module, and can even mutate at runtime, depending on the context
1120 1129 # (Python makes no guarantees on it). In contrast, __builtin__ is
1121 1130 # always a module object, though it must be explicitly imported.
1122 1131
1123 1132 # For more details:
1124 1133 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1125 1134 ns = dict()
1126 1135
1127 1136 # Put 'help' in the user namespace
1128 1137 try:
1129 1138 from site import _Helper
1130 1139 ns['help'] = _Helper()
1131 1140 except ImportError:
1132 1141 warn('help() not available - check site.py')
1133 1142
1134 1143 # make global variables for user access to the histories
1135 1144 ns['_ih'] = self.history_manager.input_hist_parsed
1136 1145 ns['_oh'] = self.history_manager.output_hist
1137 1146 ns['_dh'] = self.history_manager.dir_hist
1138 1147
1139 1148 ns['_sh'] = shadowns
1140 1149
1141 1150 # user aliases to input and output histories. These shouldn't show up
1142 1151 # in %who, as they can have very large reprs.
1143 1152 ns['In'] = self.history_manager.input_hist_parsed
1144 1153 ns['Out'] = self.history_manager.output_hist
1145 1154
1146 1155 # Store myself as the public api!!!
1147 1156 ns['get_ipython'] = self.get_ipython
1148 1157
1149 1158 ns['exit'] = self.exiter
1150 1159 ns['quit'] = self.exiter
1151 1160
1152 1161 # Sync what we've added so far to user_ns_hidden so these aren't seen
1153 1162 # by %who
1154 1163 self.user_ns_hidden.update(ns)
1155 1164
1156 1165 # Anything put into ns now would show up in %who. Think twice before
1157 1166 # putting anything here, as we really want %who to show the user their
1158 1167 # stuff, not our variables.
1159 1168
1160 1169 # Finally, update the real user's namespace
1161 1170 self.user_ns.update(ns)
1162 1171
1163 1172 @property
1164 1173 def all_ns_refs(self):
1165 1174 """Get a list of references to all the namespace dictionaries in which
1166 1175 IPython might store a user-created object.
1167 1176
1168 1177 Note that this does not include the displayhook, which also caches
1169 1178 objects from the output."""
1170 1179 return [self.user_ns, self.user_global_ns,
1171 1180 self._user_main_module.__dict__] + self._main_ns_cache.values()
1172 1181
1173 1182 def reset(self, new_session=True):
1174 1183 """Clear all internal namespaces, and attempt to release references to
1175 1184 user objects.
1176 1185
1177 1186 If new_session is True, a new history session will be opened.
1178 1187 """
1179 1188 # Clear histories
1180 1189 self.history_manager.reset(new_session)
1181 1190 # Reset counter used to index all histories
1182 1191 if new_session:
1183 1192 self.execution_count = 1
1184 1193
1185 1194 # Flush cached output items
1186 1195 if self.displayhook.do_full_cache:
1187 1196 self.displayhook.flush()
1188 1197
1189 1198 # The main execution namespaces must be cleared very carefully,
1190 1199 # skipping the deletion of the builtin-related keys, because doing so
1191 1200 # would cause errors in many object's __del__ methods.
1192 1201 if self.user_ns is not self.user_global_ns:
1193 1202 self.user_ns.clear()
1194 1203 ns = self.user_global_ns
1195 1204 drop_keys = set(ns.keys())
1196 1205 drop_keys.discard('__builtin__')
1197 1206 drop_keys.discard('__builtins__')
1198 1207 drop_keys.discard('__name__')
1199 1208 for k in drop_keys:
1200 1209 del ns[k]
1201 1210
1202 1211 self.user_ns_hidden.clear()
1203 1212
1204 1213 # Restore the user namespaces to minimal usability
1205 1214 self.init_user_ns()
1206 1215
1207 1216 # Restore the default and user aliases
1208 1217 self.alias_manager.clear_aliases()
1209 1218 self.alias_manager.init_aliases()
1210 1219
1211 1220 # Flush the private list of module references kept for script
1212 1221 # execution protection
1213 1222 self.clear_main_mod_cache()
1214 1223
1215 1224 # Clear out the namespace from the last %run
1216 1225 self.new_main_mod()
1217 1226
1218 1227 def del_var(self, varname, by_name=False):
1219 1228 """Delete a variable from the various namespaces, so that, as
1220 1229 far as possible, we're not keeping any hidden references to it.
1221 1230
1222 1231 Parameters
1223 1232 ----------
1224 1233 varname : str
1225 1234 The name of the variable to delete.
1226 1235 by_name : bool
1227 1236 If True, delete variables with the given name in each
1228 1237 namespace. If False (default), find the variable in the user
1229 1238 namespace, and delete references to it.
1230 1239 """
1231 1240 if varname in ('__builtin__', '__builtins__'):
1232 1241 raise ValueError("Refusing to delete %s" % varname)
1233 1242
1234 1243 ns_refs = self.all_ns_refs
1235 1244
1236 1245 if by_name: # Delete by name
1237 1246 for ns in ns_refs:
1238 1247 try:
1239 1248 del ns[varname]
1240 1249 except KeyError:
1241 1250 pass
1242 1251 else: # Delete by object
1243 1252 try:
1244 1253 obj = self.user_ns[varname]
1245 1254 except KeyError:
1246 1255 raise NameError("name '%s' is not defined" % varname)
1247 1256 # Also check in output history
1248 1257 ns_refs.append(self.history_manager.output_hist)
1249 1258 for ns in ns_refs:
1250 1259 to_delete = [n for n, o in ns.iteritems() if o is obj]
1251 1260 for name in to_delete:
1252 1261 del ns[name]
1253 1262
1254 1263 # displayhook keeps extra references, but not in a dictionary
1255 1264 for name in ('_', '__', '___'):
1256 1265 if getattr(self.displayhook, name) is obj:
1257 1266 setattr(self.displayhook, name, None)
1258 1267
1259 1268 def reset_selective(self, regex=None):
1260 1269 """Clear selective variables from internal namespaces based on a
1261 1270 specified regular expression.
1262 1271
1263 1272 Parameters
1264 1273 ----------
1265 1274 regex : string or compiled pattern, optional
1266 1275 A regular expression pattern that will be used in searching
1267 1276 variable names in the users namespaces.
1268 1277 """
1269 1278 if regex is not None:
1270 1279 try:
1271 1280 m = re.compile(regex)
1272 1281 except TypeError:
1273 1282 raise TypeError('regex must be a string or compiled pattern')
1274 1283 # Search for keys in each namespace that match the given regex
1275 1284 # If a match is found, delete the key/value pair.
1276 1285 for ns in self.all_ns_refs:
1277 1286 for var in ns:
1278 1287 if m.search(var):
1279 1288 del ns[var]
1280 1289
1281 1290 def push(self, variables, interactive=True):
1282 1291 """Inject a group of variables into the IPython user namespace.
1283 1292
1284 1293 Parameters
1285 1294 ----------
1286 1295 variables : dict, str or list/tuple of str
1287 1296 The variables to inject into the user's namespace. If a dict, a
1288 1297 simple update is done. If a str, the string is assumed to have
1289 1298 variable names separated by spaces. A list/tuple of str can also
1290 1299 be used to give the variable names. If just the variable names are
1291 1300 give (list/tuple/str) then the variable values looked up in the
1292 1301 callers frame.
1293 1302 interactive : bool
1294 1303 If True (default), the variables will be listed with the ``who``
1295 1304 magic.
1296 1305 """
1297 1306 vdict = None
1298 1307
1299 1308 # We need a dict of name/value pairs to do namespace updates.
1300 1309 if isinstance(variables, dict):
1301 1310 vdict = variables
1302 1311 elif isinstance(variables, (basestring, list, tuple)):
1303 1312 if isinstance(variables, basestring):
1304 1313 vlist = variables.split()
1305 1314 else:
1306 1315 vlist = variables
1307 1316 vdict = {}
1308 1317 cf = sys._getframe(1)
1309 1318 for name in vlist:
1310 1319 try:
1311 1320 vdict[name] = eval(name, cf.f_globals, cf.f_locals)
1312 1321 except:
1313 1322 print('Could not get variable %s from %s' %
1314 1323 (name,cf.f_code.co_name))
1315 1324 else:
1316 1325 raise ValueError('variables must be a dict/str/list/tuple')
1317 1326
1318 1327 # Propagate variables to user namespace
1319 1328 self.user_ns.update(vdict)
1320 1329
1321 1330 # And configure interactive visibility
1322 1331 user_ns_hidden = self.user_ns_hidden
1323 1332 if interactive:
1324 1333 user_ns_hidden.difference_update(vdict)
1325 1334 else:
1326 1335 user_ns_hidden.update(vdict)
1327 1336
1328 1337 def drop_by_id(self, variables):
1329 1338 """Remove a dict of variables from the user namespace, if they are the
1330 1339 same as the values in the dictionary.
1331 1340
1332 1341 This is intended for use by extensions: variables that they've added can
1333 1342 be taken back out if they are unloaded, without removing any that the
1334 1343 user has overwritten.
1335 1344
1336 1345 Parameters
1337 1346 ----------
1338 1347 variables : dict
1339 1348 A dictionary mapping object names (as strings) to the objects.
1340 1349 """
1341 1350 for name, obj in variables.iteritems():
1342 1351 if name in self.user_ns and self.user_ns[name] is obj:
1343 1352 del self.user_ns[name]
1344 1353 self.user_ns_hidden.discard(name)
1345 1354
1346 1355 #-------------------------------------------------------------------------
1347 1356 # Things related to object introspection
1348 1357 #-------------------------------------------------------------------------
1349 1358
1350 1359 def _ofind(self, oname, namespaces=None):
1351 1360 """Find an object in the available namespaces.
1352 1361
1353 1362 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1354 1363
1355 1364 Has special code to detect magic functions.
1356 1365 """
1357 1366 oname = oname.strip()
1358 1367 #print '1- oname: <%r>' % oname # dbg
1359 1368 if not oname.startswith(ESC_MAGIC) and \
1360 1369 not oname.startswith(ESC_MAGIC2) and \
1361 1370 not py3compat.isidentifier(oname, dotted=True):
1362 1371 return dict(found=False)
1363 1372
1364 1373 alias_ns = None
1365 1374 if namespaces is None:
1366 1375 # Namespaces to search in:
1367 1376 # Put them in a list. The order is important so that we
1368 1377 # find things in the same order that Python finds them.
1369 1378 namespaces = [ ('Interactive', self.user_ns),
1370 1379 ('Interactive (global)', self.user_global_ns),
1371 1380 ('Python builtin', builtin_mod.__dict__),
1372 1381 ('Alias', self.alias_manager.alias_table),
1373 1382 ]
1374 1383 alias_ns = self.alias_manager.alias_table
1375 1384
1376 1385 # initialize results to 'null'
1377 1386 found = False; obj = None; ospace = None; ds = None;
1378 1387 ismagic = False; isalias = False; parent = None
1379 1388
1380 1389 # We need to special-case 'print', which as of python2.6 registers as a
1381 1390 # function but should only be treated as one if print_function was
1382 1391 # loaded with a future import. In this case, just bail.
1383 1392 if (oname == 'print' and not py3compat.PY3 and not \
1384 1393 (self.compile.compiler_flags & __future__.CO_FUTURE_PRINT_FUNCTION)):
1385 1394 return {'found':found, 'obj':obj, 'namespace':ospace,
1386 1395 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1387 1396
1388 1397 # Look for the given name by splitting it in parts. If the head is
1389 1398 # found, then we look for all the remaining parts as members, and only
1390 1399 # declare success if we can find them all.
1391 1400 oname_parts = oname.split('.')
1392 1401 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1393 1402 for nsname,ns in namespaces:
1394 1403 try:
1395 1404 obj = ns[oname_head]
1396 1405 except KeyError:
1397 1406 continue
1398 1407 else:
1399 1408 #print 'oname_rest:', oname_rest # dbg
1400 1409 for part in oname_rest:
1401 1410 try:
1402 1411 parent = obj
1403 1412 obj = getattr(obj,part)
1404 1413 except:
1405 1414 # Blanket except b/c some badly implemented objects
1406 1415 # allow __getattr__ to raise exceptions other than
1407 1416 # AttributeError, which then crashes IPython.
1408 1417 break
1409 1418 else:
1410 1419 # If we finish the for loop (no break), we got all members
1411 1420 found = True
1412 1421 ospace = nsname
1413 1422 if ns == alias_ns:
1414 1423 isalias = True
1415 1424 break # namespace loop
1416 1425
1417 1426 # Try to see if it's magic
1418 1427 if not found:
1419 1428 obj = None
1420 1429 if oname.startswith(ESC_MAGIC2):
1421 1430 oname = oname.lstrip(ESC_MAGIC2)
1422 1431 obj = self.find_cell_magic(oname)
1423 1432 elif oname.startswith(ESC_MAGIC):
1424 1433 oname = oname.lstrip(ESC_MAGIC)
1425 1434 obj = self.find_line_magic(oname)
1426 1435 else:
1427 1436 # search without prefix, so run? will find %run?
1428 1437 obj = self.find_line_magic(oname)
1429 1438 if obj is None:
1430 1439 obj = self.find_cell_magic(oname)
1431 1440 if obj is not None:
1432 1441 found = True
1433 1442 ospace = 'IPython internal'
1434 1443 ismagic = True
1435 1444
1436 1445 # Last try: special-case some literals like '', [], {}, etc:
1437 1446 if not found and oname_head in ["''",'""','[]','{}','()']:
1438 1447 obj = eval(oname_head)
1439 1448 found = True
1440 1449 ospace = 'Interactive'
1441 1450
1442 1451 return {'found':found, 'obj':obj, 'namespace':ospace,
1443 1452 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1444 1453
1445 1454 def _ofind_property(self, oname, info):
1446 1455 """Second part of object finding, to look for property details."""
1447 1456 if info.found:
1448 1457 # Get the docstring of the class property if it exists.
1449 1458 path = oname.split('.')
1450 1459 root = '.'.join(path[:-1])
1451 1460 if info.parent is not None:
1452 1461 try:
1453 1462 target = getattr(info.parent, '__class__')
1454 1463 # The object belongs to a class instance.
1455 1464 try:
1456 1465 target = getattr(target, path[-1])
1457 1466 # The class defines the object.
1458 1467 if isinstance(target, property):
1459 1468 oname = root + '.__class__.' + path[-1]
1460 1469 info = Struct(self._ofind(oname))
1461 1470 except AttributeError: pass
1462 1471 except AttributeError: pass
1463 1472
1464 1473 # We return either the new info or the unmodified input if the object
1465 1474 # hadn't been found
1466 1475 return info
1467 1476
1468 1477 def _object_find(self, oname, namespaces=None):
1469 1478 """Find an object and return a struct with info about it."""
1470 1479 inf = Struct(self._ofind(oname, namespaces))
1471 1480 return Struct(self._ofind_property(oname, inf))
1472 1481
1473 1482 def _inspect(self, meth, oname, namespaces=None, **kw):
1474 1483 """Generic interface to the inspector system.
1475 1484
1476 1485 This function is meant to be called by pdef, pdoc & friends."""
1477 1486 info = self._object_find(oname, namespaces)
1478 1487 if info.found:
1479 1488 pmethod = getattr(self.inspector, meth)
1480 1489 formatter = format_screen if info.ismagic else None
1481 1490 if meth == 'pdoc':
1482 1491 pmethod(info.obj, oname, formatter)
1483 1492 elif meth == 'pinfo':
1484 1493 pmethod(info.obj, oname, formatter, info, **kw)
1485 1494 else:
1486 1495 pmethod(info.obj, oname)
1487 1496 else:
1488 1497 print('Object `%s` not found.' % oname)
1489 1498 return 'not found' # so callers can take other action
1490 1499
1491 1500 def object_inspect(self, oname, detail_level=0):
1492 1501 with self.builtin_trap:
1493 1502 info = self._object_find(oname)
1494 1503 if info.found:
1495 1504 return self.inspector.info(info.obj, oname, info=info,
1496 1505 detail_level=detail_level
1497 1506 )
1498 1507 else:
1499 1508 return oinspect.object_info(name=oname, found=False)
1500 1509
1501 1510 #-------------------------------------------------------------------------
1502 1511 # Things related to history management
1503 1512 #-------------------------------------------------------------------------
1504 1513
1505 1514 def init_history(self):
1506 1515 """Sets up the command history, and starts regular autosaves."""
1507 1516 self.history_manager = HistoryManager(shell=self, config=self.config)
1508 1517 self.configurables.append(self.history_manager)
1509 1518
1510 1519 #-------------------------------------------------------------------------
1511 1520 # Things related to exception handling and tracebacks (not debugging)
1512 1521 #-------------------------------------------------------------------------
1513 1522
1514 1523 def init_traceback_handlers(self, custom_exceptions):
1515 1524 # Syntax error handler.
1516 1525 self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor')
1517 1526
1518 1527 # The interactive one is initialized with an offset, meaning we always
1519 1528 # want to remove the topmost item in the traceback, which is our own
1520 1529 # internal code. Valid modes: ['Plain','Context','Verbose']
1521 1530 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1522 1531 color_scheme='NoColor',
1523 1532 tb_offset = 1,
1524 1533 check_cache=self.compile.check_cache)
1525 1534
1526 1535 # The instance will store a pointer to the system-wide exception hook,
1527 1536 # so that runtime code (such as magics) can access it. This is because
1528 1537 # during the read-eval loop, it may get temporarily overwritten.
1529 1538 self.sys_excepthook = sys.excepthook
1530 1539
1531 1540 # and add any custom exception handlers the user may have specified
1532 1541 self.set_custom_exc(*custom_exceptions)
1533 1542
1534 1543 # Set the exception mode
1535 1544 self.InteractiveTB.set_mode(mode=self.xmode)
1536 1545
1537 1546 def set_custom_exc(self, exc_tuple, handler):
1538 1547 """set_custom_exc(exc_tuple,handler)
1539 1548
1540 1549 Set a custom exception handler, which will be called if any of the
1541 1550 exceptions in exc_tuple occur in the mainloop (specifically, in the
1542 1551 run_code() method).
1543 1552
1544 1553 Parameters
1545 1554 ----------
1546 1555
1547 1556 exc_tuple : tuple of exception classes
1548 1557 A *tuple* of exception classes, for which to call the defined
1549 1558 handler. It is very important that you use a tuple, and NOT A
1550 1559 LIST here, because of the way Python's except statement works. If
1551 1560 you only want to trap a single exception, use a singleton tuple::
1552 1561
1553 1562 exc_tuple == (MyCustomException,)
1554 1563
1555 1564 handler : callable
1556 1565 handler must have the following signature::
1557 1566
1558 1567 def my_handler(self, etype, value, tb, tb_offset=None):
1559 1568 ...
1560 1569 return structured_traceback
1561 1570
1562 1571 Your handler must return a structured traceback (a list of strings),
1563 1572 or None.
1564 1573
1565 1574 This will be made into an instance method (via types.MethodType)
1566 1575 of IPython itself, and it will be called if any of the exceptions
1567 1576 listed in the exc_tuple are caught. If the handler is None, an
1568 1577 internal basic one is used, which just prints basic info.
1569 1578
1570 1579 To protect IPython from crashes, if your handler ever raises an
1571 1580 exception or returns an invalid result, it will be immediately
1572 1581 disabled.
1573 1582
1574 1583 WARNING: by putting in your own exception handler into IPython's main
1575 1584 execution loop, you run a very good chance of nasty crashes. This
1576 1585 facility should only be used if you really know what you are doing."""
1577 1586
1578 1587 assert type(exc_tuple)==type(()) , \
1579 1588 "The custom exceptions must be given AS A TUPLE."
1580 1589
1581 1590 def dummy_handler(self,etype,value,tb,tb_offset=None):
1582 1591 print('*** Simple custom exception handler ***')
1583 1592 print('Exception type :',etype)
1584 1593 print('Exception value:',value)
1585 1594 print('Traceback :',tb)
1586 1595 #print 'Source code :','\n'.join(self.buffer)
1587 1596
1588 1597 def validate_stb(stb):
1589 1598 """validate structured traceback return type
1590 1599
1591 1600 return type of CustomTB *should* be a list of strings, but allow
1592 1601 single strings or None, which are harmless.
1593 1602
1594 1603 This function will *always* return a list of strings,
1595 1604 and will raise a TypeError if stb is inappropriate.
1596 1605 """
1597 1606 msg = "CustomTB must return list of strings, not %r" % stb
1598 1607 if stb is None:
1599 1608 return []
1600 1609 elif isinstance(stb, basestring):
1601 1610 return [stb]
1602 1611 elif not isinstance(stb, list):
1603 1612 raise TypeError(msg)
1604 1613 # it's a list
1605 1614 for line in stb:
1606 1615 # check every element
1607 1616 if not isinstance(line, basestring):
1608 1617 raise TypeError(msg)
1609 1618 return stb
1610 1619
1611 1620 if handler is None:
1612 1621 wrapped = dummy_handler
1613 1622 else:
1614 1623 def wrapped(self,etype,value,tb,tb_offset=None):
1615 1624 """wrap CustomTB handler, to protect IPython from user code
1616 1625
1617 1626 This makes it harder (but not impossible) for custom exception
1618 1627 handlers to crash IPython.
1619 1628 """
1620 1629 try:
1621 1630 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
1622 1631 return validate_stb(stb)
1623 1632 except:
1624 1633 # clear custom handler immediately
1625 1634 self.set_custom_exc((), None)
1626 1635 print("Custom TB Handler failed, unregistering", file=io.stderr)
1627 1636 # show the exception in handler first
1628 1637 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
1629 1638 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1630 1639 print("The original exception:", file=io.stdout)
1631 1640 stb = self.InteractiveTB.structured_traceback(
1632 1641 (etype,value,tb), tb_offset=tb_offset
1633 1642 )
1634 1643 return stb
1635 1644
1636 1645 self.CustomTB = types.MethodType(wrapped,self)
1637 1646 self.custom_exceptions = exc_tuple
1638 1647
1639 1648 def excepthook(self, etype, value, tb):
1640 1649 """One more defense for GUI apps that call sys.excepthook.
1641 1650
1642 1651 GUI frameworks like wxPython trap exceptions and call
1643 1652 sys.excepthook themselves. I guess this is a feature that
1644 1653 enables them to keep running after exceptions that would
1645 1654 otherwise kill their mainloop. This is a bother for IPython
1646 1655 which excepts to catch all of the program exceptions with a try:
1647 1656 except: statement.
1648 1657
1649 1658 Normally, IPython sets sys.excepthook to a CrashHandler instance, so if
1650 1659 any app directly invokes sys.excepthook, it will look to the user like
1651 1660 IPython crashed. In order to work around this, we can disable the
1652 1661 CrashHandler and replace it with this excepthook instead, which prints a
1653 1662 regular traceback using our InteractiveTB. In this fashion, apps which
1654 1663 call sys.excepthook will generate a regular-looking exception from
1655 1664 IPython, and the CrashHandler will only be triggered by real IPython
1656 1665 crashes.
1657 1666
1658 1667 This hook should be used sparingly, only in places which are not likely
1659 1668 to be true IPython errors.
1660 1669 """
1661 1670 self.showtraceback((etype,value,tb),tb_offset=0)
1662 1671
1663 1672 def _get_exc_info(self, exc_tuple=None):
1664 1673 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
1665 1674
1666 1675 Ensures sys.last_type,value,traceback hold the exc_info we found,
1667 1676 from whichever source.
1668 1677
1669 1678 raises ValueError if none of these contain any information
1670 1679 """
1671 1680 if exc_tuple is None:
1672 1681 etype, value, tb = sys.exc_info()
1673 1682 else:
1674 1683 etype, value, tb = exc_tuple
1675 1684
1676 1685 if etype is None:
1677 1686 if hasattr(sys, 'last_type'):
1678 1687 etype, value, tb = sys.last_type, sys.last_value, \
1679 1688 sys.last_traceback
1680 1689
1681 1690 if etype is None:
1682 1691 raise ValueError("No exception to find")
1683 1692
1684 1693 # Now store the exception info in sys.last_type etc.
1685 1694 # WARNING: these variables are somewhat deprecated and not
1686 1695 # necessarily safe to use in a threaded environment, but tools
1687 1696 # like pdb depend on their existence, so let's set them. If we
1688 1697 # find problems in the field, we'll need to revisit their use.
1689 1698 sys.last_type = etype
1690 1699 sys.last_value = value
1691 1700 sys.last_traceback = tb
1692 1701
1693 1702 return etype, value, tb
1694 1703
1695 1704
1696 1705 def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
1697 1706 exception_only=False):
1698 1707 """Display the exception that just occurred.
1699 1708
1700 1709 If nothing is known about the exception, this is the method which
1701 1710 should be used throughout the code for presenting user tracebacks,
1702 1711 rather than directly invoking the InteractiveTB object.
1703 1712
1704 1713 A specific showsyntaxerror() also exists, but this method can take
1705 1714 care of calling it if needed, so unless you are explicitly catching a
1706 1715 SyntaxError exception, don't try to analyze the stack manually and
1707 1716 simply call this method."""
1708 1717
1709 1718 try:
1710 1719 try:
1711 1720 etype, value, tb = self._get_exc_info(exc_tuple)
1712 1721 except ValueError:
1713 1722 self.write_err('No traceback available to show.\n')
1714 1723 return
1715 1724
1716 1725 if etype is SyntaxError:
1717 1726 # Though this won't be called by syntax errors in the input
1718 1727 # line, there may be SyntaxError cases with imported code.
1719 1728 self.showsyntaxerror(filename)
1720 1729 elif etype is UsageError:
1721 1730 self.write_err("UsageError: %s" % value)
1722 1731 else:
1723 1732 if exception_only:
1724 1733 stb = ['An exception has occurred, use %tb to see '
1725 1734 'the full traceback.\n']
1726 1735 stb.extend(self.InteractiveTB.get_exception_only(etype,
1727 1736 value))
1728 1737 else:
1729 1738 try:
1730 1739 # Exception classes can customise their traceback - we
1731 1740 # use this in IPython.parallel for exceptions occurring
1732 1741 # in the engines. This should return a list of strings.
1733 1742 stb = value._render_traceback_()
1734 1743 except Exception:
1735 1744 stb = self.InteractiveTB.structured_traceback(etype,
1736 1745 value, tb, tb_offset=tb_offset)
1737 1746
1738 1747 self._showtraceback(etype, value, stb)
1739 1748 if self.call_pdb:
1740 1749 # drop into debugger
1741 1750 self.debugger(force=True)
1742 1751 return
1743 1752
1744 1753 # Actually show the traceback
1745 1754 self._showtraceback(etype, value, stb)
1746 1755
1747 1756 except KeyboardInterrupt:
1748 1757 self.write_err("\nKeyboardInterrupt\n")
1749 1758
1750 1759 def _showtraceback(self, etype, evalue, stb):
1751 1760 """Actually show a traceback.
1752 1761
1753 1762 Subclasses may override this method to put the traceback on a different
1754 1763 place, like a side channel.
1755 1764 """
1756 1765 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1757 1766
1758 1767 def showsyntaxerror(self, filename=None):
1759 1768 """Display the syntax error that just occurred.
1760 1769
1761 1770 This doesn't display a stack trace because there isn't one.
1762 1771
1763 1772 If a filename is given, it is stuffed in the exception instead
1764 1773 of what was there before (because Python's parser always uses
1765 1774 "<string>" when reading from a string).
1766 1775 """
1767 1776 etype, value, last_traceback = self._get_exc_info()
1768 1777
1769 1778 if filename and etype is SyntaxError:
1770 1779 try:
1771 1780 value.filename = filename
1772 1781 except:
1773 1782 # Not the format we expect; leave it alone
1774 1783 pass
1775 1784
1776 1785 stb = self.SyntaxTB.structured_traceback(etype, value, [])
1777 1786 self._showtraceback(etype, value, stb)
1778 1787
1779 1788 # This is overridden in TerminalInteractiveShell to show a message about
1780 1789 # the %paste magic.
1781 1790 def showindentationerror(self):
1782 1791 """Called by run_cell when there's an IndentationError in code entered
1783 1792 at the prompt.
1784 1793
1785 1794 This is overridden in TerminalInteractiveShell to show a message about
1786 1795 the %paste magic."""
1787 1796 self.showsyntaxerror()
1788 1797
1789 1798 #-------------------------------------------------------------------------
1790 1799 # Things related to readline
1791 1800 #-------------------------------------------------------------------------
1792 1801
1793 1802 def init_readline(self):
1794 1803 """Command history completion/saving/reloading."""
1795 1804
1796 1805 if self.readline_use:
1797 1806 import IPython.utils.rlineimpl as readline
1798 1807
1799 1808 self.rl_next_input = None
1800 1809 self.rl_do_indent = False
1801 1810
1802 1811 if not self.readline_use or not readline.have_readline:
1803 1812 self.has_readline = False
1804 1813 self.readline = None
1805 1814 # Set a number of methods that depend on readline to be no-op
1806 1815 self.readline_no_record = no_op_context
1807 1816 self.set_readline_completer = no_op
1808 1817 self.set_custom_completer = no_op
1809 1818 if self.readline_use:
1810 1819 warn('Readline services not available or not loaded.')
1811 1820 else:
1812 1821 self.has_readline = True
1813 1822 self.readline = readline
1814 1823 sys.modules['readline'] = readline
1815 1824
1816 1825 # Platform-specific configuration
1817 1826 if os.name == 'nt':
1818 1827 # FIXME - check with Frederick to see if we can harmonize
1819 1828 # naming conventions with pyreadline to avoid this
1820 1829 # platform-dependent check
1821 1830 self.readline_startup_hook = readline.set_pre_input_hook
1822 1831 else:
1823 1832 self.readline_startup_hook = readline.set_startup_hook
1824 1833
1825 1834 # Load user's initrc file (readline config)
1826 1835 # Or if libedit is used, load editrc.
1827 1836 inputrc_name = os.environ.get('INPUTRC')
1828 1837 if inputrc_name is None:
1829 1838 inputrc_name = '.inputrc'
1830 1839 if readline.uses_libedit:
1831 1840 inputrc_name = '.editrc'
1832 1841 inputrc_name = os.path.join(self.home_dir, inputrc_name)
1833 1842 if os.path.isfile(inputrc_name):
1834 1843 try:
1835 1844 readline.read_init_file(inputrc_name)
1836 1845 except:
1837 1846 warn('Problems reading readline initialization file <%s>'
1838 1847 % inputrc_name)
1839 1848
1840 1849 # Configure readline according to user's prefs
1841 1850 # This is only done if GNU readline is being used. If libedit
1842 1851 # is being used (as on Leopard) the readline config is
1843 1852 # not run as the syntax for libedit is different.
1844 1853 if not readline.uses_libedit:
1845 1854 for rlcommand in self.readline_parse_and_bind:
1846 1855 #print "loading rl:",rlcommand # dbg
1847 1856 readline.parse_and_bind(rlcommand)
1848 1857
1849 1858 # Remove some chars from the delimiters list. If we encounter
1850 1859 # unicode chars, discard them.
1851 1860 delims = readline.get_completer_delims()
1852 1861 if not py3compat.PY3:
1853 1862 delims = delims.encode("ascii", "ignore")
1854 1863 for d in self.readline_remove_delims:
1855 1864 delims = delims.replace(d, "")
1856 1865 delims = delims.replace(ESC_MAGIC, '')
1857 1866 readline.set_completer_delims(delims)
1858 1867 # otherwise we end up with a monster history after a while:
1859 1868 readline.set_history_length(self.history_length)
1860 1869
1861 1870 self.refill_readline_hist()
1862 1871 self.readline_no_record = ReadlineNoRecord(self)
1863 1872
1864 1873 # Configure auto-indent for all platforms
1865 1874 self.set_autoindent(self.autoindent)
1866 1875
1867 1876 def refill_readline_hist(self):
1868 1877 # Load the last 1000 lines from history
1869 1878 self.readline.clear_history()
1870 1879 stdin_encoding = sys.stdin.encoding or "utf-8"
1871 1880 last_cell = u""
1872 1881 for _, _, cell in self.history_manager.get_tail(1000,
1873 1882 include_latest=True):
1874 1883 # Ignore blank lines and consecutive duplicates
1875 1884 cell = cell.rstrip()
1876 1885 if cell and (cell != last_cell):
1877 1886 if self.multiline_history:
1878 1887 self.readline.add_history(py3compat.unicode_to_str(cell,
1879 1888 stdin_encoding))
1880 1889 else:
1881 1890 for line in cell.splitlines():
1882 1891 self.readline.add_history(py3compat.unicode_to_str(line,
1883 1892 stdin_encoding))
1884 1893 last_cell = cell
1885 1894
1886 1895 def set_next_input(self, s):
1887 1896 """ Sets the 'default' input string for the next command line.
1888 1897
1889 1898 Requires readline.
1890 1899
1891 1900 Example:
1892 1901
1893 1902 [D:\ipython]|1> _ip.set_next_input("Hello Word")
1894 1903 [D:\ipython]|2> Hello Word_ # cursor is here
1895 1904 """
1896 1905 self.rl_next_input = py3compat.cast_bytes_py2(s)
1897 1906
1898 1907 # Maybe move this to the terminal subclass?
1899 1908 def pre_readline(self):
1900 1909 """readline hook to be used at the start of each line.
1901 1910
1902 1911 Currently it handles auto-indent only."""
1903 1912
1904 1913 if self.rl_do_indent:
1905 1914 self.readline.insert_text(self._indent_current_str())
1906 1915 if self.rl_next_input is not None:
1907 1916 self.readline.insert_text(self.rl_next_input)
1908 1917 self.rl_next_input = None
1909 1918
1910 1919 def _indent_current_str(self):
1911 1920 """return the current level of indentation as a string"""
1912 1921 return self.input_splitter.indent_spaces * ' '
1913 1922
1914 1923 #-------------------------------------------------------------------------
1915 1924 # Things related to text completion
1916 1925 #-------------------------------------------------------------------------
1917 1926
1918 1927 def init_completer(self):
1919 1928 """Initialize the completion machinery.
1920 1929
1921 1930 This creates completion machinery that can be used by client code,
1922 1931 either interactively in-process (typically triggered by the readline
1923 1932 library), programatically (such as in test suites) or out-of-prcess
1924 1933 (typically over the network by remote frontends).
1925 1934 """
1926 1935 from IPython.core.completer import IPCompleter
1927 1936 from IPython.core.completerlib import (module_completer,
1928 1937 magic_run_completer, cd_completer, reset_completer)
1929 1938
1930 1939 self.Completer = IPCompleter(shell=self,
1931 1940 namespace=self.user_ns,
1932 1941 global_namespace=self.user_global_ns,
1933 1942 alias_table=self.alias_manager.alias_table,
1934 1943 use_readline=self.has_readline,
1935 1944 config=self.config,
1936 1945 )
1937 1946 self.configurables.append(self.Completer)
1938 1947
1939 1948 # Add custom completers to the basic ones built into IPCompleter
1940 1949 sdisp = self.strdispatchers.get('complete_command', StrDispatch())
1941 1950 self.strdispatchers['complete_command'] = sdisp
1942 1951 self.Completer.custom_completers = sdisp
1943 1952
1944 1953 self.set_hook('complete_command', module_completer, str_key = 'import')
1945 1954 self.set_hook('complete_command', module_completer, str_key = 'from')
1946 1955 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
1947 1956 self.set_hook('complete_command', cd_completer, str_key = '%cd')
1948 1957 self.set_hook('complete_command', reset_completer, str_key = '%reset')
1949 1958
1950 1959 # Only configure readline if we truly are using readline. IPython can
1951 1960 # do tab-completion over the network, in GUIs, etc, where readline
1952 1961 # itself may be absent
1953 1962 if self.has_readline:
1954 1963 self.set_readline_completer()
1955 1964
1956 1965 def complete(self, text, line=None, cursor_pos=None):
1957 1966 """Return the completed text and a list of completions.
1958 1967
1959 1968 Parameters
1960 1969 ----------
1961 1970
1962 1971 text : string
1963 1972 A string of text to be completed on. It can be given as empty and
1964 1973 instead a line/position pair are given. In this case, the
1965 1974 completer itself will split the line like readline does.
1966 1975
1967 1976 line : string, optional
1968 1977 The complete line that text is part of.
1969 1978
1970 1979 cursor_pos : int, optional
1971 1980 The position of the cursor on the input line.
1972 1981
1973 1982 Returns
1974 1983 -------
1975 1984 text : string
1976 1985 The actual text that was completed.
1977 1986
1978 1987 matches : list
1979 1988 A sorted list with all possible completions.
1980 1989
1981 1990 The optional arguments allow the completion to take more context into
1982 1991 account, and are part of the low-level completion API.
1983 1992
1984 1993 This is a wrapper around the completion mechanism, similar to what
1985 1994 readline does at the command line when the TAB key is hit. By
1986 1995 exposing it as a method, it can be used by other non-readline
1987 1996 environments (such as GUIs) for text completion.
1988 1997
1989 1998 Simple usage example:
1990 1999
1991 2000 In [1]: x = 'hello'
1992 2001
1993 2002 In [2]: _ip.complete('x.l')
1994 2003 Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip'])
1995 2004 """
1996 2005
1997 2006 # Inject names into __builtin__ so we can complete on the added names.
1998 2007 with self.builtin_trap:
1999 2008 return self.Completer.complete(text, line, cursor_pos)
2000 2009
2001 2010 def set_custom_completer(self, completer, pos=0):
2002 2011 """Adds a new custom completer function.
2003 2012
2004 2013 The position argument (defaults to 0) is the index in the completers
2005 2014 list where you want the completer to be inserted."""
2006 2015
2007 2016 newcomp = types.MethodType(completer,self.Completer)
2008 2017 self.Completer.matchers.insert(pos,newcomp)
2009 2018
2010 2019 def set_readline_completer(self):
2011 2020 """Reset readline's completer to be our own."""
2012 2021 self.readline.set_completer(self.Completer.rlcomplete)
2013 2022
2014 2023 def set_completer_frame(self, frame=None):
2015 2024 """Set the frame of the completer."""
2016 2025 if frame:
2017 2026 self.Completer.namespace = frame.f_locals
2018 2027 self.Completer.global_namespace = frame.f_globals
2019 2028 else:
2020 2029 self.Completer.namespace = self.user_ns
2021 2030 self.Completer.global_namespace = self.user_global_ns
2022 2031
2023 2032 #-------------------------------------------------------------------------
2024 2033 # Things related to magics
2025 2034 #-------------------------------------------------------------------------
2026 2035
2027 2036 def init_magics(self):
2028 2037 from IPython.core import magics as m
2029 2038 self.magics_manager = magic.MagicsManager(shell=self,
2030 2039 confg=self.config,
2031 2040 user_magics=m.UserMagics(self))
2032 2041 self.configurables.append(self.magics_manager)
2033 2042
2034 2043 # Expose as public API from the magics manager
2035 2044 self.register_magics = self.magics_manager.register
2036 2045 self.register_magic_function = self.magics_manager.register_function
2037 2046 self.define_magic = self.magics_manager.define_magic
2038 2047
2039 2048 self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
2040 2049 m.ConfigMagics, m.DeprecatedMagics, m.DisplayMagics, m.ExecutionMagics,
2041 2050 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2042 2051 m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
2043 2052 )
2044 2053
2045 2054 # Register Magic Aliases
2046 2055 mman = self.magics_manager
2047 2056 mman.register_alias('ed', 'edit')
2048 2057 mman.register_alias('hist', 'history')
2049 2058 mman.register_alias('rep', 'recall')
2050 2059
2051 2060 # FIXME: Move the color initialization to the DisplayHook, which
2052 2061 # should be split into a prompt manager and displayhook. We probably
2053 2062 # even need a centralize colors management object.
2054 2063 self.magic('colors %s' % self.colors)
2055 2064
2056 2065 def run_line_magic(self, magic_name, line):
2057 2066 """Execute the given line magic.
2058 2067
2059 2068 Parameters
2060 2069 ----------
2061 2070 magic_name : str
2062 2071 Name of the desired magic function, without '%' prefix.
2063 2072
2064 2073 line : str
2065 2074 The rest of the input line as a single string.
2066 2075 """
2067 2076 fn = self.find_line_magic(magic_name)
2068 2077 if fn is None:
2069 2078 cm = self.find_cell_magic(magic_name)
2070 2079 etpl = "Line magic function `%%%s` not found%s."
2071 2080 extra = '' if cm is None else (' (But cell magic `%%%%%s` exists, '
2072 2081 'did you mean that instead?)' % magic_name )
2073 2082 error(etpl % (magic_name, extra))
2074 2083 else:
2075 2084 # Note: this is the distance in the stack to the user's frame.
2076 2085 # This will need to be updated if the internal calling logic gets
2077 2086 # refactored, or else we'll be expanding the wrong variables.
2078 2087 stack_depth = 2
2079 2088 magic_arg_s = self.var_expand(line, stack_depth)
2080 2089 # Put magic args in a list so we can call with f(*a) syntax
2081 2090 args = [magic_arg_s]
2082 2091 # Grab local namespace if we need it:
2083 2092 if getattr(fn, "needs_local_scope", False):
2084 2093 args.append(sys._getframe(stack_depth).f_locals)
2085 2094 with self.builtin_trap:
2086 2095 result = fn(*args)
2087 2096 return result
2088 2097
2089 2098 def run_cell_magic(self, magic_name, line, cell):
2090 2099 """Execute the given cell magic.
2091 2100
2092 2101 Parameters
2093 2102 ----------
2094 2103 magic_name : str
2095 2104 Name of the desired magic function, without '%' prefix.
2096 2105
2097 2106 line : str
2098 2107 The rest of the first input line as a single string.
2099 2108
2100 2109 cell : str
2101 2110 The body of the cell as a (possibly multiline) string.
2102 2111 """
2103 2112 fn = self.find_cell_magic(magic_name)
2104 2113 if fn is None:
2105 2114 lm = self.find_line_magic(magic_name)
2106 2115 etpl = "Cell magic function `%%%%%s` not found%s."
2107 2116 extra = '' if lm is None else (' (But line magic `%%%s` exists, '
2108 2117 'did you mean that instead?)' % magic_name )
2109 2118 error(etpl % (magic_name, extra))
2110 2119 else:
2111 2120 # Note: this is the distance in the stack to the user's frame.
2112 2121 # This will need to be updated if the internal calling logic gets
2113 2122 # refactored, or else we'll be expanding the wrong variables.
2114 2123 stack_depth = 2
2115 2124 magic_arg_s = self.var_expand(line, stack_depth)
2116 2125 with self.builtin_trap:
2117 2126 result = fn(magic_arg_s, cell)
2118 2127 return result
2119 2128
2120 2129 def find_line_magic(self, magic_name):
2121 2130 """Find and return a line magic by name.
2122 2131
2123 2132 Returns None if the magic isn't found."""
2124 2133 return self.magics_manager.magics['line'].get(magic_name)
2125 2134
2126 2135 def find_cell_magic(self, magic_name):
2127 2136 """Find and return a cell magic by name.
2128 2137
2129 2138 Returns None if the magic isn't found."""
2130 2139 return self.magics_manager.magics['cell'].get(magic_name)
2131 2140
2132 2141 def find_magic(self, magic_name, magic_kind='line'):
2133 2142 """Find and return a magic of the given type by name.
2134 2143
2135 2144 Returns None if the magic isn't found."""
2136 2145 return self.magics_manager.magics[magic_kind].get(magic_name)
2137 2146
2138 2147 def magic(self, arg_s):
2139 2148 """DEPRECATED. Use run_line_magic() instead.
2140 2149
2141 2150 Call a magic function by name.
2142 2151
2143 2152 Input: a string containing the name of the magic function to call and
2144 2153 any additional arguments to be passed to the magic.
2145 2154
2146 2155 magic('name -opt foo bar') is equivalent to typing at the ipython
2147 2156 prompt:
2148 2157
2149 2158 In[1]: %name -opt foo bar
2150 2159
2151 2160 To call a magic without arguments, simply use magic('name').
2152 2161
2153 2162 This provides a proper Python function to call IPython's magics in any
2154 2163 valid Python code you can type at the interpreter, including loops and
2155 2164 compound statements.
2156 2165 """
2157 2166 # TODO: should we issue a loud deprecation warning here?
2158 2167 magic_name, _, magic_arg_s = arg_s.partition(' ')
2159 2168 magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
2160 2169 return self.run_line_magic(magic_name, magic_arg_s)
2161 2170
2162 2171 #-------------------------------------------------------------------------
2163 2172 # Things related to macros
2164 2173 #-------------------------------------------------------------------------
2165 2174
2166 2175 def define_macro(self, name, themacro):
2167 2176 """Define a new macro
2168 2177
2169 2178 Parameters
2170 2179 ----------
2171 2180 name : str
2172 2181 The name of the macro.
2173 2182 themacro : str or Macro
2174 2183 The action to do upon invoking the macro. If a string, a new
2175 2184 Macro object is created by passing the string to it.
2176 2185 """
2177 2186
2178 2187 from IPython.core import macro
2179 2188
2180 2189 if isinstance(themacro, basestring):
2181 2190 themacro = macro.Macro(themacro)
2182 2191 if not isinstance(themacro, macro.Macro):
2183 2192 raise ValueError('A macro must be a string or a Macro instance.')
2184 2193 self.user_ns[name] = themacro
2185 2194
2186 2195 #-------------------------------------------------------------------------
2187 2196 # Things related to the running of system commands
2188 2197 #-------------------------------------------------------------------------
2189 2198
2190 2199 def system_piped(self, cmd):
2191 2200 """Call the given cmd in a subprocess, piping stdout/err
2192 2201
2193 2202 Parameters
2194 2203 ----------
2195 2204 cmd : str
2196 2205 Command to execute (can not end in '&', as background processes are
2197 2206 not supported. Should not be a command that expects input
2198 2207 other than simple text.
2199 2208 """
2200 2209 if cmd.rstrip().endswith('&'):
2201 2210 # this is *far* from a rigorous test
2202 2211 # We do not support backgrounding processes because we either use
2203 2212 # pexpect or pipes to read from. Users can always just call
2204 2213 # os.system() or use ip.system=ip.system_raw
2205 2214 # if they really want a background process.
2206 2215 raise OSError("Background processes not supported.")
2207 2216
2208 2217 # we explicitly do NOT return the subprocess status code, because
2209 2218 # a non-None value would trigger :func:`sys.displayhook` calls.
2210 2219 # Instead, we store the exit_code in user_ns.
2211 2220 self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1))
2212 2221
2213 2222 def system_raw(self, cmd):
2214 2223 """Call the given cmd in a subprocess using os.system
2215 2224
2216 2225 Parameters
2217 2226 ----------
2218 2227 cmd : str
2219 2228 Command to execute.
2220 2229 """
2221 2230 cmd = self.var_expand(cmd, depth=1)
2222 2231 # protect os.system from UNC paths on Windows, which it can't handle:
2223 2232 if sys.platform == 'win32':
2224 2233 from IPython.utils._process_win32 import AvoidUNCPath
2225 2234 with AvoidUNCPath() as path:
2226 2235 if path is not None:
2227 2236 cmd = '"pushd %s &&"%s' % (path, cmd)
2228 2237 cmd = py3compat.unicode_to_str(cmd)
2229 2238 ec = os.system(cmd)
2230 2239 else:
2231 2240 cmd = py3compat.unicode_to_str(cmd)
2232 2241 ec = os.system(cmd)
2233 2242
2234 2243 # We explicitly do NOT return the subprocess status code, because
2235 2244 # a non-None value would trigger :func:`sys.displayhook` calls.
2236 2245 # Instead, we store the exit_code in user_ns.
2237 2246 self.user_ns['_exit_code'] = ec
2238 2247
2239 2248 # use piped system by default, because it is better behaved
2240 2249 system = system_piped
2241 2250
2242 2251 def getoutput(self, cmd, split=True, depth=0):
2243 2252 """Get output (possibly including stderr) from a subprocess.
2244 2253
2245 2254 Parameters
2246 2255 ----------
2247 2256 cmd : str
2248 2257 Command to execute (can not end in '&', as background processes are
2249 2258 not supported.
2250 2259 split : bool, optional
2251 2260 If True, split the output into an IPython SList. Otherwise, an
2252 2261 IPython LSString is returned. These are objects similar to normal
2253 2262 lists and strings, with a few convenience attributes for easier
2254 2263 manipulation of line-based output. You can use '?' on them for
2255 2264 details.
2256 2265 depth : int, optional
2257 2266 How many frames above the caller are the local variables which should
2258 2267 be expanded in the command string? The default (0) assumes that the
2259 2268 expansion variables are in the stack frame calling this function.
2260 2269 """
2261 2270 if cmd.rstrip().endswith('&'):
2262 2271 # this is *far* from a rigorous test
2263 2272 raise OSError("Background processes not supported.")
2264 2273 out = getoutput(self.var_expand(cmd, depth=depth+1))
2265 2274 if split:
2266 2275 out = SList(out.splitlines())
2267 2276 else:
2268 2277 out = LSString(out)
2269 2278 return out
2270 2279
2271 2280 #-------------------------------------------------------------------------
2272 2281 # Things related to aliases
2273 2282 #-------------------------------------------------------------------------
2274 2283
2275 2284 def init_alias(self):
2276 2285 self.alias_manager = AliasManager(shell=self, config=self.config)
2277 2286 self.configurables.append(self.alias_manager)
2278 2287 self.ns_table['alias'] = self.alias_manager.alias_table,
2279 2288
2280 2289 #-------------------------------------------------------------------------
2281 2290 # Things related to extensions and plugins
2282 2291 #-------------------------------------------------------------------------
2283 2292
2284 2293 def init_extension_manager(self):
2285 2294 self.extension_manager = ExtensionManager(shell=self, config=self.config)
2286 2295 self.configurables.append(self.extension_manager)
2287 2296
2288 2297 def init_plugin_manager(self):
2289 2298 self.plugin_manager = PluginManager(config=self.config)
2290 2299 self.configurables.append(self.plugin_manager)
2291 2300
2292 2301
2293 2302 #-------------------------------------------------------------------------
2294 2303 # Things related to payloads
2295 2304 #-------------------------------------------------------------------------
2296 2305
2297 2306 def init_payload(self):
2298 2307 self.payload_manager = PayloadManager(config=self.config)
2299 2308 self.configurables.append(self.payload_manager)
2300 2309
2301 2310 #-------------------------------------------------------------------------
2302 2311 # Things related to the prefilter
2303 2312 #-------------------------------------------------------------------------
2304 2313
2305 2314 def init_prefilter(self):
2306 2315 self.prefilter_manager = PrefilterManager(shell=self, config=self.config)
2307 2316 self.configurables.append(self.prefilter_manager)
2308 2317 # Ultimately this will be refactored in the new interpreter code, but
2309 2318 # for now, we should expose the main prefilter method (there's legacy
2310 2319 # code out there that may rely on this).
2311 2320 self.prefilter = self.prefilter_manager.prefilter_lines
2312 2321
2313 2322 def auto_rewrite_input(self, cmd):
2314 2323 """Print to the screen the rewritten form of the user's command.
2315 2324
2316 2325 This shows visual feedback by rewriting input lines that cause
2317 2326 automatic calling to kick in, like::
2318 2327
2319 2328 /f x
2320 2329
2321 2330 into::
2322 2331
2323 2332 ------> f(x)
2324 2333
2325 2334 after the user's input prompt. This helps the user understand that the
2326 2335 input line was transformed automatically by IPython.
2327 2336 """
2328 2337 if not self.show_rewritten_input:
2329 2338 return
2330 2339
2331 2340 rw = self.prompt_manager.render('rewrite') + cmd
2332 2341
2333 2342 try:
2334 2343 # plain ascii works better w/ pyreadline, on some machines, so
2335 2344 # we use it and only print uncolored rewrite if we have unicode
2336 2345 rw = str(rw)
2337 2346 print(rw, file=io.stdout)
2338 2347 except UnicodeEncodeError:
2339 2348 print("------> " + cmd)
2340 2349
2341 2350 #-------------------------------------------------------------------------
2342 2351 # Things related to extracting values/expressions from kernel and user_ns
2343 2352 #-------------------------------------------------------------------------
2344 2353
2345 2354 def _simple_error(self):
2346 2355 etype, value = sys.exc_info()[:2]
2347 2356 return u'[ERROR] {e.__name__}: {v}'.format(e=etype, v=value)
2348 2357
2349 2358 def user_variables(self, names):
2350 2359 """Get a list of variable names from the user's namespace.
2351 2360
2352 2361 Parameters
2353 2362 ----------
2354 2363 names : list of strings
2355 2364 A list of names of variables to be read from the user namespace.
2356 2365
2357 2366 Returns
2358 2367 -------
2359 2368 A dict, keyed by the input names and with the repr() of each value.
2360 2369 """
2361 2370 out = {}
2362 2371 user_ns = self.user_ns
2363 2372 for varname in names:
2364 2373 try:
2365 2374 value = repr(user_ns[varname])
2366 2375 except:
2367 2376 value = self._simple_error()
2368 2377 out[varname] = value
2369 2378 return out
2370 2379
2371 2380 def user_expressions(self, expressions):
2372 2381 """Evaluate a dict of expressions in the user's namespace.
2373 2382
2374 2383 Parameters
2375 2384 ----------
2376 2385 expressions : dict
2377 2386 A dict with string keys and string values. The expression values
2378 2387 should be valid Python expressions, each of which will be evaluated
2379 2388 in the user namespace.
2380 2389
2381 2390 Returns
2382 2391 -------
2383 2392 A dict, keyed like the input expressions dict, with the repr() of each
2384 2393 value.
2385 2394 """
2386 2395 out = {}
2387 2396 user_ns = self.user_ns
2388 2397 global_ns = self.user_global_ns
2389 2398 for key, expr in expressions.iteritems():
2390 2399 try:
2391 2400 value = repr(eval(expr, global_ns, user_ns))
2392 2401 except:
2393 2402 value = self._simple_error()
2394 2403 out[key] = value
2395 2404 return out
2396 2405
2397 2406 #-------------------------------------------------------------------------
2398 2407 # Things related to the running of code
2399 2408 #-------------------------------------------------------------------------
2400 2409
2401 2410 def ex(self, cmd):
2402 2411 """Execute a normal python statement in user namespace."""
2403 2412 with self.builtin_trap:
2404 2413 exec cmd in self.user_global_ns, self.user_ns
2405 2414
2406 2415 def ev(self, expr):
2407 2416 """Evaluate python expression expr in user namespace.
2408 2417
2409 2418 Returns the result of evaluation
2410 2419 """
2411 2420 with self.builtin_trap:
2412 2421 return eval(expr, self.user_global_ns, self.user_ns)
2413 2422
2414 2423 def safe_execfile(self, fname, *where, **kw):
2415 2424 """A safe version of the builtin execfile().
2416 2425
2417 2426 This version will never throw an exception, but instead print
2418 2427 helpful error messages to the screen. This only works on pure
2419 2428 Python files with the .py extension.
2420 2429
2421 2430 Parameters
2422 2431 ----------
2423 2432 fname : string
2424 2433 The name of the file to be executed.
2425 2434 where : tuple
2426 2435 One or two namespaces, passed to execfile() as (globals,locals).
2427 2436 If only one is given, it is passed as both.
2428 2437 exit_ignore : bool (False)
2429 2438 If True, then silence SystemExit for non-zero status (it is always
2430 2439 silenced for zero status, as it is so common).
2431 2440 raise_exceptions : bool (False)
2432 2441 If True raise exceptions everywhere. Meant for testing.
2433 2442
2434 2443 """
2435 2444 kw.setdefault('exit_ignore', False)
2436 2445 kw.setdefault('raise_exceptions', False)
2437 2446
2438 2447 fname = os.path.abspath(os.path.expanduser(fname))
2439 2448
2440 2449 # Make sure we can open the file
2441 2450 try:
2442 2451 with open(fname) as thefile:
2443 2452 pass
2444 2453 except:
2445 2454 warn('Could not open file <%s> for safe execution.' % fname)
2446 2455 return
2447 2456
2448 2457 # Find things also in current directory. This is needed to mimic the
2449 2458 # behavior of running a script from the system command line, where
2450 2459 # Python inserts the script's directory into sys.path
2451 2460 dname = os.path.dirname(fname)
2452 2461
2453 2462 with prepended_to_syspath(dname):
2454 2463 # Ensure that __file__ is always defined to match Python behavior
2455 2464 save_fname = self.user_ns.get('__file__',None)
2456 2465 self.user_ns['__file__'] = fname
2457 2466 try:
2458 2467 py3compat.execfile(fname,*where)
2459 2468 except SystemExit as status:
2460 2469 # If the call was made with 0 or None exit status (sys.exit(0)
2461 2470 # or sys.exit() ), don't bother showing a traceback, as both of
2462 2471 # these are considered normal by the OS:
2463 2472 # > python -c'import sys;sys.exit(0)'; echo $?
2464 2473 # 0
2465 2474 # > python -c'import sys;sys.exit()'; echo $?
2466 2475 # 0
2467 2476 # For other exit status, we show the exception unless
2468 2477 # explicitly silenced, but only in short form.
2469 2478 if kw['raise_exceptions']:
2470 2479 raise
2471 2480 if status.code not in (0, None) and not kw['exit_ignore']:
2472 2481 self.showtraceback(exception_only=True)
2473 2482 except:
2474 2483 if kw['raise_exceptions']:
2475 2484 raise
2476 2485 self.showtraceback()
2477 2486 finally:
2478 2487 self.user_ns['__file__'] = save_fname
2479 2488
2480 2489 def safe_execfile_ipy(self, fname):
2481 2490 """Like safe_execfile, but for .ipy files with IPython syntax.
2482 2491
2483 2492 Parameters
2484 2493 ----------
2485 2494 fname : str
2486 2495 The name of the file to execute. The filename must have a
2487 2496 .ipy extension.
2488 2497 """
2489 2498 fname = os.path.abspath(os.path.expanduser(fname))
2490 2499
2491 2500 # Make sure we can open the file
2492 2501 try:
2493 2502 with open(fname) as thefile:
2494 2503 pass
2495 2504 except:
2496 2505 warn('Could not open file <%s> for safe execution.' % fname)
2497 2506 return
2498 2507
2499 2508 # Find things also in current directory. This is needed to mimic the
2500 2509 # behavior of running a script from the system command line, where
2501 2510 # Python inserts the script's directory into sys.path
2502 2511 dname = os.path.dirname(fname)
2503 2512
2504 2513 with prepended_to_syspath(dname):
2505 2514 # Ensure that __file__ is always defined to match Python behavior
2506 2515 save_fname = self.user_ns.get('__file__',None)
2507 2516 self.user_ns['__file__'] = fname
2508 2517 try:
2509 2518 with open(fname) as thefile:
2510 2519 # self.run_cell currently captures all exceptions
2511 2520 # raised in user code. It would be nice if there were
2512 2521 # versions of runlines, execfile that did raise, so
2513 2522 # we could catch the errors.
2514 2523 self.run_cell(thefile.read(), store_history=False)
2515 2524 except:
2516 2525 self.showtraceback()
2517 2526 warn('Unknown failure executing file: <%s>' % fname)
2518 2527 finally:
2519 2528 self.user_ns['__file__'] = save_fname
2520 2529
2521 2530 def safe_run_module(self, mod_name, where):
2522 2531 """A safe version of runpy.run_module().
2523 2532
2524 2533 This version will never throw an exception, but instead print
2525 2534 helpful error messages to the screen.
2526 2535
2527 2536 Parameters
2528 2537 ----------
2529 2538 mod_name : string
2530 2539 The name of the module to be executed.
2531 2540 where : dict
2532 2541 The globals namespace.
2533 2542 """
2534 2543 try:
2535 2544 where.update(
2536 2545 runpy.run_module(str(mod_name), run_name="__main__",
2537 2546 alter_sys=True)
2538 2547 )
2539 2548 except:
2540 2549 self.showtraceback()
2541 2550 warn('Unknown failure executing module: <%s>' % mod_name)
2542 2551
2543 2552 def _run_cached_cell_magic(self, magic_name, line):
2544 2553 """Special method to call a cell magic with the data stored in self.
2545 2554 """
2546 2555 cell = self._current_cell_magic_body
2547 2556 self._current_cell_magic_body = None
2548 2557 return self.run_cell_magic(magic_name, line, cell)
2549 2558
2550 2559 def run_cell(self, raw_cell, store_history=False, silent=False):
2551 2560 """Run a complete IPython cell.
2552 2561
2553 2562 Parameters
2554 2563 ----------
2555 2564 raw_cell : str
2556 2565 The code (including IPython code such as %magic functions) to run.
2557 2566 store_history : bool
2558 2567 If True, the raw and translated cell will be stored in IPython's
2559 2568 history. For user code calling back into IPython's machinery, this
2560 2569 should be set to False.
2561 2570 silent : bool
2562 2571 If True, avoid side-effets, such as implicit displayhooks, history,
2563 2572 and logging. silent=True forces store_history=False.
2564 2573 """
2565 2574 if (not raw_cell) or raw_cell.isspace():
2566 2575 return
2567 2576
2568 2577 if silent:
2569 2578 store_history = False
2570 2579
2571 2580 self.input_splitter.push(raw_cell)
2572 2581
2573 2582 # Check for cell magics, which leave state behind. This interface is
2574 2583 # ugly, we need to do something cleaner later... Now the logic is
2575 2584 # simply that the input_splitter remembers if there was a cell magic,
2576 2585 # and in that case we grab the cell body.
2577 2586 if self.input_splitter.cell_magic_parts:
2578 2587 self._current_cell_magic_body = \
2579 2588 ''.join(self.input_splitter.cell_magic_parts)
2580 2589 cell = self.input_splitter.source_reset()
2581 2590
2582 2591 with self.builtin_trap:
2583 2592 prefilter_failed = False
2584 2593 if len(cell.splitlines()) == 1:
2585 2594 try:
2586 2595 # use prefilter_lines to handle trailing newlines
2587 2596 # restore trailing newline for ast.parse
2588 2597 cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
2589 2598 except AliasError as e:
2590 2599 error(e)
2591 2600 prefilter_failed = True
2592 2601 except Exception:
2593 2602 # don't allow prefilter errors to crash IPython
2594 2603 self.showtraceback()
2595 2604 prefilter_failed = True
2596 2605
2597 2606 # Store raw and processed history
2598 2607 if store_history:
2599 2608 self.history_manager.store_inputs(self.execution_count,
2600 2609 cell, raw_cell)
2601 2610 if not silent:
2602 2611 self.logger.log(cell, raw_cell)
2603 2612
2604 2613 if not prefilter_failed:
2605 2614 # don't run if prefilter failed
2606 2615 cell_name = self.compile.cache(cell, self.execution_count)
2607 2616
2608 2617 with self.display_trap:
2609 2618 try:
2610 2619 code_ast = self.compile.ast_parse(cell,
2611 2620 filename=cell_name)
2612 2621 except IndentationError:
2613 2622 self.showindentationerror()
2614 2623 if store_history:
2615 2624 self.execution_count += 1
2616 2625 return None
2617 2626 except (OverflowError, SyntaxError, ValueError, TypeError,
2618 2627 MemoryError):
2619 2628 self.showsyntaxerror()
2620 2629 if store_history:
2621 2630 self.execution_count += 1
2622 2631 return None
2623 2632
2624 2633 interactivity = "none" if silent else self.ast_node_interactivity
2625 2634 self.run_ast_nodes(code_ast.body, cell_name,
2626 2635 interactivity=interactivity)
2627 2636
2628 2637 # Execute any registered post-execution functions.
2629 2638 # unless we are silent
2630 2639 post_exec = [] if silent else self._post_execute.iteritems()
2631 2640
2632 2641 for func, status in post_exec:
2633 2642 if self.disable_failing_post_execute and not status:
2634 2643 continue
2635 2644 try:
2636 2645 func()
2637 2646 except KeyboardInterrupt:
2638 2647 print("\nKeyboardInterrupt", file=io.stderr)
2639 2648 except Exception:
2640 2649 # register as failing:
2641 2650 self._post_execute[func] = False
2642 2651 self.showtraceback()
2643 2652 print('\n'.join([
2644 2653 "post-execution function %r produced an error." % func,
2645 2654 "If this problem persists, you can disable failing post-exec functions with:",
2646 2655 "",
2647 2656 " get_ipython().disable_failing_post_execute = True"
2648 2657 ]), file=io.stderr)
2649 2658
2650 2659 if store_history:
2651 2660 # Write output to the database. Does nothing unless
2652 2661 # history output logging is enabled.
2653 2662 self.history_manager.store_output(self.execution_count)
2654 2663 # Each cell is a *single* input, regardless of how many lines it has
2655 2664 self.execution_count += 1
2656 2665
2657 2666 def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr'):
2658 2667 """Run a sequence of AST nodes. The execution mode depends on the
2659 2668 interactivity parameter.
2660 2669
2661 2670 Parameters
2662 2671 ----------
2663 2672 nodelist : list
2664 2673 A sequence of AST nodes to run.
2665 2674 cell_name : str
2666 2675 Will be passed to the compiler as the filename of the cell. Typically
2667 2676 the value returned by ip.compile.cache(cell).
2668 2677 interactivity : str
2669 2678 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
2670 2679 run interactively (displaying output from expressions). 'last_expr'
2671 2680 will run the last node interactively only if it is an expression (i.e.
2672 2681 expressions in loops or other blocks are not displayed. Other values
2673 2682 for this parameter will raise a ValueError.
2674 2683 """
2675 2684 if not nodelist:
2676 2685 return
2677 2686
2678 2687 if interactivity == 'last_expr':
2679 2688 if isinstance(nodelist[-1], ast.Expr):
2680 2689 interactivity = "last"
2681 2690 else:
2682 2691 interactivity = "none"
2683 2692
2684 2693 if interactivity == 'none':
2685 2694 to_run_exec, to_run_interactive = nodelist, []
2686 2695 elif interactivity == 'last':
2687 2696 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
2688 2697 elif interactivity == 'all':
2689 2698 to_run_exec, to_run_interactive = [], nodelist
2690 2699 else:
2691 2700 raise ValueError("Interactivity was %r" % interactivity)
2692 2701
2693 2702 exec_count = self.execution_count
2694 2703
2695 2704 try:
2696 2705 for i, node in enumerate(to_run_exec):
2697 2706 mod = ast.Module([node])
2698 2707 code = self.compile(mod, cell_name, "exec")
2699 2708 if self.run_code(code):
2700 2709 return True
2701 2710
2702 2711 for i, node in enumerate(to_run_interactive):
2703 2712 mod = ast.Interactive([node])
2704 2713 code = self.compile(mod, cell_name, "single")
2705 2714 if self.run_code(code):
2706 2715 return True
2707 2716
2708 2717 # Flush softspace
2709 2718 if softspace(sys.stdout, 0):
2710 2719 print()
2711 2720
2712 2721 except:
2713 2722 # It's possible to have exceptions raised here, typically by
2714 2723 # compilation of odd code (such as a naked 'return' outside a
2715 2724 # function) that did parse but isn't valid. Typically the exception
2716 2725 # is a SyntaxError, but it's safest just to catch anything and show
2717 2726 # the user a traceback.
2718 2727
2719 2728 # We do only one try/except outside the loop to minimize the impact
2720 2729 # on runtime, and also because if any node in the node list is
2721 2730 # broken, we should stop execution completely.
2722 2731 self.showtraceback()
2723 2732
2724 2733 return False
2725 2734
2726 2735 def run_code(self, code_obj):
2727 2736 """Execute a code object.
2728 2737
2729 2738 When an exception occurs, self.showtraceback() is called to display a
2730 2739 traceback.
2731 2740
2732 2741 Parameters
2733 2742 ----------
2734 2743 code_obj : code object
2735 2744 A compiled code object, to be executed
2736 2745
2737 2746 Returns
2738 2747 -------
2739 2748 False : successful execution.
2740 2749 True : an error occurred.
2741 2750 """
2742 2751
2743 2752 # Set our own excepthook in case the user code tries to call it
2744 2753 # directly, so that the IPython crash handler doesn't get triggered
2745 2754 old_excepthook,sys.excepthook = sys.excepthook, self.excepthook
2746 2755
2747 2756 # we save the original sys.excepthook in the instance, in case config
2748 2757 # code (such as magics) needs access to it.
2749 2758 self.sys_excepthook = old_excepthook
2750 2759 outflag = 1 # happens in more places, so it's easier as default
2751 2760 try:
2752 2761 try:
2753 2762 self.hooks.pre_run_code_hook()
2754 2763 #rprint('Running code', repr(code_obj)) # dbg
2755 2764 exec code_obj in self.user_global_ns, self.user_ns
2756 2765 finally:
2757 2766 # Reset our crash handler in place
2758 2767 sys.excepthook = old_excepthook
2759 2768 except SystemExit:
2760 2769 self.showtraceback(exception_only=True)
2761 2770 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
2762 2771 except self.custom_exceptions:
2763 2772 etype,value,tb = sys.exc_info()
2764 2773 self.CustomTB(etype,value,tb)
2765 2774 except:
2766 2775 self.showtraceback()
2767 2776 else:
2768 2777 outflag = 0
2769 2778 return outflag
2770 2779
2771 2780 # For backwards compatibility
2772 2781 runcode = run_code
2773 2782
2774 2783 #-------------------------------------------------------------------------
2775 2784 # Things related to GUI support and pylab
2776 2785 #-------------------------------------------------------------------------
2777 2786
2778 2787 def enable_gui(self, gui=None):
2779 2788 raise NotImplementedError('Implement enable_gui in a subclass')
2780 2789
2781 2790 def enable_pylab(self, gui=None, import_all=True):
2782 2791 """Activate pylab support at runtime.
2783 2792
2784 2793 This turns on support for matplotlib, preloads into the interactive
2785 2794 namespace all of numpy and pylab, and configures IPython to correctly
2786 2795 interact with the GUI event loop. The GUI backend to be used can be
2787 2796 optionally selected with the optional :param:`gui` argument.
2788 2797
2789 2798 Parameters
2790 2799 ----------
2791 2800 gui : optional, string
2792 2801
2793 2802 If given, dictates the choice of matplotlib GUI backend to use
2794 2803 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
2795 2804 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
2796 2805 matplotlib (as dictated by the matplotlib build-time options plus the
2797 2806 user's matplotlibrc configuration file). Note that not all backends
2798 2807 make sense in all contexts, for example a terminal ipython can't
2799 2808 display figures inline.
2800 2809 """
2801 2810 from IPython.core.pylabtools import mpl_runner
2802 2811 # We want to prevent the loading of pylab to pollute the user's
2803 2812 # namespace as shown by the %who* magics, so we execute the activation
2804 2813 # code in an empty namespace, and we update *both* user_ns and
2805 2814 # user_ns_hidden with this information.
2806 2815 ns = {}
2807 2816 try:
2808 2817 gui = pylab_activate(ns, gui, import_all, self)
2809 2818 except KeyError:
2810 2819 error("Backend %r not supported" % gui)
2811 2820 return
2812 2821 self.user_ns.update(ns)
2813 2822 self.user_ns_hidden.update(ns)
2814 2823 # Now we must activate the gui pylab wants to use, and fix %run to take
2815 2824 # plot updates into account
2816 2825 self.enable_gui(gui)
2817 2826 self.magics_manager.registry['ExecutionMagics'].default_runner = \
2818 2827 mpl_runner(self.safe_execfile)
2819 2828
2820 2829 #-------------------------------------------------------------------------
2821 2830 # Utilities
2822 2831 #-------------------------------------------------------------------------
2823 2832
2824 2833 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
2825 2834 """Expand python variables in a string.
2826 2835
2827 2836 The depth argument indicates how many frames above the caller should
2828 2837 be walked to look for the local namespace where to expand variables.
2829 2838
2830 2839 The global namespace for expansion is always the user's interactive
2831 2840 namespace.
2832 2841 """
2833 2842 ns = self.user_ns.copy()
2834 2843 ns.update(sys._getframe(depth+1).f_locals)
2835 2844 ns.pop('self', None)
2836 2845 try:
2837 2846 cmd = formatter.format(cmd, **ns)
2838 2847 except Exception:
2839 2848 # if formatter couldn't format, just let it go untransformed
2840 2849 pass
2841 2850 return cmd
2842 2851
2843 2852 def mktempfile(self, data=None, prefix='ipython_edit_'):
2844 2853 """Make a new tempfile and return its filename.
2845 2854
2846 2855 This makes a call to tempfile.mktemp, but it registers the created
2847 2856 filename internally so ipython cleans it up at exit time.
2848 2857
2849 2858 Optional inputs:
2850 2859
2851 2860 - data(None): if data is given, it gets written out to the temp file
2852 2861 immediately, and the file is closed again."""
2853 2862
2854 2863 filename = tempfile.mktemp('.py', prefix)
2855 2864 self.tempfiles.append(filename)
2856 2865
2857 2866 if data:
2858 2867 tmp_file = open(filename,'w')
2859 2868 tmp_file.write(data)
2860 2869 tmp_file.close()
2861 2870 return filename
2862 2871
2863 2872 # TODO: This should be removed when Term is refactored.
2864 2873 def write(self,data):
2865 2874 """Write a string to the default output"""
2866 2875 io.stdout.write(data)
2867 2876
2868 2877 # TODO: This should be removed when Term is refactored.
2869 2878 def write_err(self,data):
2870 2879 """Write a string to the default error output"""
2871 2880 io.stderr.write(data)
2872 2881
2873 2882 def ask_yes_no(self, prompt, default=None):
2874 2883 if self.quiet:
2875 2884 return True
2876 2885 return ask_yes_no(prompt,default)
2877 2886
2878 2887 def show_usage(self):
2879 2888 """Show a usage message"""
2880 2889 page.page(IPython.core.usage.interactive_usage)
2881 2890
2882 2891 def extract_input_lines(self, range_str, raw=False):
2883 2892 """Return as a string a set of input history slices.
2884 2893
2885 2894 Parameters
2886 2895 ----------
2887 2896 range_str : string
2888 2897 The set of slices is given as a string, like "~5/6-~4/2 4:8 9",
2889 2898 since this function is for use by magic functions which get their
2890 2899 arguments as strings. The number before the / is the session
2891 2900 number: ~n goes n back from the current session.
2892 2901
2893 2902 Optional Parameters:
2894 2903 - raw(False): by default, the processed input is used. If this is
2895 2904 true, the raw input history is used instead.
2896 2905
2897 2906 Note that slices can be called with two notations:
2898 2907
2899 2908 N:M -> standard python form, means including items N...(M-1).
2900 2909
2901 2910 N-M -> include items N..M (closed endpoint)."""
2902 2911 lines = self.history_manager.get_range_by_str(range_str, raw=raw)
2903 2912 return "\n".join(x for _, _, x in lines)
2904 2913
2905 2914 def find_user_code(self, target, raw=True, py_only=False):
2906 2915 """Get a code string from history, file, url, or a string or macro.
2907 2916
2908 2917 This is mainly used by magic functions.
2909 2918
2910 2919 Parameters
2911 2920 ----------
2912 2921
2913 2922 target : str
2914 2923
2915 2924 A string specifying code to retrieve. This will be tried respectively
2916 2925 as: ranges of input history (see %history for syntax), url,
2917 2926 correspnding .py file, filename, or an expression evaluating to a
2918 2927 string or Macro in the user namespace.
2919 2928
2920 2929 raw : bool
2921 2930 If true (default), retrieve raw history. Has no effect on the other
2922 2931 retrieval mechanisms.
2923 2932
2924 2933 py_only : bool (default False)
2925 2934 Only try to fetch python code, do not try alternative methods to decode file
2926 2935 if unicode fails.
2927 2936
2928 2937 Returns
2929 2938 -------
2930 2939 A string of code.
2931 2940
2932 2941 ValueError is raised if nothing is found, and TypeError if it evaluates
2933 2942 to an object of another type. In each case, .args[0] is a printable
2934 2943 message.
2935 2944 """
2936 2945 code = self.extract_input_lines(target, raw=raw) # Grab history
2937 2946 if code:
2938 2947 return code
2939 2948 utarget = unquote_filename(target)
2940 2949 try:
2941 2950 if utarget.startswith(('http://', 'https://')):
2942 2951 return openpy.read_py_url(utarget, skip_encoding_cookie=True)
2943 2952 except UnicodeDecodeError:
2944 2953 if not py_only :
2945 2954 response = urllib.urlopen(target)
2946 2955 return response.read().decode('latin1')
2947 2956 raise ValueError(("'%s' seem to be unreadable.") % utarget)
2948 2957
2949 2958 potential_target = [target]
2950 2959 try :
2951 2960 potential_target.insert(0,get_py_filename(target))
2952 2961 except IOError:
2953 2962 pass
2954 2963
2955 2964 for tgt in potential_target :
2956 2965 if os.path.isfile(tgt): # Read file
2957 2966 try :
2958 2967 return openpy.read_py_file(tgt, skip_encoding_cookie=True)
2959 2968 except UnicodeDecodeError :
2960 2969 if not py_only :
2961 2970 with io_open(tgt,'r', encoding='latin1') as f :
2962 2971 return f.read()
2963 2972 raise ValueError(("'%s' seem to be unreadable.") % target)
2964 2973
2965 2974 try: # User namespace
2966 2975 codeobj = eval(target, self.user_ns)
2967 2976 except Exception:
2968 2977 raise ValueError(("'%s' was not found in history, as a file, url, "
2969 2978 "nor in the user namespace.") % target)
2970 2979 if isinstance(codeobj, basestring):
2971 2980 return codeobj
2972 2981 elif isinstance(codeobj, Macro):
2973 2982 return codeobj.value
2974 2983
2975 2984 raise TypeError("%s is neither a string nor a macro." % target,
2976 2985 codeobj)
2977 2986
2978 2987 #-------------------------------------------------------------------------
2979 2988 # Things related to IPython exiting
2980 2989 #-------------------------------------------------------------------------
2981 2990 def atexit_operations(self):
2982 2991 """This will be executed at the time of exit.
2983 2992
2984 2993 Cleanup operations and saving of persistent data that is done
2985 2994 unconditionally by IPython should be performed here.
2986 2995
2987 2996 For things that may depend on startup flags or platform specifics (such
2988 2997 as having readline or not), register a separate atexit function in the
2989 2998 code that has the appropriate information, rather than trying to
2990 2999 clutter
2991 3000 """
2992 3001 # Close the history session (this stores the end time and line count)
2993 3002 # this must be *before* the tempfile cleanup, in case of temporary
2994 3003 # history db
2995 3004 self.history_manager.end_session()
2996 3005
2997 3006 # Cleanup all tempfiles left around
2998 3007 for tfile in self.tempfiles:
2999 3008 try:
3000 3009 os.unlink(tfile)
3001 3010 except OSError:
3002 3011 pass
3003 3012
3004 3013 # Clear all user namespaces to release all references cleanly.
3005 3014 self.reset(new_session=False)
3006 3015
3007 3016 # Run user hooks
3008 3017 self.hooks.shutdown_hook()
3009 3018
3010 3019 def cleanup(self):
3011 3020 self.restore_sys_module_state()
3012 3021
3013 3022
3014 3023 class InteractiveShellABC(object):
3015 3024 """An abstract base class for InteractiveShell."""
3016 3025 __metaclass__ = abc.ABCMeta
3017 3026
3018 3027 InteractiveShellABC.register(InteractiveShell)
@@ -1,686 +1,690
1 1 """AsyncResult objects for the client
2 2
3 3 Authors:
4 4
5 5 * MinRK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2010-2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import print_function
19 19
20 20 import sys
21 21 import time
22 22 from datetime import datetime
23 23
24 24 from zmq import MessageTracker
25 25
26 26 from IPython.core.display import clear_output, display, display_pretty
27 27 from IPython.external.decorator import decorator
28 28 from IPython.parallel import error
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Functions
32 32 #-----------------------------------------------------------------------------
33 33
34 34 def _total_seconds(td):
35 35 """timedelta.total_seconds was added in 2.7"""
36 36 try:
37 37 # Python >= 2.7
38 38 return td.total_seconds()
39 39 except AttributeError:
40 40 # Python 2.6
41 41 return 1e-6 * (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6)
42 42
43 43 def _raw_text(s):
44 44 display_pretty(s, raw=True)
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Classes
48 48 #-----------------------------------------------------------------------------
49 49
50 50 # global empty tracker that's always done:
51 51 finished_tracker = MessageTracker()
52 52
53 53 @decorator
54 54 def check_ready(f, self, *args, **kwargs):
55 55 """Call spin() to sync state prior to calling the method."""
56 56 self.wait(0)
57 57 if not self._ready:
58 58 raise error.TimeoutError("result not ready")
59 59 return f(self, *args, **kwargs)
60 60
61 61 class AsyncResult(object):
62 62 """Class for representing results of non-blocking calls.
63 63
64 64 Provides the same interface as :py:class:`multiprocessing.pool.AsyncResult`.
65 65 """
66 66
67 67 msg_ids = None
68 68 _targets = None
69 69 _tracker = None
70 70 _single_result = False
71 71
72 72 def __init__(self, client, msg_ids, fname='unknown', targets=None, tracker=None):
73 73 if isinstance(msg_ids, basestring):
74 74 # always a list
75 75 msg_ids = [msg_ids]
76 76 if tracker is None:
77 77 # default to always done
78 78 tracker = finished_tracker
79 79 self._client = client
80 80 self.msg_ids = msg_ids
81 81 self._fname=fname
82 82 self._targets = targets
83 83 self._tracker = tracker
84 84 self._ready = False
85 85 self._success = None
86 self._metadata = None
86 self._metadata = [ self._client.metadata.get(id) for id in self.msg_ids ]
87 87 if len(msg_ids) == 1:
88 88 self._single_result = not isinstance(targets, (list, tuple))
89 89 else:
90 90 self._single_result = False
91 91
92 92 def __repr__(self):
93 93 if self._ready:
94 94 return "<%s: finished>"%(self.__class__.__name__)
95 95 else:
96 96 return "<%s: %s>"%(self.__class__.__name__,self._fname)
97 97
98 98
99 99 def _reconstruct_result(self, res):
100 100 """Reconstruct our result from actual result list (always a list)
101 101
102 102 Override me in subclasses for turning a list of results
103 103 into the expected form.
104 104 """
105 105 if self._single_result:
106 106 return res[0]
107 107 else:
108 108 return res
109 109
110 110 def get(self, timeout=-1):
111 111 """Return the result when it arrives.
112 112
113 113 If `timeout` is not ``None`` and the result does not arrive within
114 114 `timeout` seconds then ``TimeoutError`` is raised. If the
115 115 remote call raised an exception then that exception will be reraised
116 116 by get() inside a `RemoteError`.
117 117 """
118 118 if not self.ready():
119 119 self.wait(timeout)
120 120
121 121 if self._ready:
122 122 if self._success:
123 123 return self._result
124 124 else:
125 125 raise self._exception
126 126 else:
127 127 raise error.TimeoutError("Result not ready.")
128 128
129 def _check_ready(self):
130 if not self.ready():
131 raise error.TimeoutError("Result not ready.")
132
129 133 def ready(self):
130 134 """Return whether the call has completed."""
131 135 if not self._ready:
132 136 self.wait(0)
133 137 return self._ready
134 138
135 139 def wait(self, timeout=-1):
136 140 """Wait until the result is available or until `timeout` seconds pass.
137 141
138 142 This method always returns None.
139 143 """
140 144 if self._ready:
141 145 return
142 146 self._ready = self._client.wait(self.msg_ids, timeout)
143 147 if self._ready:
144 148 try:
145 149 results = map(self._client.results.get, self.msg_ids)
146 150 self._result = results
147 151 if self._single_result:
148 152 r = results[0]
149 153 if isinstance(r, Exception):
150 154 raise r
151 155 else:
152 156 results = error.collect_exceptions(results, self._fname)
153 157 self._result = self._reconstruct_result(results)
154 158 except Exception as e:
155 159 self._exception = e
156 160 self._success = False
157 161 else:
158 162 self._success = True
159 163 finally:
160 self._metadata = map(self._client.metadata.get, self.msg_ids)
161 self._wait_for_outputs(10)
162 164
165 self._wait_for_outputs(10)
163 166
164 167
165 168 def successful(self):
166 169 """Return whether the call completed without raising an exception.
167 170
168 171 Will raise ``AssertionError`` if the result is not ready.
169 172 """
170 173 assert self.ready()
171 174 return self._success
172 175
173 176 #----------------------------------------------------------------
174 177 # Extra methods not in mp.pool.AsyncResult
175 178 #----------------------------------------------------------------
176 179
177 180 def get_dict(self, timeout=-1):
178 181 """Get the results as a dict, keyed by engine_id.
179 182
180 183 timeout behavior is described in `get()`.
181 184 """
182 185
183 186 results = self.get(timeout)
184 187 engine_ids = [ md['engine_id'] for md in self._metadata ]
185 188 bycount = sorted(engine_ids, key=lambda k: engine_ids.count(k))
186 189 maxcount = bycount.count(bycount[-1])
187 190 if maxcount > 1:
188 191 raise ValueError("Cannot build dict, %i jobs ran on engine #%i"%(
189 192 maxcount, bycount[-1]))
190 193
191 194 return dict(zip(engine_ids,results))
192 195
193 196 @property
194 197 def result(self):
195 """result property wrapper for `get(timeout=0)`."""
198 """result property wrapper for `get(timeout=-1)`."""
196 199 return self.get()
197 200
198 201 # abbreviated alias:
199 202 r = result
200 203
201 204 @property
202 @check_ready
203 205 def metadata(self):
204 206 """property for accessing execution metadata."""
205 207 if self._single_result:
206 208 return self._metadata[0]
207 209 else:
208 210 return self._metadata
209 211
210 212 @property
211 213 def result_dict(self):
212 214 """result property as a dict."""
213 215 return self.get_dict()
214 216
215 217 def __dict__(self):
216 218 return self.get_dict(0)
217 219
218 220 def abort(self):
219 221 """abort my tasks."""
220 222 assert not self.ready(), "Can't abort, I am already done!"
221 223 return self._client.abort(self.msg_ids, targets=self._targets, block=True)
222 224
223 225 @property
224 226 def sent(self):
225 227 """check whether my messages have been sent."""
226 228 return self._tracker.done
227 229
228 230 def wait_for_send(self, timeout=-1):
229 231 """wait for pyzmq send to complete.
230 232
231 233 This is necessary when sending arrays that you intend to edit in-place.
232 234 `timeout` is in seconds, and will raise TimeoutError if it is reached
233 235 before the send completes.
234 236 """
235 237 return self._tracker.wait(timeout)
236 238
237 239 #-------------------------------------
238 240 # dict-access
239 241 #-------------------------------------
240 242
241 @check_ready
242 243 def __getitem__(self, key):
243 244 """getitem returns result value(s) if keyed by int/slice, or metadata if key is str.
244 245 """
245 246 if isinstance(key, int):
247 self._check_ready()
246 248 return error.collect_exceptions([self._result[key]], self._fname)[0]
247 249 elif isinstance(key, slice):
250 self._check_ready()
248 251 return error.collect_exceptions(self._result[key], self._fname)
249 252 elif isinstance(key, basestring):
253 # metadata proxy *does not* require that results are done
250 254 values = [ md[key] for md in self._metadata ]
251 255 if self._single_result:
252 256 return values[0]
253 257 else:
254 258 return values
255 259 else:
256 260 raise TypeError("Invalid key type %r, must be 'int','slice', or 'str'"%type(key))
257 261
258 262 def __getattr__(self, key):
259 263 """getattr maps to getitem for convenient attr access to metadata."""
260 264 try:
261 265 return self.__getitem__(key)
262 266 except (error.TimeoutError, KeyError):
263 267 raise AttributeError("%r object has no attribute %r"%(
264 268 self.__class__.__name__, key))
265 269
266 270 # asynchronous iterator:
267 271 def __iter__(self):
268 272 if self._single_result:
269 273 raise TypeError("AsyncResults with a single result are not iterable.")
270 274 try:
271 275 rlist = self.get(0)
272 276 except error.TimeoutError:
273 277 # wait for each result individually
274 278 for msg_id in self.msg_ids:
275 279 ar = AsyncResult(self._client, msg_id, self._fname)
276 280 yield ar.get()
277 281 else:
278 282 # already done
279 283 for r in rlist:
280 284 yield r
281 285
282 286 def __len__(self):
283 287 return len(self.msg_ids)
284 288
285 289 #-------------------------------------
286 290 # Sugar methods and attributes
287 291 #-------------------------------------
288 292
289 293 def timedelta(self, start, end, start_key=min, end_key=max):
290 294 """compute the difference between two sets of timestamps
291 295
292 296 The default behavior is to use the earliest of the first
293 297 and the latest of the second list, but this can be changed
294 298 by passing a different
295 299
296 300 Parameters
297 301 ----------
298 302
299 303 start : one or more datetime objects (e.g. ar.submitted)
300 304 end : one or more datetime objects (e.g. ar.received)
301 305 start_key : callable
302 306 Function to call on `start` to extract the relevant
303 307 entry [defalt: min]
304 308 end_key : callable
305 309 Function to call on `end` to extract the relevant
306 310 entry [default: max]
307 311
308 312 Returns
309 313 -------
310 314
311 315 dt : float
312 316 The time elapsed (in seconds) between the two selected timestamps.
313 317 """
314 318 if not isinstance(start, datetime):
315 319 # handle single_result AsyncResults, where ar.stamp is single object,
316 320 # not a list
317 321 start = start_key(start)
318 322 if not isinstance(end, datetime):
319 323 # handle single_result AsyncResults, where ar.stamp is single object,
320 324 # not a list
321 325 end = end_key(end)
322 326 return _total_seconds(end - start)
323 327
324 328 @property
325 329 def progress(self):
326 330 """the number of tasks which have been completed at this point.
327 331
328 332 Fractional progress would be given by 1.0 * ar.progress / len(ar)
329 333 """
330 334 self.wait(0)
331 335 return len(self) - len(set(self.msg_ids).intersection(self._client.outstanding))
332 336
333 337 @property
334 338 def elapsed(self):
335 339 """elapsed time since initial submission"""
336 340 if self.ready():
337 341 return self.wall_time
338 342
339 343 now = submitted = datetime.now()
340 344 for msg_id in self.msg_ids:
341 345 if msg_id in self._client.metadata:
342 346 stamp = self._client.metadata[msg_id]['submitted']
343 347 if stamp and stamp < submitted:
344 348 submitted = stamp
345 349 return _total_seconds(now-submitted)
346 350
347 351 @property
348 352 @check_ready
349 353 def serial_time(self):
350 354 """serial computation time of a parallel calculation
351 355
352 356 Computed as the sum of (completed-started) of each task
353 357 """
354 358 t = 0
355 359 for md in self._metadata:
356 360 t += _total_seconds(md['completed'] - md['started'])
357 361 return t
358 362
359 363 @property
360 364 @check_ready
361 365 def wall_time(self):
362 366 """actual computation time of a parallel calculation
363 367
364 368 Computed as the time between the latest `received` stamp
365 369 and the earliest `submitted`.
366 370
367 371 Only reliable if Client was spinning/waiting when the task finished, because
368 372 the `received` timestamp is created when a result is pulled off of the zmq queue,
369 373 which happens as a result of `client.spin()`.
370 374
371 375 For similar comparison of other timestamp pairs, check out AsyncResult.timedelta.
372 376
373 377 """
374 378 return self.timedelta(self.submitted, self.received)
375 379
376 380 def wait_interactive(self, interval=1., timeout=None):
377 381 """interactive wait, printing progress at regular intervals"""
378 382 N = len(self)
379 383 tic = time.time()
380 384 while not self.ready() and (timeout is None or time.time() - tic <= timeout):
381 385 self.wait(interval)
382 386 clear_output()
383 387 print("%4i/%i tasks finished after %4i s" % (self.progress, N, self.elapsed), end="")
384 388 sys.stdout.flush()
385 389 print()
386 390 print("done")
387 391
388 392 def _republish_displaypub(self, content, eid):
389 393 """republish individual displaypub content dicts"""
390 394 try:
391 395 ip = get_ipython()
392 396 except NameError:
393 397 # displaypub is meaningless outside IPython
394 398 return
395 399 md = content['metadata'] or {}
396 400 md['engine'] = eid
397 401 ip.display_pub.publish(content['source'], content['data'], md)
398 402
399 403 def _display_stream(self, text, prefix='', file=None):
400 404 if not text:
401 405 # nothing to display
402 406 return
403 407 if file is None:
404 408 file = sys.stdout
405 409 end = '' if text.endswith('\n') else '\n'
406 410
407 411 multiline = text.count('\n') > int(text.endswith('\n'))
408 412 if prefix and multiline and not text.startswith('\n'):
409 413 prefix = prefix + '\n'
410 414 print("%s%s" % (prefix, text), file=file, end=end)
411 415
412 416
413 417 def _display_single_result(self):
414 418 self._display_stream(self.stdout)
415 419 self._display_stream(self.stderr, file=sys.stderr)
416 420
417 421 try:
418 422 get_ipython()
419 423 except NameError:
420 424 # displaypub is meaningless outside IPython
421 425 return
422 426
423 427 for output in self.outputs:
424 428 self._republish_displaypub(output, self.engine_id)
425 429
426 430 if self.pyout is not None:
427 431 display(self.get())
428 432
429 433 def _wait_for_outputs(self, timeout=-1):
430 434 """wait for the 'status=idle' message that indicates we have all outputs
431 435 """
432 436 if not self._success:
433 437 # don't wait on errors
434 438 return
435 439 tic = time.time()
436 440 while not all(md['outputs_ready'] for md in self._metadata):
437 441 time.sleep(0.01)
438 442 self._client._flush_iopub(self._client._iopub_socket)
439 443 if timeout >= 0 and time.time() > tic + timeout:
440 444 break
441 445
442 446 @check_ready
443 447 def display_outputs(self, groupby="type"):
444 448 """republish the outputs of the computation
445 449
446 450 Parameters
447 451 ----------
448 452
449 453 groupby : str [default: type]
450 454 if 'type':
451 455 Group outputs by type (show all stdout, then all stderr, etc.):
452 456
453 457 [stdout:1] foo
454 458 [stdout:2] foo
455 459 [stderr:1] bar
456 460 [stderr:2] bar
457 461 if 'engine':
458 462 Display outputs for each engine before moving on to the next:
459 463
460 464 [stdout:1] foo
461 465 [stderr:1] bar
462 466 [stdout:2] foo
463 467 [stderr:2] bar
464 468
465 469 if 'order':
466 470 Like 'type', but further collate individual displaypub
467 471 outputs. This is meant for cases of each command producing
468 472 several plots, and you would like to see all of the first
469 473 plots together, then all of the second plots, and so on.
470 474 """
471 475 if self._single_result:
472 476 self._display_single_result()
473 477 return
474 478
475 479 stdouts = self.stdout
476 480 stderrs = self.stderr
477 481 pyouts = self.pyout
478 482 output_lists = self.outputs
479 483 results = self.get()
480 484
481 485 targets = self.engine_id
482 486
483 487 if groupby == "engine":
484 488 for eid,stdout,stderr,outputs,r,pyout in zip(
485 489 targets, stdouts, stderrs, output_lists, results, pyouts
486 490 ):
487 491 self._display_stream(stdout, '[stdout:%i] ' % eid)
488 492 self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr)
489 493
490 494 try:
491 495 get_ipython()
492 496 except NameError:
493 497 # displaypub is meaningless outside IPython
494 498 return
495 499
496 500 if outputs or pyout is not None:
497 501 _raw_text('[output:%i]' % eid)
498 502
499 503 for output in outputs:
500 504 self._republish_displaypub(output, eid)
501 505
502 506 if pyout is not None:
503 507 display(r)
504 508
505 509 elif groupby in ('type', 'order'):
506 510 # republish stdout:
507 511 for eid,stdout in zip(targets, stdouts):
508 512 self._display_stream(stdout, '[stdout:%i] ' % eid)
509 513
510 514 # republish stderr:
511 515 for eid,stderr in zip(targets, stderrs):
512 516 self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr)
513 517
514 518 try:
515 519 get_ipython()
516 520 except NameError:
517 521 # displaypub is meaningless outside IPython
518 522 return
519 523
520 524 if groupby == 'order':
521 525 output_dict = dict((eid, outputs) for eid,outputs in zip(targets, output_lists))
522 526 N = max(len(outputs) for outputs in output_lists)
523 527 for i in range(N):
524 528 for eid in targets:
525 529 outputs = output_dict[eid]
526 530 if len(outputs) >= N:
527 531 _raw_text('[output:%i]' % eid)
528 532 self._republish_displaypub(outputs[i], eid)
529 533 else:
530 534 # republish displaypub output
531 535 for eid,outputs in zip(targets, output_lists):
532 536 if outputs:
533 537 _raw_text('[output:%i]' % eid)
534 538 for output in outputs:
535 539 self._republish_displaypub(output, eid)
536 540
537 541 # finally, add pyout:
538 542 for eid,r,pyout in zip(targets, results, pyouts):
539 543 if pyout is not None:
540 544 display(r)
541 545
542 546 else:
543 547 raise ValueError("groupby must be one of 'type', 'engine', 'collate', not %r" % groupby)
544 548
545 549
546 550
547 551
548 552 class AsyncMapResult(AsyncResult):
549 553 """Class for representing results of non-blocking gathers.
550 554
551 555 This will properly reconstruct the gather.
552 556
553 557 This class is iterable at any time, and will wait on results as they come.
554 558
555 559 If ordered=False, then the first results to arrive will come first, otherwise
556 560 results will be yielded in the order they were submitted.
557 561
558 562 """
559 563
560 564 def __init__(self, client, msg_ids, mapObject, fname='', ordered=True):
561 565 AsyncResult.__init__(self, client, msg_ids, fname=fname)
562 566 self._mapObject = mapObject
563 567 self._single_result = False
564 568 self.ordered = ordered
565 569
566 570 def _reconstruct_result(self, res):
567 571 """Perform the gather on the actual results."""
568 572 return self._mapObject.joinPartitions(res)
569 573
570 574 # asynchronous iterator:
571 575 def __iter__(self):
572 576 it = self._ordered_iter if self.ordered else self._unordered_iter
573 577 for r in it():
574 578 yield r
575 579
576 580 # asynchronous ordered iterator:
577 581 def _ordered_iter(self):
578 582 """iterator for results *as they arrive*, preserving submission order."""
579 583 try:
580 584 rlist = self.get(0)
581 585 except error.TimeoutError:
582 586 # wait for each result individually
583 587 for msg_id in self.msg_ids:
584 588 ar = AsyncResult(self._client, msg_id, self._fname)
585 589 rlist = ar.get()
586 590 try:
587 591 for r in rlist:
588 592 yield r
589 593 except TypeError:
590 594 # flattened, not a list
591 595 # this could get broken by flattened data that returns iterables
592 596 # but most calls to map do not expose the `flatten` argument
593 597 yield rlist
594 598 else:
595 599 # already done
596 600 for r in rlist:
597 601 yield r
598 602
599 603 # asynchronous unordered iterator:
600 604 def _unordered_iter(self):
601 605 """iterator for results *as they arrive*, on FCFS basis, ignoring submission order."""
602 606 try:
603 607 rlist = self.get(0)
604 608 except error.TimeoutError:
605 609 pending = set(self.msg_ids)
606 610 while pending:
607 611 try:
608 612 self._client.wait(pending, 1e-3)
609 613 except error.TimeoutError:
610 614 # ignore timeout error, because that only means
611 615 # *some* jobs are outstanding
612 616 pass
613 617 # update ready set with those no longer outstanding:
614 618 ready = pending.difference(self._client.outstanding)
615 619 # update pending to exclude those that are finished
616 620 pending = pending.difference(ready)
617 621 while ready:
618 622 msg_id = ready.pop()
619 623 ar = AsyncResult(self._client, msg_id, self._fname)
620 624 rlist = ar.get()
621 625 try:
622 626 for r in rlist:
623 627 yield r
624 628 except TypeError:
625 629 # flattened, not a list
626 630 # this could get broken by flattened data that returns iterables
627 631 # but most calls to map do not expose the `flatten` argument
628 632 yield rlist
629 633 else:
630 634 # already done
631 635 for r in rlist:
632 636 yield r
633 637
634 638
635 639 class AsyncHubResult(AsyncResult):
636 640 """Class to wrap pending results that must be requested from the Hub.
637 641
638 642 Note that waiting/polling on these objects requires polling the Hubover the network,
639 643 so use `AsyncHubResult.wait()` sparingly.
640 644 """
641 645
642 646 def _wait_for_outputs(self, timeout=None):
643 647 """no-op, because HubResults are never incomplete"""
644 648 return
645 649
646 650 def wait(self, timeout=-1):
647 651 """wait for result to complete."""
648 652 start = time.time()
649 653 if self._ready:
650 654 return
651 655 local_ids = filter(lambda msg_id: msg_id in self._client.outstanding, self.msg_ids)
652 656 local_ready = self._client.wait(local_ids, timeout)
653 657 if local_ready:
654 658 remote_ids = filter(lambda msg_id: msg_id not in self._client.results, self.msg_ids)
655 659 if not remote_ids:
656 660 self._ready = True
657 661 else:
658 662 rdict = self._client.result_status(remote_ids, status_only=False)
659 663 pending = rdict['pending']
660 664 while pending and (timeout < 0 or time.time() < start+timeout):
661 665 rdict = self._client.result_status(remote_ids, status_only=False)
662 666 pending = rdict['pending']
663 667 if pending:
664 668 time.sleep(0.1)
665 669 if not pending:
666 670 self._ready = True
667 671 if self._ready:
668 672 try:
669 673 results = map(self._client.results.get, self.msg_ids)
670 674 self._result = results
671 675 if self._single_result:
672 676 r = results[0]
673 677 if isinstance(r, Exception):
674 678 raise r
675 679 else:
676 680 results = error.collect_exceptions(results, self._fname)
677 681 self._result = self._reconstruct_result(results)
678 682 except Exception as e:
679 683 self._exception = e
680 684 self._success = False
681 685 else:
682 686 self._success = True
683 687 finally:
684 688 self._metadata = map(self._client.metadata.get, self.msg_ids)
685 689
686 690 __all__ = ['AsyncResult', 'AsyncMapResult', 'AsyncHubResult']
@@ -1,1713 +1,1718
1 1 """A semi-synchronous Client for the ZMQ cluster
2 2
3 3 Authors:
4 4
5 5 * MinRK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2010-2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import os
19 19 import json
20 20 import sys
21 21 from threading import Thread, Event
22 22 import time
23 23 import warnings
24 24 from datetime import datetime
25 25 from getpass import getpass
26 26 from pprint import pprint
27 27
28 28 pjoin = os.path.join
29 29
30 30 import zmq
31 31 # from zmq.eventloop import ioloop, zmqstream
32 32
33 33 from IPython.config.configurable import MultipleInstanceError
34 34 from IPython.core.application import BaseIPythonApplication
35 35 from IPython.core.profiledir import ProfileDir, ProfileDirError
36 36
37 37 from IPython.utils.coloransi import TermColors
38 38 from IPython.utils.jsonutil import rekey
39 39 from IPython.utils.localinterfaces import LOCAL_IPS
40 40 from IPython.utils.path import get_ipython_dir
41 41 from IPython.utils.py3compat import cast_bytes
42 42 from IPython.utils.traitlets import (HasTraits, Integer, Instance, Unicode,
43 43 Dict, List, Bool, Set, Any)
44 44 from IPython.external.decorator import decorator
45 45 from IPython.external.ssh import tunnel
46 46
47 47 from IPython.parallel import Reference
48 48 from IPython.parallel import error
49 49 from IPython.parallel import util
50 50
51 51 from IPython.zmq.session import Session, Message
52 from IPython.zmq import serialize
52 53
53 54 from .asyncresult import AsyncResult, AsyncHubResult
54 55 from .view import DirectView, LoadBalancedView
55 56
56 57 if sys.version_info[0] >= 3:
57 58 # xrange is used in a couple 'isinstance' tests in py2
58 59 # should be just 'range' in 3k
59 60 xrange = range
60 61
61 62 #--------------------------------------------------------------------------
62 63 # Decorators for Client methods
63 64 #--------------------------------------------------------------------------
64 65
65 66 @decorator
66 67 def spin_first(f, self, *args, **kwargs):
67 68 """Call spin() to sync state prior to calling the method."""
68 69 self.spin()
69 70 return f(self, *args, **kwargs)
70 71
71 72
72 73 #--------------------------------------------------------------------------
73 74 # Classes
74 75 #--------------------------------------------------------------------------
75 76
76 77
77 78 class ExecuteReply(object):
78 79 """wrapper for finished Execute results"""
79 80 def __init__(self, msg_id, content, metadata):
80 81 self.msg_id = msg_id
81 82 self._content = content
82 83 self.execution_count = content['execution_count']
83 84 self.metadata = metadata
84 85
85 86 def __getitem__(self, key):
86 87 return self.metadata[key]
87 88
88 89 def __getattr__(self, key):
89 90 if key not in self.metadata:
90 91 raise AttributeError(key)
91 92 return self.metadata[key]
92 93
93 94 def __repr__(self):
94 95 pyout = self.metadata['pyout'] or {'data':{}}
95 96 text_out = pyout['data'].get('text/plain', '')
96 97 if len(text_out) > 32:
97 98 text_out = text_out[:29] + '...'
98 99
99 100 return "<ExecuteReply[%i]: %s>" % (self.execution_count, text_out)
100 101
101 102 def _repr_pretty_(self, p, cycle):
102 103 pyout = self.metadata['pyout'] or {'data':{}}
103 104 text_out = pyout['data'].get('text/plain', '')
104 105
105 106 if not text_out:
106 107 return
107 108
108 109 try:
109 110 ip = get_ipython()
110 111 except NameError:
111 112 colors = "NoColor"
112 113 else:
113 114 colors = ip.colors
114 115
115 116 if colors == "NoColor":
116 117 out = normal = ""
117 118 else:
118 119 out = TermColors.Red
119 120 normal = TermColors.Normal
120 121
121 122 if '\n' in text_out and not text_out.startswith('\n'):
122 123 # add newline for multiline reprs
123 124 text_out = '\n' + text_out
124 125
125 126 p.text(
126 127 out + u'Out[%i:%i]: ' % (
127 128 self.metadata['engine_id'], self.execution_count
128 129 ) + normal + text_out
129 130 )
130 131
131 132 def _repr_html_(self):
132 133 pyout = self.metadata['pyout'] or {'data':{}}
133 134 return pyout['data'].get("text/html")
134 135
135 136 def _repr_latex_(self):
136 137 pyout = self.metadata['pyout'] or {'data':{}}
137 138 return pyout['data'].get("text/latex")
138 139
139 140 def _repr_json_(self):
140 141 pyout = self.metadata['pyout'] or {'data':{}}
141 142 return pyout['data'].get("application/json")
142 143
143 144 def _repr_javascript_(self):
144 145 pyout = self.metadata['pyout'] or {'data':{}}
145 146 return pyout['data'].get("application/javascript")
146 147
147 148 def _repr_png_(self):
148 149 pyout = self.metadata['pyout'] or {'data':{}}
149 150 return pyout['data'].get("image/png")
150 151
151 152 def _repr_jpeg_(self):
152 153 pyout = self.metadata['pyout'] or {'data':{}}
153 154 return pyout['data'].get("image/jpeg")
154 155
155 156 def _repr_svg_(self):
156 157 pyout = self.metadata['pyout'] or {'data':{}}
157 158 return pyout['data'].get("image/svg+xml")
158 159
159 160
160 161 class Metadata(dict):
161 162 """Subclass of dict for initializing metadata values.
162 163
163 164 Attribute access works on keys.
164 165
165 166 These objects have a strict set of keys - errors will raise if you try
166 167 to add new keys.
167 168 """
168 169 def __init__(self, *args, **kwargs):
169 170 dict.__init__(self)
170 171 md = {'msg_id' : None,
171 172 'submitted' : None,
172 173 'started' : None,
173 174 'completed' : None,
174 175 'received' : None,
175 176 'engine_uuid' : None,
176 177 'engine_id' : None,
177 178 'follow' : None,
178 179 'after' : None,
179 180 'status' : None,
180 181
181 182 'pyin' : None,
182 183 'pyout' : None,
183 184 'pyerr' : None,
184 185 'stdout' : '',
185 186 'stderr' : '',
186 187 'outputs' : [],
188 'data': {},
187 189 'outputs_ready' : False,
188 190 }
189 191 self.update(md)
190 192 self.update(dict(*args, **kwargs))
191 193
192 194 def __getattr__(self, key):
193 195 """getattr aliased to getitem"""
194 196 if key in self.iterkeys():
195 197 return self[key]
196 198 else:
197 199 raise AttributeError(key)
198 200
199 201 def __setattr__(self, key, value):
200 202 """setattr aliased to setitem, with strict"""
201 203 if key in self.iterkeys():
202 204 self[key] = value
203 205 else:
204 206 raise AttributeError(key)
205 207
206 208 def __setitem__(self, key, value):
207 209 """strict static key enforcement"""
208 210 if key in self.iterkeys():
209 211 dict.__setitem__(self, key, value)
210 212 else:
211 213 raise KeyError(key)
212 214
213 215
214 216 class Client(HasTraits):
215 217 """A semi-synchronous client to the IPython ZMQ cluster
216 218
217 219 Parameters
218 220 ----------
219 221
220 222 url_file : str/unicode; path to ipcontroller-client.json
221 223 This JSON file should contain all the information needed to connect to a cluster,
222 224 and is likely the only argument needed.
223 225 Connection information for the Hub's registration. If a json connector
224 226 file is given, then likely no further configuration is necessary.
225 227 [Default: use profile]
226 228 profile : bytes
227 229 The name of the Cluster profile to be used to find connector information.
228 230 If run from an IPython application, the default profile will be the same
229 231 as the running application, otherwise it will be 'default'.
230 232 context : zmq.Context
231 233 Pass an existing zmq.Context instance, otherwise the client will create its own.
232 234 debug : bool
233 235 flag for lots of message printing for debug purposes
234 236 timeout : int/float
235 237 time (in seconds) to wait for connection replies from the Hub
236 238 [Default: 10]
237 239
238 240 #-------------- session related args ----------------
239 241
240 242 config : Config object
241 243 If specified, this will be relayed to the Session for configuration
242 244 username : str
243 245 set username for the session object
244 246
245 247 #-------------- ssh related args ----------------
246 248 # These are args for configuring the ssh tunnel to be used
247 249 # credentials are used to forward connections over ssh to the Controller
248 250 # Note that the ip given in `addr` needs to be relative to sshserver
249 251 # The most basic case is to leave addr as pointing to localhost (127.0.0.1),
250 252 # and set sshserver as the same machine the Controller is on. However,
251 253 # the only requirement is that sshserver is able to see the Controller
252 254 # (i.e. is within the same trusted network).
253 255
254 256 sshserver : str
255 257 A string of the form passed to ssh, i.e. 'server.tld' or 'user@server.tld:port'
256 258 If keyfile or password is specified, and this is not, it will default to
257 259 the ip given in addr.
258 260 sshkey : str; path to ssh private key file
259 261 This specifies a key to be used in ssh login, default None.
260 262 Regular default ssh keys will be used without specifying this argument.
261 263 password : str
262 264 Your ssh password to sshserver. Note that if this is left None,
263 265 you will be prompted for it if passwordless key based login is unavailable.
264 266 paramiko : bool
265 267 flag for whether to use paramiko instead of shell ssh for tunneling.
266 268 [default: True on win32, False else]
267 269
268 270
269 271 Attributes
270 272 ----------
271 273
272 274 ids : list of int engine IDs
273 275 requesting the ids attribute always synchronizes
274 276 the registration state. To request ids without synchronization,
275 277 use semi-private _ids attributes.
276 278
277 279 history : list of msg_ids
278 280 a list of msg_ids, keeping track of all the execution
279 281 messages you have submitted in order.
280 282
281 283 outstanding : set of msg_ids
282 284 a set of msg_ids that have been submitted, but whose
283 285 results have not yet been received.
284 286
285 287 results : dict
286 288 a dict of all our results, keyed by msg_id
287 289
288 290 block : bool
289 291 determines default behavior when block not specified
290 292 in execution methods
291 293
292 294 Methods
293 295 -------
294 296
295 297 spin
296 298 flushes incoming results and registration state changes
297 299 control methods spin, and requesting `ids` also ensures up to date
298 300
299 301 wait
300 302 wait on one or more msg_ids
301 303
302 304 execution methods
303 305 apply
304 306 legacy: execute, run
305 307
306 308 data movement
307 309 push, pull, scatter, gather
308 310
309 311 query methods
310 312 queue_status, get_result, purge, result_status
311 313
312 314 control methods
313 315 abort, shutdown
314 316
315 317 """
316 318
317 319
318 320 block = Bool(False)
319 321 outstanding = Set()
320 322 results = Instance('collections.defaultdict', (dict,))
321 323 metadata = Instance('collections.defaultdict', (Metadata,))
322 324 history = List()
323 325 debug = Bool(False)
324 326 _spin_thread = Any()
325 327 _stop_spinning = Any()
326 328
327 329 profile=Unicode()
328 330 def _profile_default(self):
329 331 if BaseIPythonApplication.initialized():
330 332 # an IPython app *might* be running, try to get its profile
331 333 try:
332 334 return BaseIPythonApplication.instance().profile
333 335 except (AttributeError, MultipleInstanceError):
334 336 # could be a *different* subclass of config.Application,
335 337 # which would raise one of these two errors.
336 338 return u'default'
337 339 else:
338 340 return u'default'
339 341
340 342
341 343 _outstanding_dict = Instance('collections.defaultdict', (set,))
342 344 _ids = List()
343 345 _connected=Bool(False)
344 346 _ssh=Bool(False)
345 347 _context = Instance('zmq.Context')
346 348 _config = Dict()
347 349 _engines=Instance(util.ReverseDict, (), {})
348 350 # _hub_socket=Instance('zmq.Socket')
349 351 _query_socket=Instance('zmq.Socket')
350 352 _control_socket=Instance('zmq.Socket')
351 353 _iopub_socket=Instance('zmq.Socket')
352 354 _notification_socket=Instance('zmq.Socket')
353 355 _mux_socket=Instance('zmq.Socket')
354 356 _task_socket=Instance('zmq.Socket')
355 357 _task_scheme=Unicode()
356 358 _closed = False
357 359 _ignored_control_replies=Integer(0)
358 360 _ignored_hub_replies=Integer(0)
359 361
360 362 def __new__(self, *args, **kw):
361 363 # don't raise on positional args
362 364 return HasTraits.__new__(self, **kw)
363 365
364 366 def __init__(self, url_file=None, profile=None, profile_dir=None, ipython_dir=None,
365 367 context=None, debug=False,
366 368 sshserver=None, sshkey=None, password=None, paramiko=None,
367 369 timeout=10, **extra_args
368 370 ):
369 371 if profile:
370 372 super(Client, self).__init__(debug=debug, profile=profile)
371 373 else:
372 374 super(Client, self).__init__(debug=debug)
373 375 if context is None:
374 376 context = zmq.Context.instance()
375 377 self._context = context
376 378 self._stop_spinning = Event()
377 379
378 380 if 'url_or_file' in extra_args:
379 381 url_file = extra_args['url_or_file']
380 382 warnings.warn("url_or_file arg no longer supported, use url_file", DeprecationWarning)
381 383
382 384 if url_file and util.is_url(url_file):
383 385 raise ValueError("single urls cannot be specified, url-files must be used.")
384 386
385 387 self._setup_profile_dir(self.profile, profile_dir, ipython_dir)
386 388
387 389 if self._cd is not None:
388 390 if url_file is None:
389 391 url_file = pjoin(self._cd.security_dir, 'ipcontroller-client.json')
390 392 if url_file is None:
391 393 raise ValueError(
392 394 "I can't find enough information to connect to a hub!"
393 395 " Please specify at least one of url_file or profile."
394 396 )
395 397
396 398 with open(url_file) as f:
397 399 cfg = json.load(f)
398 400
399 401 self._task_scheme = cfg['task_scheme']
400 402
401 403 # sync defaults from args, json:
402 404 if sshserver:
403 405 cfg['ssh'] = sshserver
404 406
405 407 location = cfg.setdefault('location', None)
406 408
407 409 proto,addr = cfg['interface'].split('://')
408 410 addr = util.disambiguate_ip_address(addr)
409 411 cfg['interface'] = "%s://%s" % (proto, addr)
410 412
411 413 # turn interface,port into full urls:
412 414 for key in ('control', 'task', 'mux', 'iopub', 'notification', 'registration'):
413 415 cfg[key] = cfg['interface'] + ':%i' % cfg[key]
414 416
415 417 url = cfg['registration']
416 418
417 419 if location is not None and addr == '127.0.0.1':
418 420 # location specified, and connection is expected to be local
419 421 if location not in LOCAL_IPS and not sshserver:
420 422 # load ssh from JSON *only* if the controller is not on
421 423 # this machine
422 424 sshserver=cfg['ssh']
423 425 if location not in LOCAL_IPS and not sshserver:
424 426 # warn if no ssh specified, but SSH is probably needed
425 427 # This is only a warning, because the most likely cause
426 428 # is a local Controller on a laptop whose IP is dynamic
427 429 warnings.warn("""
428 430 Controller appears to be listening on localhost, but not on this machine.
429 431 If this is true, you should specify Client(...,sshserver='you@%s')
430 432 or instruct your controller to listen on an external IP."""%location,
431 433 RuntimeWarning)
432 434 elif not sshserver:
433 435 # otherwise sync with cfg
434 436 sshserver = cfg['ssh']
435 437
436 438 self._config = cfg
437 439
438 440 self._ssh = bool(sshserver or sshkey or password)
439 441 if self._ssh and sshserver is None:
440 442 # default to ssh via localhost
441 443 sshserver = addr
442 444 if self._ssh and password is None:
443 445 if tunnel.try_passwordless_ssh(sshserver, sshkey, paramiko):
444 446 password=False
445 447 else:
446 448 password = getpass("SSH Password for %s: "%sshserver)
447 449 ssh_kwargs = dict(keyfile=sshkey, password=password, paramiko=paramiko)
448 450
449 451 # configure and construct the session
450 452 extra_args['packer'] = cfg['pack']
451 453 extra_args['unpacker'] = cfg['unpack']
452 454 extra_args['key'] = cast_bytes(cfg['exec_key'])
453 455
454 456 self.session = Session(**extra_args)
455 457
456 458 self._query_socket = self._context.socket(zmq.DEALER)
457 459
458 460 if self._ssh:
459 461 tunnel.tunnel_connection(self._query_socket, cfg['registration'], sshserver, **ssh_kwargs)
460 462 else:
461 463 self._query_socket.connect(cfg['registration'])
462 464
463 465 self.session.debug = self.debug
464 466
465 467 self._notification_handlers = {'registration_notification' : self._register_engine,
466 468 'unregistration_notification' : self._unregister_engine,
467 469 'shutdown_notification' : lambda msg: self.close(),
468 470 }
469 471 self._queue_handlers = {'execute_reply' : self._handle_execute_reply,
470 472 'apply_reply' : self._handle_apply_reply}
471 473 self._connect(sshserver, ssh_kwargs, timeout)
472 474
473 475 # last step: setup magics, if we are in IPython:
474 476
475 477 try:
476 478 ip = get_ipython()
477 479 except NameError:
478 480 return
479 481 else:
480 482 if 'px' not in ip.magics_manager.magics:
481 483 # in IPython but we are the first Client.
482 484 # activate a default view for parallel magics.
483 485 self.activate()
484 486
485 487 def __del__(self):
486 488 """cleanup sockets, but _not_ context."""
487 489 self.close()
488 490
489 491 def _setup_profile_dir(self, profile, profile_dir, ipython_dir):
490 492 if ipython_dir is None:
491 493 ipython_dir = get_ipython_dir()
492 494 if profile_dir is not None:
493 495 try:
494 496 self._cd = ProfileDir.find_profile_dir(profile_dir)
495 497 return
496 498 except ProfileDirError:
497 499 pass
498 500 elif profile is not None:
499 501 try:
500 502 self._cd = ProfileDir.find_profile_dir_by_name(
501 503 ipython_dir, profile)
502 504 return
503 505 except ProfileDirError:
504 506 pass
505 507 self._cd = None
506 508
507 509 def _update_engines(self, engines):
508 510 """Update our engines dict and _ids from a dict of the form: {id:uuid}."""
509 511 for k,v in engines.iteritems():
510 512 eid = int(k)
511 513 if eid not in self._engines:
512 514 self._ids.append(eid)
513 515 self._engines[eid] = v
514 516 self._ids = sorted(self._ids)
515 517 if sorted(self._engines.keys()) != range(len(self._engines)) and \
516 518 self._task_scheme == 'pure' and self._task_socket:
517 519 self._stop_scheduling_tasks()
518 520
519 521 def _stop_scheduling_tasks(self):
520 522 """Stop scheduling tasks because an engine has been unregistered
521 523 from a pure ZMQ scheduler.
522 524 """
523 525 self._task_socket.close()
524 526 self._task_socket = None
525 527 msg = "An engine has been unregistered, and we are using pure " +\
526 528 "ZMQ task scheduling. Task farming will be disabled."
527 529 if self.outstanding:
528 530 msg += " If you were running tasks when this happened, " +\
529 531 "some `outstanding` msg_ids may never resolve."
530 532 warnings.warn(msg, RuntimeWarning)
531 533
532 534 def _build_targets(self, targets):
533 535 """Turn valid target IDs or 'all' into two lists:
534 536 (int_ids, uuids).
535 537 """
536 538 if not self._ids:
537 539 # flush notification socket if no engines yet, just in case
538 540 if not self.ids:
539 541 raise error.NoEnginesRegistered("Can't build targets without any engines")
540 542
541 543 if targets is None:
542 544 targets = self._ids
543 545 elif isinstance(targets, basestring):
544 546 if targets.lower() == 'all':
545 547 targets = self._ids
546 548 else:
547 549 raise TypeError("%r not valid str target, must be 'all'"%(targets))
548 550 elif isinstance(targets, int):
549 551 if targets < 0:
550 552 targets = self.ids[targets]
551 553 if targets not in self._ids:
552 554 raise IndexError("No such engine: %i"%targets)
553 555 targets = [targets]
554 556
555 557 if isinstance(targets, slice):
556 558 indices = range(len(self._ids))[targets]
557 559 ids = self.ids
558 560 targets = [ ids[i] for i in indices ]
559 561
560 562 if not isinstance(targets, (tuple, list, xrange)):
561 563 raise TypeError("targets by int/slice/collection of ints only, not %s"%(type(targets)))
562 564
563 565 return [cast_bytes(self._engines[t]) for t in targets], list(targets)
564 566
565 567 def _connect(self, sshserver, ssh_kwargs, timeout):
566 568 """setup all our socket connections to the cluster. This is called from
567 569 __init__."""
568 570
569 571 # Maybe allow reconnecting?
570 572 if self._connected:
571 573 return
572 574 self._connected=True
573 575
574 576 def connect_socket(s, url):
575 577 # url = util.disambiguate_url(url, self._config['location'])
576 578 if self._ssh:
577 579 return tunnel.tunnel_connection(s, url, sshserver, **ssh_kwargs)
578 580 else:
579 581 return s.connect(url)
580 582
581 583 self.session.send(self._query_socket, 'connection_request')
582 584 # use Poller because zmq.select has wrong units in pyzmq 2.1.7
583 585 poller = zmq.Poller()
584 586 poller.register(self._query_socket, zmq.POLLIN)
585 587 # poll expects milliseconds, timeout is seconds
586 588 evts = poller.poll(timeout*1000)
587 589 if not evts:
588 590 raise error.TimeoutError("Hub connection request timed out")
589 591 idents,msg = self.session.recv(self._query_socket,mode=0)
590 592 if self.debug:
591 593 pprint(msg)
592 594 content = msg['content']
593 595 # self._config['registration'] = dict(content)
594 596 cfg = self._config
595 597 if content['status'] == 'ok':
596 598 self._mux_socket = self._context.socket(zmq.DEALER)
597 599 connect_socket(self._mux_socket, cfg['mux'])
598 600
599 601 self._task_socket = self._context.socket(zmq.DEALER)
600 602 connect_socket(self._task_socket, cfg['task'])
601 603
602 604 self._notification_socket = self._context.socket(zmq.SUB)
603 605 self._notification_socket.setsockopt(zmq.SUBSCRIBE, b'')
604 606 connect_socket(self._notification_socket, cfg['notification'])
605 607
606 608 self._control_socket = self._context.socket(zmq.DEALER)
607 609 connect_socket(self._control_socket, cfg['control'])
608 610
609 611 self._iopub_socket = self._context.socket(zmq.SUB)
610 612 self._iopub_socket.setsockopt(zmq.SUBSCRIBE, b'')
611 613 connect_socket(self._iopub_socket, cfg['iopub'])
612 614
613 615 self._update_engines(dict(content['engines']))
614 616 else:
615 617 self._connected = False
616 618 raise Exception("Failed to connect!")
617 619
618 620 #--------------------------------------------------------------------------
619 621 # handlers and callbacks for incoming messages
620 622 #--------------------------------------------------------------------------
621 623
622 624 def _unwrap_exception(self, content):
623 625 """unwrap exception, and remap engine_id to int."""
624 626 e = error.unwrap_exception(content)
625 627 # print e.traceback
626 628 if e.engine_info:
627 629 e_uuid = e.engine_info['engine_uuid']
628 630 eid = self._engines[e_uuid]
629 631 e.engine_info['engine_id'] = eid
630 632 return e
631 633
632 634 def _extract_metadata(self, msg):
633 635 header = msg['header']
634 636 parent = msg['parent_header']
635 637 msg_meta = msg['metadata']
636 638 content = msg['content']
637 639 md = {'msg_id' : parent['msg_id'],
638 640 'received' : datetime.now(),
639 641 'engine_uuid' : msg_meta.get('engine', None),
640 642 'follow' : msg_meta.get('follow', []),
641 643 'after' : msg_meta.get('after', []),
642 644 'status' : content['status'],
643 645 }
644 646
645 647 if md['engine_uuid'] is not None:
646 648 md['engine_id'] = self._engines.get(md['engine_uuid'], None)
647 649
648 650 if 'date' in parent:
649 651 md['submitted'] = parent['date']
650 652 if 'started' in msg_meta:
651 653 md['started'] = msg_meta['started']
652 654 if 'date' in header:
653 655 md['completed'] = header['date']
654 656 return md
655 657
656 658 def _register_engine(self, msg):
657 659 """Register a new engine, and update our connection info."""
658 660 content = msg['content']
659 661 eid = content['id']
660 662 d = {eid : content['uuid']}
661 663 self._update_engines(d)
662 664
663 665 def _unregister_engine(self, msg):
664 666 """Unregister an engine that has died."""
665 667 content = msg['content']
666 668 eid = int(content['id'])
667 669 if eid in self._ids:
668 670 self._ids.remove(eid)
669 671 uuid = self._engines.pop(eid)
670 672
671 673 self._handle_stranded_msgs(eid, uuid)
672 674
673 675 if self._task_socket and self._task_scheme == 'pure':
674 676 self._stop_scheduling_tasks()
675 677
676 678 def _handle_stranded_msgs(self, eid, uuid):
677 679 """Handle messages known to be on an engine when the engine unregisters.
678 680
679 681 It is possible that this will fire prematurely - that is, an engine will
680 682 go down after completing a result, and the client will be notified
681 683 of the unregistration and later receive the successful result.
682 684 """
683 685
684 686 outstanding = self._outstanding_dict[uuid]
685 687
686 688 for msg_id in list(outstanding):
687 689 if msg_id in self.results:
688 690 # we already
689 691 continue
690 692 try:
691 693 raise error.EngineError("Engine %r died while running task %r"%(eid, msg_id))
692 694 except:
693 695 content = error.wrap_exception()
694 696 # build a fake message:
695 697 parent = {}
696 698 header = {}
697 699 parent['msg_id'] = msg_id
698 700 header['engine'] = uuid
699 701 header['date'] = datetime.now()
700 702 msg = dict(parent_header=parent, header=header, content=content)
701 703 self._handle_apply_reply(msg)
702 704
703 705 def _handle_execute_reply(self, msg):
704 706 """Save the reply to an execute_request into our results.
705 707
706 708 execute messages are never actually used. apply is used instead.
707 709 """
708 710
709 711 parent = msg['parent_header']
710 712 msg_id = parent['msg_id']
711 713 if msg_id not in self.outstanding:
712 714 if msg_id in self.history:
713 715 print ("got stale result: %s"%msg_id)
714 716 else:
715 717 print ("got unknown result: %s"%msg_id)
716 718 else:
717 719 self.outstanding.remove(msg_id)
718 720
719 721 content = msg['content']
720 722 header = msg['header']
721 723
722 724 # construct metadata:
723 725 md = self.metadata[msg_id]
724 726 md.update(self._extract_metadata(msg))
725 727 # is this redundant?
726 728 self.metadata[msg_id] = md
727 729
728 730 e_outstanding = self._outstanding_dict[md['engine_uuid']]
729 731 if msg_id in e_outstanding:
730 732 e_outstanding.remove(msg_id)
731 733
732 734 # construct result:
733 735 if content['status'] == 'ok':
734 736 self.results[msg_id] = ExecuteReply(msg_id, content, md)
735 737 elif content['status'] == 'aborted':
736 738 self.results[msg_id] = error.TaskAborted(msg_id)
737 739 elif content['status'] == 'resubmitted':
738 740 # TODO: handle resubmission
739 741 pass
740 742 else:
741 743 self.results[msg_id] = self._unwrap_exception(content)
742 744
743 745 def _handle_apply_reply(self, msg):
744 746 """Save the reply to an apply_request into our results."""
745 747 parent = msg['parent_header']
746 748 msg_id = parent['msg_id']
747 749 if msg_id not in self.outstanding:
748 750 if msg_id in self.history:
749 751 print ("got stale result: %s"%msg_id)
750 752 print self.results[msg_id]
751 753 print msg
752 754 else:
753 755 print ("got unknown result: %s"%msg_id)
754 756 else:
755 757 self.outstanding.remove(msg_id)
756 758 content = msg['content']
757 759 header = msg['header']
758 760
759 761 # construct metadata:
760 762 md = self.metadata[msg_id]
761 763 md.update(self._extract_metadata(msg))
762 764 # is this redundant?
763 765 self.metadata[msg_id] = md
764 766
765 767 e_outstanding = self._outstanding_dict[md['engine_uuid']]
766 768 if msg_id in e_outstanding:
767 769 e_outstanding.remove(msg_id)
768 770
769 771 # construct result:
770 772 if content['status'] == 'ok':
771 self.results[msg_id] = util.unserialize_object(msg['buffers'])[0]
773 self.results[msg_id] = serialize.unserialize_object(msg['buffers'])[0]
772 774 elif content['status'] == 'aborted':
773 775 self.results[msg_id] = error.TaskAborted(msg_id)
774 776 elif content['status'] == 'resubmitted':
775 777 # TODO: handle resubmission
776 778 pass
777 779 else:
778 780 self.results[msg_id] = self._unwrap_exception(content)
779 781
780 782 def _flush_notifications(self):
781 783 """Flush notifications of engine registrations waiting
782 784 in ZMQ queue."""
783 785 idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
784 786 while msg is not None:
785 787 if self.debug:
786 788 pprint(msg)
787 789 msg_type = msg['header']['msg_type']
788 790 handler = self._notification_handlers.get(msg_type, None)
789 791 if handler is None:
790 792 raise Exception("Unhandled message type: %s"%msg.msg_type)
791 793 else:
792 794 handler(msg)
793 795 idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
794 796
795 797 def _flush_results(self, sock):
796 798 """Flush task or queue results waiting in ZMQ queue."""
797 799 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
798 800 while msg is not None:
799 801 if self.debug:
800 802 pprint(msg)
801 803 msg_type = msg['header']['msg_type']
802 804 handler = self._queue_handlers.get(msg_type, None)
803 805 if handler is None:
804 806 raise Exception("Unhandled message type: %s"%msg.msg_type)
805 807 else:
806 808 handler(msg)
807 809 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
808 810
809 811 def _flush_control(self, sock):
810 812 """Flush replies from the control channel waiting
811 813 in the ZMQ queue.
812 814
813 815 Currently: ignore them."""
814 816 if self._ignored_control_replies <= 0:
815 817 return
816 818 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
817 819 while msg is not None:
818 820 self._ignored_control_replies -= 1
819 821 if self.debug:
820 822 pprint(msg)
821 823 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
822 824
823 825 def _flush_ignored_control(self):
824 826 """flush ignored control replies"""
825 827 while self._ignored_control_replies > 0:
826 828 self.session.recv(self._control_socket)
827 829 self._ignored_control_replies -= 1
828 830
829 831 def _flush_ignored_hub_replies(self):
830 832 ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
831 833 while msg is not None:
832 834 ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
833 835
834 836 def _flush_iopub(self, sock):
835 837 """Flush replies from the iopub channel waiting
836 838 in the ZMQ queue.
837 839 """
838 840 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
839 841 while msg is not None:
840 842 if self.debug:
841 843 pprint(msg)
842 844 parent = msg['parent_header']
843 845 # ignore IOPub messages with no parent.
844 846 # Caused by print statements or warnings from before the first execution.
845 847 if not parent:
846 848 continue
847 849 msg_id = parent['msg_id']
848 850 content = msg['content']
849 851 header = msg['header']
850 852 msg_type = msg['header']['msg_type']
851 853
852 854 # init metadata:
853 855 md = self.metadata[msg_id]
854 856
855 857 if msg_type == 'stream':
856 858 name = content['name']
857 859 s = md[name] or ''
858 860 md[name] = s + content['data']
859 861 elif msg_type == 'pyerr':
860 862 md.update({'pyerr' : self._unwrap_exception(content)})
861 863 elif msg_type == 'pyin':
862 864 md.update({'pyin' : content['code']})
863 865 elif msg_type == 'display_data':
864 866 md['outputs'].append(content)
865 867 elif msg_type == 'pyout':
866 868 md['pyout'] = content
869 elif msg_type == 'data_message':
870 data, remainder = serialize.unserialize_object(msg['buffers'])
871 md['data'].update(data)
867 872 elif msg_type == 'status':
868 873 # idle message comes after all outputs
869 874 if content['execution_state'] == 'idle':
870 875 md['outputs_ready'] = True
871 876 else:
872 877 # unhandled msg_type (status, etc.)
873 878 pass
874 879
875 880 # reduntant?
876 881 self.metadata[msg_id] = md
877 882
878 883 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
879 884
880 885 #--------------------------------------------------------------------------
881 886 # len, getitem
882 887 #--------------------------------------------------------------------------
883 888
884 889 def __len__(self):
885 890 """len(client) returns # of engines."""
886 891 return len(self.ids)
887 892
888 893 def __getitem__(self, key):
889 894 """index access returns DirectView multiplexer objects
890 895
891 896 Must be int, slice, or list/tuple/xrange of ints"""
892 897 if not isinstance(key, (int, slice, tuple, list, xrange)):
893 898 raise TypeError("key by int/slice/iterable of ints only, not %s"%(type(key)))
894 899 else:
895 900 return self.direct_view(key)
896 901
897 902 #--------------------------------------------------------------------------
898 903 # Begin public methods
899 904 #--------------------------------------------------------------------------
900 905
901 906 @property
902 907 def ids(self):
903 908 """Always up-to-date ids property."""
904 909 self._flush_notifications()
905 910 # always copy:
906 911 return list(self._ids)
907 912
908 913 def activate(self, targets='all', suffix=''):
909 914 """Create a DirectView and register it with IPython magics
910 915
911 916 Defines the magics `%px, %autopx, %pxresult, %%px`
912 917
913 918 Parameters
914 919 ----------
915 920
916 921 targets: int, list of ints, or 'all'
917 922 The engines on which the view's magics will run
918 923 suffix: str [default: '']
919 924 The suffix, if any, for the magics. This allows you to have
920 925 multiple views associated with parallel magics at the same time.
921 926
922 927 e.g. ``rc.activate(targets=0, suffix='0')`` will give you
923 928 the magics ``%px0``, ``%pxresult0``, etc. for running magics just
924 929 on engine 0.
925 930 """
926 931 view = self.direct_view(targets)
927 932 view.block = True
928 933 view.activate(suffix)
929 934 return view
930 935
931 936 def close(self):
932 937 if self._closed:
933 938 return
934 939 self.stop_spin_thread()
935 940 snames = filter(lambda n: n.endswith('socket'), dir(self))
936 941 for socket in map(lambda name: getattr(self, name), snames):
937 942 if isinstance(socket, zmq.Socket) and not socket.closed:
938 943 socket.close()
939 944 self._closed = True
940 945
941 946 def _spin_every(self, interval=1):
942 947 """target func for use in spin_thread"""
943 948 while True:
944 949 if self._stop_spinning.is_set():
945 950 return
946 951 time.sleep(interval)
947 952 self.spin()
948 953
949 954 def spin_thread(self, interval=1):
950 955 """call Client.spin() in a background thread on some regular interval
951 956
952 957 This helps ensure that messages don't pile up too much in the zmq queue
953 958 while you are working on other things, or just leaving an idle terminal.
954 959
955 960 It also helps limit potential padding of the `received` timestamp
956 961 on AsyncResult objects, used for timings.
957 962
958 963 Parameters
959 964 ----------
960 965
961 966 interval : float, optional
962 967 The interval on which to spin the client in the background thread
963 968 (simply passed to time.sleep).
964 969
965 970 Notes
966 971 -----
967 972
968 973 For precision timing, you may want to use this method to put a bound
969 974 on the jitter (in seconds) in `received` timestamps used
970 975 in AsyncResult.wall_time.
971 976
972 977 """
973 978 if self._spin_thread is not None:
974 979 self.stop_spin_thread()
975 980 self._stop_spinning.clear()
976 981 self._spin_thread = Thread(target=self._spin_every, args=(interval,))
977 982 self._spin_thread.daemon = True
978 983 self._spin_thread.start()
979 984
980 985 def stop_spin_thread(self):
981 986 """stop background spin_thread, if any"""
982 987 if self._spin_thread is not None:
983 988 self._stop_spinning.set()
984 989 self._spin_thread.join()
985 990 self._spin_thread = None
986 991
987 992 def spin(self):
988 993 """Flush any registration notifications and execution results
989 994 waiting in the ZMQ queue.
990 995 """
991 996 if self._notification_socket:
992 997 self._flush_notifications()
993 998 if self._iopub_socket:
994 999 self._flush_iopub(self._iopub_socket)
995 1000 if self._mux_socket:
996 1001 self._flush_results(self._mux_socket)
997 1002 if self._task_socket:
998 1003 self._flush_results(self._task_socket)
999 1004 if self._control_socket:
1000 1005 self._flush_control(self._control_socket)
1001 1006 if self._query_socket:
1002 1007 self._flush_ignored_hub_replies()
1003 1008
1004 1009 def wait(self, jobs=None, timeout=-1):
1005 1010 """waits on one or more `jobs`, for up to `timeout` seconds.
1006 1011
1007 1012 Parameters
1008 1013 ----------
1009 1014
1010 1015 jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects
1011 1016 ints are indices to self.history
1012 1017 strs are msg_ids
1013 1018 default: wait on all outstanding messages
1014 1019 timeout : float
1015 1020 a time in seconds, after which to give up.
1016 1021 default is -1, which means no timeout
1017 1022
1018 1023 Returns
1019 1024 -------
1020 1025
1021 1026 True : when all msg_ids are done
1022 1027 False : timeout reached, some msg_ids still outstanding
1023 1028 """
1024 1029 tic = time.time()
1025 1030 if jobs is None:
1026 1031 theids = self.outstanding
1027 1032 else:
1028 1033 if isinstance(jobs, (int, basestring, AsyncResult)):
1029 1034 jobs = [jobs]
1030 1035 theids = set()
1031 1036 for job in jobs:
1032 1037 if isinstance(job, int):
1033 1038 # index access
1034 1039 job = self.history[job]
1035 1040 elif isinstance(job, AsyncResult):
1036 1041 map(theids.add, job.msg_ids)
1037 1042 continue
1038 1043 theids.add(job)
1039 1044 if not theids.intersection(self.outstanding):
1040 1045 return True
1041 1046 self.spin()
1042 1047 while theids.intersection(self.outstanding):
1043 1048 if timeout >= 0 and ( time.time()-tic ) > timeout:
1044 1049 break
1045 1050 time.sleep(1e-3)
1046 1051 self.spin()
1047 1052 return len(theids.intersection(self.outstanding)) == 0
1048 1053
1049 1054 #--------------------------------------------------------------------------
1050 1055 # Control methods
1051 1056 #--------------------------------------------------------------------------
1052 1057
1053 1058 @spin_first
1054 1059 def clear(self, targets=None, block=None):
1055 1060 """Clear the namespace in target(s)."""
1056 1061 block = self.block if block is None else block
1057 1062 targets = self._build_targets(targets)[0]
1058 1063 for t in targets:
1059 1064 self.session.send(self._control_socket, 'clear_request', content={}, ident=t)
1060 1065 error = False
1061 1066 if block:
1062 1067 self._flush_ignored_control()
1063 1068 for i in range(len(targets)):
1064 1069 idents,msg = self.session.recv(self._control_socket,0)
1065 1070 if self.debug:
1066 1071 pprint(msg)
1067 1072 if msg['content']['status'] != 'ok':
1068 1073 error = self._unwrap_exception(msg['content'])
1069 1074 else:
1070 1075 self._ignored_control_replies += len(targets)
1071 1076 if error:
1072 1077 raise error
1073 1078
1074 1079
1075 1080 @spin_first
1076 1081 def abort(self, jobs=None, targets=None, block=None):
1077 1082 """Abort specific jobs from the execution queues of target(s).
1078 1083
1079 1084 This is a mechanism to prevent jobs that have already been submitted
1080 1085 from executing.
1081 1086
1082 1087 Parameters
1083 1088 ----------
1084 1089
1085 1090 jobs : msg_id, list of msg_ids, or AsyncResult
1086 1091 The jobs to be aborted
1087 1092
1088 1093 If unspecified/None: abort all outstanding jobs.
1089 1094
1090 1095 """
1091 1096 block = self.block if block is None else block
1092 1097 jobs = jobs if jobs is not None else list(self.outstanding)
1093 1098 targets = self._build_targets(targets)[0]
1094 1099
1095 1100 msg_ids = []
1096 1101 if isinstance(jobs, (basestring,AsyncResult)):
1097 1102 jobs = [jobs]
1098 1103 bad_ids = filter(lambda obj: not isinstance(obj, (basestring, AsyncResult)), jobs)
1099 1104 if bad_ids:
1100 1105 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
1101 1106 for j in jobs:
1102 1107 if isinstance(j, AsyncResult):
1103 1108 msg_ids.extend(j.msg_ids)
1104 1109 else:
1105 1110 msg_ids.append(j)
1106 1111 content = dict(msg_ids=msg_ids)
1107 1112 for t in targets:
1108 1113 self.session.send(self._control_socket, 'abort_request',
1109 1114 content=content, ident=t)
1110 1115 error = False
1111 1116 if block:
1112 1117 self._flush_ignored_control()
1113 1118 for i in range(len(targets)):
1114 1119 idents,msg = self.session.recv(self._control_socket,0)
1115 1120 if self.debug:
1116 1121 pprint(msg)
1117 1122 if msg['content']['status'] != 'ok':
1118 1123 error = self._unwrap_exception(msg['content'])
1119 1124 else:
1120 1125 self._ignored_control_replies += len(targets)
1121 1126 if error:
1122 1127 raise error
1123 1128
1124 1129 @spin_first
1125 1130 def shutdown(self, targets='all', restart=False, hub=False, block=None):
1126 1131 """Terminates one or more engine processes, optionally including the hub.
1127 1132
1128 1133 Parameters
1129 1134 ----------
1130 1135
1131 1136 targets: list of ints or 'all' [default: all]
1132 1137 Which engines to shutdown.
1133 1138 hub: bool [default: False]
1134 1139 Whether to include the Hub. hub=True implies targets='all'.
1135 1140 block: bool [default: self.block]
1136 1141 Whether to wait for clean shutdown replies or not.
1137 1142 restart: bool [default: False]
1138 1143 NOT IMPLEMENTED
1139 1144 whether to restart engines after shutting them down.
1140 1145 """
1141 1146
1142 1147 if restart:
1143 1148 raise NotImplementedError("Engine restart is not yet implemented")
1144 1149
1145 1150 block = self.block if block is None else block
1146 1151 if hub:
1147 1152 targets = 'all'
1148 1153 targets = self._build_targets(targets)[0]
1149 1154 for t in targets:
1150 1155 self.session.send(self._control_socket, 'shutdown_request',
1151 1156 content={'restart':restart},ident=t)
1152 1157 error = False
1153 1158 if block or hub:
1154 1159 self._flush_ignored_control()
1155 1160 for i in range(len(targets)):
1156 1161 idents,msg = self.session.recv(self._control_socket, 0)
1157 1162 if self.debug:
1158 1163 pprint(msg)
1159 1164 if msg['content']['status'] != 'ok':
1160 1165 error = self._unwrap_exception(msg['content'])
1161 1166 else:
1162 1167 self._ignored_control_replies += len(targets)
1163 1168
1164 1169 if hub:
1165 1170 time.sleep(0.25)
1166 1171 self.session.send(self._query_socket, 'shutdown_request')
1167 1172 idents,msg = self.session.recv(self._query_socket, 0)
1168 1173 if self.debug:
1169 1174 pprint(msg)
1170 1175 if msg['content']['status'] != 'ok':
1171 1176 error = self._unwrap_exception(msg['content'])
1172 1177
1173 1178 if error:
1174 1179 raise error
1175 1180
1176 1181 #--------------------------------------------------------------------------
1177 1182 # Execution related methods
1178 1183 #--------------------------------------------------------------------------
1179 1184
1180 1185 def _maybe_raise(self, result):
1181 1186 """wrapper for maybe raising an exception if apply failed."""
1182 1187 if isinstance(result, error.RemoteError):
1183 1188 raise result
1184 1189
1185 1190 return result
1186 1191
1187 1192 def send_apply_request(self, socket, f, args=None, kwargs=None, metadata=None, track=False,
1188 1193 ident=None):
1189 1194 """construct and send an apply message via a socket.
1190 1195
1191 1196 This is the principal method with which all engine execution is performed by views.
1192 1197 """
1193 1198
1194 1199 if self._closed:
1195 1200 raise RuntimeError("Client cannot be used after its sockets have been closed")
1196 1201
1197 1202 # defaults:
1198 1203 args = args if args is not None else []
1199 1204 kwargs = kwargs if kwargs is not None else {}
1200 1205 metadata = metadata if metadata is not None else {}
1201 1206
1202 1207 # validate arguments
1203 1208 if not callable(f) and not isinstance(f, Reference):
1204 1209 raise TypeError("f must be callable, not %s"%type(f))
1205 1210 if not isinstance(args, (tuple, list)):
1206 1211 raise TypeError("args must be tuple or list, not %s"%type(args))
1207 1212 if not isinstance(kwargs, dict):
1208 1213 raise TypeError("kwargs must be dict, not %s"%type(kwargs))
1209 1214 if not isinstance(metadata, dict):
1210 1215 raise TypeError("metadata must be dict, not %s"%type(metadata))
1211 1216
1212 bufs = util.pack_apply_message(f, args, kwargs,
1217 bufs = serialize.pack_apply_message(f, args, kwargs,
1213 1218 buffer_threshold=self.session.buffer_threshold,
1214 1219 item_threshold=self.session.item_threshold,
1215 1220 )
1216 1221
1217 1222 msg = self.session.send(socket, "apply_request", buffers=bufs, ident=ident,
1218 1223 metadata=metadata, track=track)
1219 1224
1220 1225 msg_id = msg['header']['msg_id']
1221 1226 self.outstanding.add(msg_id)
1222 1227 if ident:
1223 1228 # possibly routed to a specific engine
1224 1229 if isinstance(ident, list):
1225 1230 ident = ident[-1]
1226 1231 if ident in self._engines.values():
1227 1232 # save for later, in case of engine death
1228 1233 self._outstanding_dict[ident].add(msg_id)
1229 1234 self.history.append(msg_id)
1230 1235 self.metadata[msg_id]['submitted'] = datetime.now()
1231 1236
1232 1237 return msg
1233 1238
1234 1239 def send_execute_request(self, socket, code, silent=True, metadata=None, ident=None):
1235 1240 """construct and send an execute request via a socket.
1236 1241
1237 1242 """
1238 1243
1239 1244 if self._closed:
1240 1245 raise RuntimeError("Client cannot be used after its sockets have been closed")
1241 1246
1242 1247 # defaults:
1243 1248 metadata = metadata if metadata is not None else {}
1244 1249
1245 1250 # validate arguments
1246 1251 if not isinstance(code, basestring):
1247 1252 raise TypeError("code must be text, not %s" % type(code))
1248 1253 if not isinstance(metadata, dict):
1249 1254 raise TypeError("metadata must be dict, not %s" % type(metadata))
1250 1255
1251 1256 content = dict(code=code, silent=bool(silent), user_variables=[], user_expressions={})
1252 1257
1253 1258
1254 1259 msg = self.session.send(socket, "execute_request", content=content, ident=ident,
1255 1260 metadata=metadata)
1256 1261
1257 1262 msg_id = msg['header']['msg_id']
1258 1263 self.outstanding.add(msg_id)
1259 1264 if ident:
1260 1265 # possibly routed to a specific engine
1261 1266 if isinstance(ident, list):
1262 1267 ident = ident[-1]
1263 1268 if ident in self._engines.values():
1264 1269 # save for later, in case of engine death
1265 1270 self._outstanding_dict[ident].add(msg_id)
1266 1271 self.history.append(msg_id)
1267 1272 self.metadata[msg_id]['submitted'] = datetime.now()
1268 1273
1269 1274 return msg
1270 1275
1271 1276 #--------------------------------------------------------------------------
1272 1277 # construct a View object
1273 1278 #--------------------------------------------------------------------------
1274 1279
1275 1280 def load_balanced_view(self, targets=None):
1276 1281 """construct a DirectView object.
1277 1282
1278 1283 If no arguments are specified, create a LoadBalancedView
1279 1284 using all engines.
1280 1285
1281 1286 Parameters
1282 1287 ----------
1283 1288
1284 1289 targets: list,slice,int,etc. [default: use all engines]
1285 1290 The subset of engines across which to load-balance
1286 1291 """
1287 1292 if targets == 'all':
1288 1293 targets = None
1289 1294 if targets is not None:
1290 1295 targets = self._build_targets(targets)[1]
1291 1296 return LoadBalancedView(client=self, socket=self._task_socket, targets=targets)
1292 1297
1293 1298 def direct_view(self, targets='all'):
1294 1299 """construct a DirectView object.
1295 1300
1296 1301 If no targets are specified, create a DirectView using all engines.
1297 1302
1298 1303 rc.direct_view('all') is distinguished from rc[:] in that 'all' will
1299 1304 evaluate the target engines at each execution, whereas rc[:] will connect to
1300 1305 all *current* engines, and that list will not change.
1301 1306
1302 1307 That is, 'all' will always use all engines, whereas rc[:] will not use
1303 1308 engines added after the DirectView is constructed.
1304 1309
1305 1310 Parameters
1306 1311 ----------
1307 1312
1308 1313 targets: list,slice,int,etc. [default: use all engines]
1309 1314 The engines to use for the View
1310 1315 """
1311 1316 single = isinstance(targets, int)
1312 1317 # allow 'all' to be lazily evaluated at each execution
1313 1318 if targets != 'all':
1314 1319 targets = self._build_targets(targets)[1]
1315 1320 if single:
1316 1321 targets = targets[0]
1317 1322 return DirectView(client=self, socket=self._mux_socket, targets=targets)
1318 1323
1319 1324 #--------------------------------------------------------------------------
1320 1325 # Query methods
1321 1326 #--------------------------------------------------------------------------
1322 1327
1323 1328 @spin_first
1324 1329 def get_result(self, indices_or_msg_ids=None, block=None):
1325 1330 """Retrieve a result by msg_id or history index, wrapped in an AsyncResult object.
1326 1331
1327 1332 If the client already has the results, no request to the Hub will be made.
1328 1333
1329 1334 This is a convenient way to construct AsyncResult objects, which are wrappers
1330 1335 that include metadata about execution, and allow for awaiting results that
1331 1336 were not submitted by this Client.
1332 1337
1333 1338 It can also be a convenient way to retrieve the metadata associated with
1334 1339 blocking execution, since it always retrieves
1335 1340
1336 1341 Examples
1337 1342 --------
1338 1343 ::
1339 1344
1340 1345 In [10]: r = client.apply()
1341 1346
1342 1347 Parameters
1343 1348 ----------
1344 1349
1345 1350 indices_or_msg_ids : integer history index, str msg_id, or list of either
1346 1351 The indices or msg_ids of indices to be retrieved
1347 1352
1348 1353 block : bool
1349 1354 Whether to wait for the result to be done
1350 1355
1351 1356 Returns
1352 1357 -------
1353 1358
1354 1359 AsyncResult
1355 1360 A single AsyncResult object will always be returned.
1356 1361
1357 1362 AsyncHubResult
1358 1363 A subclass of AsyncResult that retrieves results from the Hub
1359 1364
1360 1365 """
1361 1366 block = self.block if block is None else block
1362 1367 if indices_or_msg_ids is None:
1363 1368 indices_or_msg_ids = -1
1364 1369
1365 1370 if not isinstance(indices_or_msg_ids, (list,tuple)):
1366 1371 indices_or_msg_ids = [indices_or_msg_ids]
1367 1372
1368 1373 theids = []
1369 1374 for id in indices_or_msg_ids:
1370 1375 if isinstance(id, int):
1371 1376 id = self.history[id]
1372 1377 if not isinstance(id, basestring):
1373 1378 raise TypeError("indices must be str or int, not %r"%id)
1374 1379 theids.append(id)
1375 1380
1376 1381 local_ids = filter(lambda msg_id: msg_id in self.history or msg_id in self.results, theids)
1377 1382 remote_ids = filter(lambda msg_id: msg_id not in local_ids, theids)
1378 1383
1379 1384 if remote_ids:
1380 1385 ar = AsyncHubResult(self, msg_ids=theids)
1381 1386 else:
1382 1387 ar = AsyncResult(self, msg_ids=theids)
1383 1388
1384 1389 if block:
1385 1390 ar.wait()
1386 1391
1387 1392 return ar
1388 1393
1389 1394 @spin_first
1390 1395 def resubmit(self, indices_or_msg_ids=None, metadata=None, block=None):
1391 1396 """Resubmit one or more tasks.
1392 1397
1393 1398 in-flight tasks may not be resubmitted.
1394 1399
1395 1400 Parameters
1396 1401 ----------
1397 1402
1398 1403 indices_or_msg_ids : integer history index, str msg_id, or list of either
1399 1404 The indices or msg_ids of indices to be retrieved
1400 1405
1401 1406 block : bool
1402 1407 Whether to wait for the result to be done
1403 1408
1404 1409 Returns
1405 1410 -------
1406 1411
1407 1412 AsyncHubResult
1408 1413 A subclass of AsyncResult that retrieves results from the Hub
1409 1414
1410 1415 """
1411 1416 block = self.block if block is None else block
1412 1417 if indices_or_msg_ids is None:
1413 1418 indices_or_msg_ids = -1
1414 1419
1415 1420 if not isinstance(indices_or_msg_ids, (list,tuple)):
1416 1421 indices_or_msg_ids = [indices_or_msg_ids]
1417 1422
1418 1423 theids = []
1419 1424 for id in indices_or_msg_ids:
1420 1425 if isinstance(id, int):
1421 1426 id = self.history[id]
1422 1427 if not isinstance(id, basestring):
1423 1428 raise TypeError("indices must be str or int, not %r"%id)
1424 1429 theids.append(id)
1425 1430
1426 1431 content = dict(msg_ids = theids)
1427 1432
1428 1433 self.session.send(self._query_socket, 'resubmit_request', content)
1429 1434
1430 1435 zmq.select([self._query_socket], [], [])
1431 1436 idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK)
1432 1437 if self.debug:
1433 1438 pprint(msg)
1434 1439 content = msg['content']
1435 1440 if content['status'] != 'ok':
1436 1441 raise self._unwrap_exception(content)
1437 1442 mapping = content['resubmitted']
1438 1443 new_ids = [ mapping[msg_id] for msg_id in theids ]
1439 1444
1440 1445 ar = AsyncHubResult(self, msg_ids=new_ids)
1441 1446
1442 1447 if block:
1443 1448 ar.wait()
1444 1449
1445 1450 return ar
1446 1451
1447 1452 @spin_first
1448 1453 def result_status(self, msg_ids, status_only=True):
1449 1454 """Check on the status of the result(s) of the apply request with `msg_ids`.
1450 1455
1451 1456 If status_only is False, then the actual results will be retrieved, else
1452 1457 only the status of the results will be checked.
1453 1458
1454 1459 Parameters
1455 1460 ----------
1456 1461
1457 1462 msg_ids : list of msg_ids
1458 1463 if int:
1459 1464 Passed as index to self.history for convenience.
1460 1465 status_only : bool (default: True)
1461 1466 if False:
1462 1467 Retrieve the actual results of completed tasks.
1463 1468
1464 1469 Returns
1465 1470 -------
1466 1471
1467 1472 results : dict
1468 1473 There will always be the keys 'pending' and 'completed', which will
1469 1474 be lists of msg_ids that are incomplete or complete. If `status_only`
1470 1475 is False, then completed results will be keyed by their `msg_id`.
1471 1476 """
1472 1477 if not isinstance(msg_ids, (list,tuple)):
1473 1478 msg_ids = [msg_ids]
1474 1479
1475 1480 theids = []
1476 1481 for msg_id in msg_ids:
1477 1482 if isinstance(msg_id, int):
1478 1483 msg_id = self.history[msg_id]
1479 1484 if not isinstance(msg_id, basestring):
1480 1485 raise TypeError("msg_ids must be str, not %r"%msg_id)
1481 1486 theids.append(msg_id)
1482 1487
1483 1488 completed = []
1484 1489 local_results = {}
1485 1490
1486 1491 # comment this block out to temporarily disable local shortcut:
1487 1492 for msg_id in theids:
1488 1493 if msg_id in self.results:
1489 1494 completed.append(msg_id)
1490 1495 local_results[msg_id] = self.results[msg_id]
1491 1496 theids.remove(msg_id)
1492 1497
1493 1498 if theids: # some not locally cached
1494 1499 content = dict(msg_ids=theids, status_only=status_only)
1495 1500 msg = self.session.send(self._query_socket, "result_request", content=content)
1496 1501 zmq.select([self._query_socket], [], [])
1497 1502 idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK)
1498 1503 if self.debug:
1499 1504 pprint(msg)
1500 1505 content = msg['content']
1501 1506 if content['status'] != 'ok':
1502 1507 raise self._unwrap_exception(content)
1503 1508 buffers = msg['buffers']
1504 1509 else:
1505 1510 content = dict(completed=[],pending=[])
1506 1511
1507 1512 content['completed'].extend(completed)
1508 1513
1509 1514 if status_only:
1510 1515 return content
1511 1516
1512 1517 failures = []
1513 1518 # load cached results into result:
1514 1519 content.update(local_results)
1515 1520
1516 1521 # update cache with results:
1517 1522 for msg_id in sorted(theids):
1518 1523 if msg_id in content['completed']:
1519 1524 rec = content[msg_id]
1520 1525 parent = rec['header']
1521 1526 header = rec['result_header']
1522 1527 rcontent = rec['result_content']
1523 1528 iodict = rec['io']
1524 1529 if isinstance(rcontent, str):
1525 1530 rcontent = self.session.unpack(rcontent)
1526 1531
1527 1532 md = self.metadata[msg_id]
1528 1533 md_msg = dict(
1529 1534 content=rcontent,
1530 1535 parent_header=parent,
1531 1536 header=header,
1532 1537 metadata=rec['result_metadata'],
1533 1538 )
1534 1539 md.update(self._extract_metadata(md_msg))
1535 1540 if rec.get('received'):
1536 1541 md['received'] = rec['received']
1537 1542 md.update(iodict)
1538 1543
1539 1544 if rcontent['status'] == 'ok':
1540 1545 if header['msg_type'] == 'apply_reply':
1541 res,buffers = util.unserialize_object(buffers)
1546 res,buffers = serialize.unserialize_object(buffers)
1542 1547 elif header['msg_type'] == 'execute_reply':
1543 1548 res = ExecuteReply(msg_id, rcontent, md)
1544 1549 else:
1545 1550 raise KeyError("unhandled msg type: %r" % header[msg_type])
1546 1551 else:
1547 1552 res = self._unwrap_exception(rcontent)
1548 1553 failures.append(res)
1549 1554
1550 1555 self.results[msg_id] = res
1551 1556 content[msg_id] = res
1552 1557
1553 1558 if len(theids) == 1 and failures:
1554 1559 raise failures[0]
1555 1560
1556 1561 error.collect_exceptions(failures, "result_status")
1557 1562 return content
1558 1563
1559 1564 @spin_first
1560 1565 def queue_status(self, targets='all', verbose=False):
1561 1566 """Fetch the status of engine queues.
1562 1567
1563 1568 Parameters
1564 1569 ----------
1565 1570
1566 1571 targets : int/str/list of ints/strs
1567 1572 the engines whose states are to be queried.
1568 1573 default : all
1569 1574 verbose : bool
1570 1575 Whether to return lengths only, or lists of ids for each element
1571 1576 """
1572 1577 if targets == 'all':
1573 1578 # allow 'all' to be evaluated on the engine
1574 1579 engine_ids = None
1575 1580 else:
1576 1581 engine_ids = self._build_targets(targets)[1]
1577 1582 content = dict(targets=engine_ids, verbose=verbose)
1578 1583 self.session.send(self._query_socket, "queue_request", content=content)
1579 1584 idents,msg = self.session.recv(self._query_socket, 0)
1580 1585 if self.debug:
1581 1586 pprint(msg)
1582 1587 content = msg['content']
1583 1588 status = content.pop('status')
1584 1589 if status != 'ok':
1585 1590 raise self._unwrap_exception(content)
1586 1591 content = rekey(content)
1587 1592 if isinstance(targets, int):
1588 1593 return content[targets]
1589 1594 else:
1590 1595 return content
1591 1596
1592 1597 @spin_first
1593 1598 def purge_results(self, jobs=[], targets=[]):
1594 1599 """Tell the Hub to forget results.
1595 1600
1596 1601 Individual results can be purged by msg_id, or the entire
1597 1602 history of specific targets can be purged.
1598 1603
1599 1604 Use `purge_results('all')` to scrub everything from the Hub's db.
1600 1605
1601 1606 Parameters
1602 1607 ----------
1603 1608
1604 1609 jobs : str or list of str or AsyncResult objects
1605 1610 the msg_ids whose results should be forgotten.
1606 1611 targets : int/str/list of ints/strs
1607 1612 The targets, by int_id, whose entire history is to be purged.
1608 1613
1609 1614 default : None
1610 1615 """
1611 1616 if not targets and not jobs:
1612 1617 raise ValueError("Must specify at least one of `targets` and `jobs`")
1613 1618 if targets:
1614 1619 targets = self._build_targets(targets)[1]
1615 1620
1616 1621 # construct msg_ids from jobs
1617 1622 if jobs == 'all':
1618 1623 msg_ids = jobs
1619 1624 else:
1620 1625 msg_ids = []
1621 1626 if isinstance(jobs, (basestring,AsyncResult)):
1622 1627 jobs = [jobs]
1623 1628 bad_ids = filter(lambda obj: not isinstance(obj, (basestring, AsyncResult)), jobs)
1624 1629 if bad_ids:
1625 1630 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
1626 1631 for j in jobs:
1627 1632 if isinstance(j, AsyncResult):
1628 1633 msg_ids.extend(j.msg_ids)
1629 1634 else:
1630 1635 msg_ids.append(j)
1631 1636
1632 1637 content = dict(engine_ids=targets, msg_ids=msg_ids)
1633 1638 self.session.send(self._query_socket, "purge_request", content=content)
1634 1639 idents, msg = self.session.recv(self._query_socket, 0)
1635 1640 if self.debug:
1636 1641 pprint(msg)
1637 1642 content = msg['content']
1638 1643 if content['status'] != 'ok':
1639 1644 raise self._unwrap_exception(content)
1640 1645
1641 1646 @spin_first
1642 1647 def hub_history(self):
1643 1648 """Get the Hub's history
1644 1649
1645 1650 Just like the Client, the Hub has a history, which is a list of msg_ids.
1646 1651 This will contain the history of all clients, and, depending on configuration,
1647 1652 may contain history across multiple cluster sessions.
1648 1653
1649 1654 Any msg_id returned here is a valid argument to `get_result`.
1650 1655
1651 1656 Returns
1652 1657 -------
1653 1658
1654 1659 msg_ids : list of strs
1655 1660 list of all msg_ids, ordered by task submission time.
1656 1661 """
1657 1662
1658 1663 self.session.send(self._query_socket, "history_request", content={})
1659 1664 idents, msg = self.session.recv(self._query_socket, 0)
1660 1665
1661 1666 if self.debug:
1662 1667 pprint(msg)
1663 1668 content = msg['content']
1664 1669 if content['status'] != 'ok':
1665 1670 raise self._unwrap_exception(content)
1666 1671 else:
1667 1672 return content['history']
1668 1673
1669 1674 @spin_first
1670 1675 def db_query(self, query, keys=None):
1671 1676 """Query the Hub's TaskRecord database
1672 1677
1673 1678 This will return a list of task record dicts that match `query`
1674 1679
1675 1680 Parameters
1676 1681 ----------
1677 1682
1678 1683 query : mongodb query dict
1679 1684 The search dict. See mongodb query docs for details.
1680 1685 keys : list of strs [optional]
1681 1686 The subset of keys to be returned. The default is to fetch everything but buffers.
1682 1687 'msg_id' will *always* be included.
1683 1688 """
1684 1689 if isinstance(keys, basestring):
1685 1690 keys = [keys]
1686 1691 content = dict(query=query, keys=keys)
1687 1692 self.session.send(self._query_socket, "db_request", content=content)
1688 1693 idents, msg = self.session.recv(self._query_socket, 0)
1689 1694 if self.debug:
1690 1695 pprint(msg)
1691 1696 content = msg['content']
1692 1697 if content['status'] != 'ok':
1693 1698 raise self._unwrap_exception(content)
1694 1699
1695 1700 records = content['records']
1696 1701
1697 1702 buffer_lens = content['buffer_lens']
1698 1703 result_buffer_lens = content['result_buffer_lens']
1699 1704 buffers = msg['buffers']
1700 1705 has_bufs = buffer_lens is not None
1701 1706 has_rbufs = result_buffer_lens is not None
1702 1707 for i,rec in enumerate(records):
1703 1708 # relink buffers
1704 1709 if has_bufs:
1705 1710 blen = buffer_lens[i]
1706 1711 rec['buffers'], buffers = buffers[:blen],buffers[blen:]
1707 1712 if has_rbufs:
1708 1713 blen = result_buffer_lens[i]
1709 1714 rec['result_buffers'], buffers = buffers[:blen],buffers[blen:]
1710 1715
1711 1716 return records
1712 1717
1713 1718 __all__ = [ 'Client' ]
@@ -1,1412 +1,1414
1 1 """The IPython Controller Hub with 0MQ
2 2 This is the master object that handles connections from engines and clients,
3 3 and monitors traffic through the various queues.
4 4
5 5 Authors:
6 6
7 7 * Min RK
8 8 """
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2010-2011 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19 from __future__ import print_function
20 20
21 21 import json
22 22 import os
23 23 import sys
24 24 import time
25 25 from datetime import datetime
26 26
27 27 import zmq
28 28 from zmq.eventloop import ioloop
29 29 from zmq.eventloop.zmqstream import ZMQStream
30 30
31 31 # internal:
32 32 from IPython.utils.importstring import import_item
33 33 from IPython.utils.py3compat import cast_bytes
34 34 from IPython.utils.traitlets import (
35 35 HasTraits, Instance, Integer, Unicode, Dict, Set, Tuple, CBytes, DottedObjectName
36 36 )
37 37
38 38 from IPython.parallel import error, util
39 39 from IPython.parallel.factory import RegistrationFactory
40 40
41 41 from IPython.zmq.session import SessionFactory
42 42
43 43 from .heartmonitor import HeartMonitor
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Code
47 47 #-----------------------------------------------------------------------------
48 48
49 49 def _passer(*args, **kwargs):
50 50 return
51 51
52 52 def _printer(*args, **kwargs):
53 53 print (args)
54 54 print (kwargs)
55 55
56 56 def empty_record():
57 57 """Return an empty dict with all record keys."""
58 58 return {
59 59 'msg_id' : None,
60 60 'header' : None,
61 61 'metadata' : None,
62 62 'content': None,
63 63 'buffers': None,
64 64 'submitted': None,
65 65 'client_uuid' : None,
66 66 'engine_uuid' : None,
67 67 'started': None,
68 68 'completed': None,
69 69 'resubmitted': None,
70 70 'received': None,
71 71 'result_header' : None,
72 72 'result_metadata' : None,
73 73 'result_content' : None,
74 74 'result_buffers' : None,
75 75 'queue' : None,
76 76 'pyin' : None,
77 77 'pyout': None,
78 78 'pyerr': None,
79 79 'stdout': '',
80 80 'stderr': '',
81 81 }
82 82
83 83 def init_record(msg):
84 84 """Initialize a TaskRecord based on a request."""
85 85 header = msg['header']
86 86 return {
87 87 'msg_id' : header['msg_id'],
88 88 'header' : header,
89 89 'content': msg['content'],
90 90 'metadata': msg['metadata'],
91 91 'buffers': msg['buffers'],
92 92 'submitted': header['date'],
93 93 'client_uuid' : None,
94 94 'engine_uuid' : None,
95 95 'started': None,
96 96 'completed': None,
97 97 'resubmitted': None,
98 98 'received': None,
99 99 'result_header' : None,
100 100 'result_metadata': None,
101 101 'result_content' : None,
102 102 'result_buffers' : None,
103 103 'queue' : None,
104 104 'pyin' : None,
105 105 'pyout': None,
106 106 'pyerr': None,
107 107 'stdout': '',
108 108 'stderr': '',
109 109 }
110 110
111 111
112 112 class EngineConnector(HasTraits):
113 113 """A simple object for accessing the various zmq connections of an object.
114 114 Attributes are:
115 115 id (int): engine ID
116 116 uuid (unicode): engine UUID
117 117 pending: set of msg_ids
118 118 stallback: DelayedCallback for stalled registration
119 119 """
120 120
121 121 id = Integer(0)
122 122 uuid = Unicode()
123 123 pending = Set()
124 124 stallback = Instance(ioloop.DelayedCallback)
125 125
126 126
127 127 _db_shortcuts = {
128 128 'sqlitedb' : 'IPython.parallel.controller.sqlitedb.SQLiteDB',
129 129 'mongodb' : 'IPython.parallel.controller.mongodb.MongoDB',
130 130 'dictdb' : 'IPython.parallel.controller.dictdb.DictDB',
131 131 'nodb' : 'IPython.parallel.controller.dictdb.NoDB',
132 132 }
133 133
134 134 class HubFactory(RegistrationFactory):
135 135 """The Configurable for setting up a Hub."""
136 136
137 137 # port-pairs for monitoredqueues:
138 138 hb = Tuple(Integer,Integer,config=True,
139 139 help="""PUB/ROUTER Port pair for Engine heartbeats""")
140 140 def _hb_default(self):
141 141 return tuple(util.select_random_ports(2))
142 142
143 143 mux = Tuple(Integer,Integer,config=True,
144 144 help="""Client/Engine Port pair for MUX queue""")
145 145
146 146 def _mux_default(self):
147 147 return tuple(util.select_random_ports(2))
148 148
149 149 task = Tuple(Integer,Integer,config=True,
150 150 help="""Client/Engine Port pair for Task queue""")
151 151 def _task_default(self):
152 152 return tuple(util.select_random_ports(2))
153 153
154 154 control = Tuple(Integer,Integer,config=True,
155 155 help="""Client/Engine Port pair for Control queue""")
156 156
157 157 def _control_default(self):
158 158 return tuple(util.select_random_ports(2))
159 159
160 160 iopub = Tuple(Integer,Integer,config=True,
161 161 help="""Client/Engine Port pair for IOPub relay""")
162 162
163 163 def _iopub_default(self):
164 164 return tuple(util.select_random_ports(2))
165 165
166 166 # single ports:
167 167 mon_port = Integer(config=True,
168 168 help="""Monitor (SUB) port for queue traffic""")
169 169
170 170 def _mon_port_default(self):
171 171 return util.select_random_ports(1)[0]
172 172
173 173 notifier_port = Integer(config=True,
174 174 help="""PUB port for sending engine status notifications""")
175 175
176 176 def _notifier_port_default(self):
177 177 return util.select_random_ports(1)[0]
178 178
179 179 engine_ip = Unicode('127.0.0.1', config=True,
180 180 help="IP on which to listen for engine connections. [default: loopback]")
181 181 engine_transport = Unicode('tcp', config=True,
182 182 help="0MQ transport for engine connections. [default: tcp]")
183 183
184 184 client_ip = Unicode('127.0.0.1', config=True,
185 185 help="IP on which to listen for client connections. [default: loopback]")
186 186 client_transport = Unicode('tcp', config=True,
187 187 help="0MQ transport for client connections. [default : tcp]")
188 188
189 189 monitor_ip = Unicode('127.0.0.1', config=True,
190 190 help="IP on which to listen for monitor messages. [default: loopback]")
191 191 monitor_transport = Unicode('tcp', config=True,
192 192 help="0MQ transport for monitor messages. [default : tcp]")
193 193
194 194 monitor_url = Unicode('')
195 195
196 196 db_class = DottedObjectName('NoDB',
197 197 config=True, help="""The class to use for the DB backend
198 198
199 199 Options include:
200 200
201 201 SQLiteDB: SQLite
202 202 MongoDB : use MongoDB
203 203 DictDB : in-memory storage (fastest, but be mindful of memory growth of the Hub)
204 204 NoDB : disable database altogether (default)
205 205
206 206 """)
207 207
208 208 # not configurable
209 209 db = Instance('IPython.parallel.controller.dictdb.BaseDB')
210 210 heartmonitor = Instance('IPython.parallel.controller.heartmonitor.HeartMonitor')
211 211
212 212 def _ip_changed(self, name, old, new):
213 213 self.engine_ip = new
214 214 self.client_ip = new
215 215 self.monitor_ip = new
216 216 self._update_monitor_url()
217 217
218 218 def _update_monitor_url(self):
219 219 self.monitor_url = "%s://%s:%i" % (self.monitor_transport, self.monitor_ip, self.mon_port)
220 220
221 221 def _transport_changed(self, name, old, new):
222 222 self.engine_transport = new
223 223 self.client_transport = new
224 224 self.monitor_transport = new
225 225 self._update_monitor_url()
226 226
227 227 def __init__(self, **kwargs):
228 228 super(HubFactory, self).__init__(**kwargs)
229 229 self._update_monitor_url()
230 230
231 231
232 232 def construct(self):
233 233 self.init_hub()
234 234
235 235 def start(self):
236 236 self.heartmonitor.start()
237 237 self.log.info("Heartmonitor started")
238 238
239 239 def client_url(self, channel):
240 240 """return full zmq url for a named client channel"""
241 241 return "%s://%s:%i" % (self.client_transport, self.client_ip, self.client_info[channel])
242 242
243 243 def engine_url(self, channel):
244 244 """return full zmq url for a named engine channel"""
245 245 return "%s://%s:%i" % (self.engine_transport, self.engine_ip, self.engine_info[channel])
246 246
247 247 def init_hub(self):
248 248 """construct Hub object"""
249 249
250 250 ctx = self.context
251 251 loop = self.loop
252 252
253 253 try:
254 254 scheme = self.config.TaskScheduler.scheme_name
255 255 except AttributeError:
256 256 from .scheduler import TaskScheduler
257 257 scheme = TaskScheduler.scheme_name.get_default_value()
258 258
259 259 # build connection dicts
260 260 engine = self.engine_info = {
261 261 'interface' : "%s://%s" % (self.engine_transport, self.engine_ip),
262 262 'registration' : self.regport,
263 263 'control' : self.control[1],
264 264 'mux' : self.mux[1],
265 265 'hb_ping' : self.hb[0],
266 266 'hb_pong' : self.hb[1],
267 267 'task' : self.task[1],
268 268 'iopub' : self.iopub[1],
269 269 }
270 270
271 271 client = self.client_info = {
272 272 'interface' : "%s://%s" % (self.client_transport, self.client_ip),
273 273 'registration' : self.regport,
274 274 'control' : self.control[0],
275 275 'mux' : self.mux[0],
276 276 'task' : self.task[0],
277 277 'task_scheme' : scheme,
278 278 'iopub' : self.iopub[0],
279 279 'notification' : self.notifier_port,
280 280 }
281 281
282 282 self.log.debug("Hub engine addrs: %s", self.engine_info)
283 283 self.log.debug("Hub client addrs: %s", self.client_info)
284 284
285 285 # Registrar socket
286 286 q = ZMQStream(ctx.socket(zmq.ROUTER), loop)
287 287 q.bind(self.client_url('registration'))
288 288 self.log.info("Hub listening on %s for registration.", self.client_url('registration'))
289 289 if self.client_ip != self.engine_ip:
290 290 q.bind(self.engine_url('registration'))
291 291 self.log.info("Hub listening on %s for registration.", self.engine_url('registration'))
292 292
293 293 ### Engine connections ###
294 294
295 295 # heartbeat
296 296 hpub = ctx.socket(zmq.PUB)
297 297 hpub.bind(self.engine_url('hb_ping'))
298 298 hrep = ctx.socket(zmq.ROUTER)
299 299 hrep.bind(self.engine_url('hb_pong'))
300 300 self.heartmonitor = HeartMonitor(loop=loop, config=self.config, log=self.log,
301 301 pingstream=ZMQStream(hpub,loop),
302 302 pongstream=ZMQStream(hrep,loop)
303 303 )
304 304
305 305 ### Client connections ###
306 306
307 307 # Notifier socket
308 308 n = ZMQStream(ctx.socket(zmq.PUB), loop)
309 309 n.bind(self.client_url('notification'))
310 310
311 311 ### build and launch the queues ###
312 312
313 313 # monitor socket
314 314 sub = ctx.socket(zmq.SUB)
315 315 sub.setsockopt(zmq.SUBSCRIBE, b"")
316 316 sub.bind(self.monitor_url)
317 317 sub.bind('inproc://monitor')
318 318 sub = ZMQStream(sub, loop)
319 319
320 320 # connect the db
321 321 db_class = _db_shortcuts.get(self.db_class.lower(), self.db_class)
322 322 self.log.info('Hub using DB backend: %r', (db_class.split('.')[-1]))
323 323 self.db = import_item(str(db_class))(session=self.session.session,
324 324 config=self.config, log=self.log)
325 325 time.sleep(.25)
326 326
327 327 # resubmit stream
328 328 r = ZMQStream(ctx.socket(zmq.DEALER), loop)
329 329 url = util.disambiguate_url(self.client_url('task'))
330 330 r.connect(url)
331 331
332 332 self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor,
333 333 query=q, notifier=n, resubmit=r, db=self.db,
334 334 engine_info=self.engine_info, client_info=self.client_info,
335 335 log=self.log)
336 336
337 337
338 338 class Hub(SessionFactory):
339 339 """The IPython Controller Hub with 0MQ connections
340 340
341 341 Parameters
342 342 ==========
343 343 loop: zmq IOLoop instance
344 344 session: Session object
345 345 <removed> context: zmq context for creating new connections (?)
346 346 queue: ZMQStream for monitoring the command queue (SUB)
347 347 query: ZMQStream for engine registration and client queries requests (ROUTER)
348 348 heartbeat: HeartMonitor object checking the pulse of the engines
349 349 notifier: ZMQStream for broadcasting engine registration changes (PUB)
350 350 db: connection to db for out of memory logging of commands
351 351 NotImplemented
352 352 engine_info: dict of zmq connection information for engines to connect
353 353 to the queues.
354 354 client_info: dict of zmq connection information for engines to connect
355 355 to the queues.
356 356 """
357 357
358 358 engine_state_file = Unicode()
359 359
360 360 # internal data structures:
361 361 ids=Set() # engine IDs
362 362 keytable=Dict()
363 363 by_ident=Dict()
364 364 engines=Dict()
365 365 clients=Dict()
366 366 hearts=Dict()
367 367 pending=Set()
368 368 queues=Dict() # pending msg_ids keyed by engine_id
369 369 tasks=Dict() # pending msg_ids submitted as tasks, keyed by client_id
370 370 completed=Dict() # completed msg_ids keyed by engine_id
371 371 all_completed=Set() # completed msg_ids keyed by engine_id
372 372 dead_engines=Set() # completed msg_ids keyed by engine_id
373 373 unassigned=Set() # set of task msg_ds not yet assigned a destination
374 374 incoming_registrations=Dict()
375 375 registration_timeout=Integer()
376 376 _idcounter=Integer(0)
377 377
378 378 # objects from constructor:
379 379 query=Instance(ZMQStream)
380 380 monitor=Instance(ZMQStream)
381 381 notifier=Instance(ZMQStream)
382 382 resubmit=Instance(ZMQStream)
383 383 heartmonitor=Instance(HeartMonitor)
384 384 db=Instance(object)
385 385 client_info=Dict()
386 386 engine_info=Dict()
387 387
388 388
389 389 def __init__(self, **kwargs):
390 390 """
391 391 # universal:
392 392 loop: IOLoop for creating future connections
393 393 session: streamsession for sending serialized data
394 394 # engine:
395 395 queue: ZMQStream for monitoring queue messages
396 396 query: ZMQStream for engine+client registration and client requests
397 397 heartbeat: HeartMonitor object for tracking engines
398 398 # extra:
399 399 db: ZMQStream for db connection (NotImplemented)
400 400 engine_info: zmq address/protocol dict for engine connections
401 401 client_info: zmq address/protocol dict for client connections
402 402 """
403 403
404 404 super(Hub, self).__init__(**kwargs)
405 405 self.registration_timeout = max(5000, 2*self.heartmonitor.period)
406 406
407 407 # register our callbacks
408 408 self.query.on_recv(self.dispatch_query)
409 409 self.monitor.on_recv(self.dispatch_monitor_traffic)
410 410
411 411 self.heartmonitor.add_heart_failure_handler(self.handle_heart_failure)
412 412 self.heartmonitor.add_new_heart_handler(self.handle_new_heart)
413 413
414 414 self.monitor_handlers = {b'in' : self.save_queue_request,
415 415 b'out': self.save_queue_result,
416 416 b'intask': self.save_task_request,
417 417 b'outtask': self.save_task_result,
418 418 b'tracktask': self.save_task_destination,
419 419 b'incontrol': _passer,
420 420 b'outcontrol': _passer,
421 421 b'iopub': self.save_iopub_message,
422 422 }
423 423
424 424 self.query_handlers = {'queue_request': self.queue_status,
425 425 'result_request': self.get_results,
426 426 'history_request': self.get_history,
427 427 'db_request': self.db_query,
428 428 'purge_request': self.purge_results,
429 429 'load_request': self.check_load,
430 430 'resubmit_request': self.resubmit_task,
431 431 'shutdown_request': self.shutdown_request,
432 432 'registration_request' : self.register_engine,
433 433 'unregistration_request' : self.unregister_engine,
434 434 'connection_request': self.connection_request,
435 435 }
436 436
437 437 # ignore resubmit replies
438 438 self.resubmit.on_recv(lambda msg: None, copy=False)
439 439
440 440 self.log.info("hub::created hub")
441 441
442 442 @property
443 443 def _next_id(self):
444 444 """gemerate a new ID.
445 445
446 446 No longer reuse old ids, just count from 0."""
447 447 newid = self._idcounter
448 448 self._idcounter += 1
449 449 return newid
450 450 # newid = 0
451 451 # incoming = [id[0] for id in self.incoming_registrations.itervalues()]
452 452 # # print newid, self.ids, self.incoming_registrations
453 453 # while newid in self.ids or newid in incoming:
454 454 # newid += 1
455 455 # return newid
456 456
457 457 #-----------------------------------------------------------------------------
458 458 # message validation
459 459 #-----------------------------------------------------------------------------
460 460
461 461 def _validate_targets(self, targets):
462 462 """turn any valid targets argument into a list of integer ids"""
463 463 if targets is None:
464 464 # default to all
465 465 return self.ids
466 466
467 467 if isinstance(targets, (int,str,unicode)):
468 468 # only one target specified
469 469 targets = [targets]
470 470 _targets = []
471 471 for t in targets:
472 472 # map raw identities to ids
473 473 if isinstance(t, (str,unicode)):
474 474 t = self.by_ident.get(cast_bytes(t), t)
475 475 _targets.append(t)
476 476 targets = _targets
477 477 bad_targets = [ t for t in targets if t not in self.ids ]
478 478 if bad_targets:
479 479 raise IndexError("No Such Engine: %r" % bad_targets)
480 480 if not targets:
481 481 raise IndexError("No Engines Registered")
482 482 return targets
483 483
484 484 #-----------------------------------------------------------------------------
485 485 # dispatch methods (1 per stream)
486 486 #-----------------------------------------------------------------------------
487 487
488 488
489 489 @util.log_errors
490 490 def dispatch_monitor_traffic(self, msg):
491 491 """all ME and Task queue messages come through here, as well as
492 492 IOPub traffic."""
493 493 self.log.debug("monitor traffic: %r", msg[0])
494 494 switch = msg[0]
495 495 try:
496 496 idents, msg = self.session.feed_identities(msg[1:])
497 497 except ValueError:
498 498 idents=[]
499 499 if not idents:
500 500 self.log.error("Monitor message without topic: %r", msg)
501 501 return
502 502 handler = self.monitor_handlers.get(switch, None)
503 503 if handler is not None:
504 504 handler(idents, msg)
505 505 else:
506 506 self.log.error("Unrecognized monitor topic: %r", switch)
507 507
508 508
509 509 @util.log_errors
510 510 def dispatch_query(self, msg):
511 511 """Route registration requests and queries from clients."""
512 512 try:
513 513 idents, msg = self.session.feed_identities(msg)
514 514 except ValueError:
515 515 idents = []
516 516 if not idents:
517 517 self.log.error("Bad Query Message: %r", msg)
518 518 return
519 519 client_id = idents[0]
520 520 try:
521 521 msg = self.session.unserialize(msg, content=True)
522 522 except Exception:
523 523 content = error.wrap_exception()
524 524 self.log.error("Bad Query Message: %r", msg, exc_info=True)
525 525 self.session.send(self.query, "hub_error", ident=client_id,
526 526 content=content)
527 527 return
528 528 # print client_id, header, parent, content
529 529 #switch on message type:
530 530 msg_type = msg['header']['msg_type']
531 531 self.log.info("client::client %r requested %r", client_id, msg_type)
532 532 handler = self.query_handlers.get(msg_type, None)
533 533 try:
534 534 assert handler is not None, "Bad Message Type: %r" % msg_type
535 535 except:
536 536 content = error.wrap_exception()
537 537 self.log.error("Bad Message Type: %r", msg_type, exc_info=True)
538 538 self.session.send(self.query, "hub_error", ident=client_id,
539 539 content=content)
540 540 return
541 541
542 542 else:
543 543 handler(idents, msg)
544 544
545 545 def dispatch_db(self, msg):
546 546 """"""
547 547 raise NotImplementedError
548 548
549 549 #---------------------------------------------------------------------------
550 550 # handler methods (1 per event)
551 551 #---------------------------------------------------------------------------
552 552
553 553 #----------------------- Heartbeat --------------------------------------
554 554
555 555 def handle_new_heart(self, heart):
556 556 """handler to attach to heartbeater.
557 557 Called when a new heart starts to beat.
558 558 Triggers completion of registration."""
559 559 self.log.debug("heartbeat::handle_new_heart(%r)", heart)
560 560 if heart not in self.incoming_registrations:
561 561 self.log.info("heartbeat::ignoring new heart: %r", heart)
562 562 else:
563 563 self.finish_registration(heart)
564 564
565 565
566 566 def handle_heart_failure(self, heart):
567 567 """handler to attach to heartbeater.
568 568 called when a previously registered heart fails to respond to beat request.
569 569 triggers unregistration"""
570 570 self.log.debug("heartbeat::handle_heart_failure(%r)", heart)
571 571 eid = self.hearts.get(heart, None)
572 572 uuid = self.engines[eid].uuid
573 573 if eid is None or self.keytable[eid] in self.dead_engines:
574 574 self.log.info("heartbeat::ignoring heart failure %r (not an engine or already dead)", heart)
575 575 else:
576 576 self.unregister_engine(heart, dict(content=dict(id=eid, queue=uuid)))
577 577
578 578 #----------------------- MUX Queue Traffic ------------------------------
579 579
580 580 def save_queue_request(self, idents, msg):
581 581 if len(idents) < 2:
582 582 self.log.error("invalid identity prefix: %r", idents)
583 583 return
584 584 queue_id, client_id = idents[:2]
585 585 try:
586 586 msg = self.session.unserialize(msg)
587 587 except Exception:
588 588 self.log.error("queue::client %r sent invalid message to %r: %r", client_id, queue_id, msg, exc_info=True)
589 589 return
590 590
591 591 eid = self.by_ident.get(queue_id, None)
592 592 if eid is None:
593 593 self.log.error("queue::target %r not registered", queue_id)
594 594 self.log.debug("queue:: valid are: %r", self.by_ident.keys())
595 595 return
596 596 record = init_record(msg)
597 597 msg_id = record['msg_id']
598 598 self.log.info("queue::client %r submitted request %r to %s", client_id, msg_id, eid)
599 599 # Unicode in records
600 600 record['engine_uuid'] = queue_id.decode('ascii')
601 601 record['client_uuid'] = msg['header']['session']
602 602 record['queue'] = 'mux'
603 603
604 604 try:
605 605 # it's posible iopub arrived first:
606 606 existing = self.db.get_record(msg_id)
607 607 for key,evalue in existing.iteritems():
608 608 rvalue = record.get(key, None)
609 609 if evalue and rvalue and evalue != rvalue:
610 610 self.log.warn("conflicting initial state for record: %r:%r <%r> %r", msg_id, rvalue, key, evalue)
611 611 elif evalue and not rvalue:
612 612 record[key] = evalue
613 613 try:
614 614 self.db.update_record(msg_id, record)
615 615 except Exception:
616 616 self.log.error("DB Error updating record %r", msg_id, exc_info=True)
617 617 except KeyError:
618 618 try:
619 619 self.db.add_record(msg_id, record)
620 620 except Exception:
621 621 self.log.error("DB Error adding record %r", msg_id, exc_info=True)
622 622
623 623
624 624 self.pending.add(msg_id)
625 625 self.queues[eid].append(msg_id)
626 626
627 627 def save_queue_result(self, idents, msg):
628 628 if len(idents) < 2:
629 629 self.log.error("invalid identity prefix: %r", idents)
630 630 return
631 631
632 632 client_id, queue_id = idents[:2]
633 633 try:
634 634 msg = self.session.unserialize(msg)
635 635 except Exception:
636 636 self.log.error("queue::engine %r sent invalid message to %r: %r",
637 637 queue_id, client_id, msg, exc_info=True)
638 638 return
639 639
640 640 eid = self.by_ident.get(queue_id, None)
641 641 if eid is None:
642 642 self.log.error("queue::unknown engine %r is sending a reply: ", queue_id)
643 643 return
644 644
645 645 parent = msg['parent_header']
646 646 if not parent:
647 647 return
648 648 msg_id = parent['msg_id']
649 649 if msg_id in self.pending:
650 650 self.pending.remove(msg_id)
651 651 self.all_completed.add(msg_id)
652 652 self.queues[eid].remove(msg_id)
653 653 self.completed[eid].append(msg_id)
654 654 self.log.info("queue::request %r completed on %s", msg_id, eid)
655 655 elif msg_id not in self.all_completed:
656 656 # it could be a result from a dead engine that died before delivering the
657 657 # result
658 658 self.log.warn("queue:: unknown msg finished %r", msg_id)
659 659 return
660 660 # update record anyway, because the unregistration could have been premature
661 661 rheader = msg['header']
662 662 md = msg['metadata']
663 663 completed = rheader['date']
664 664 started = md.get('started', None)
665 665 result = {
666 666 'result_header' : rheader,
667 667 'result_metadata': md,
668 668 'result_content': msg['content'],
669 669 'received': datetime.now(),
670 670 'started' : started,
671 671 'completed' : completed
672 672 }
673 673
674 674 result['result_buffers'] = msg['buffers']
675 675 try:
676 676 self.db.update_record(msg_id, result)
677 677 except Exception:
678 678 self.log.error("DB Error updating record %r", msg_id, exc_info=True)
679 679
680 680
681 681 #--------------------- Task Queue Traffic ------------------------------
682 682
683 683 def save_task_request(self, idents, msg):
684 684 """Save the submission of a task."""
685 685 client_id = idents[0]
686 686
687 687 try:
688 688 msg = self.session.unserialize(msg)
689 689 except Exception:
690 690 self.log.error("task::client %r sent invalid task message: %r",
691 691 client_id, msg, exc_info=True)
692 692 return
693 693 record = init_record(msg)
694 694
695 695 record['client_uuid'] = msg['header']['session']
696 696 record['queue'] = 'task'
697 697 header = msg['header']
698 698 msg_id = header['msg_id']
699 699 self.pending.add(msg_id)
700 700 self.unassigned.add(msg_id)
701 701 try:
702 702 # it's posible iopub arrived first:
703 703 existing = self.db.get_record(msg_id)
704 704 if existing['resubmitted']:
705 705 for key in ('submitted', 'client_uuid', 'buffers'):
706 706 # don't clobber these keys on resubmit
707 707 # submitted and client_uuid should be different
708 708 # and buffers might be big, and shouldn't have changed
709 709 record.pop(key)
710 710 # still check content,header which should not change
711 711 # but are not expensive to compare as buffers
712 712
713 713 for key,evalue in existing.iteritems():
714 714 if key.endswith('buffers'):
715 715 # don't compare buffers
716 716 continue
717 717 rvalue = record.get(key, None)
718 718 if evalue and rvalue and evalue != rvalue:
719 719 self.log.warn("conflicting initial state for record: %r:%r <%r> %r", msg_id, rvalue, key, evalue)
720 720 elif evalue and not rvalue:
721 721 record[key] = evalue
722 722 try:
723 723 self.db.update_record(msg_id, record)
724 724 except Exception:
725 725 self.log.error("DB Error updating record %r", msg_id, exc_info=True)
726 726 except KeyError:
727 727 try:
728 728 self.db.add_record(msg_id, record)
729 729 except Exception:
730 730 self.log.error("DB Error adding record %r", msg_id, exc_info=True)
731 731 except Exception:
732 732 self.log.error("DB Error saving task request %r", msg_id, exc_info=True)
733 733
734 734 def save_task_result(self, idents, msg):
735 735 """save the result of a completed task."""
736 736 client_id = idents[0]
737 737 try:
738 738 msg = self.session.unserialize(msg)
739 739 except Exception:
740 740 self.log.error("task::invalid task result message send to %r: %r",
741 741 client_id, msg, exc_info=True)
742 742 return
743 743
744 744 parent = msg['parent_header']
745 745 if not parent:
746 746 # print msg
747 747 self.log.warn("Task %r had no parent!", msg)
748 748 return
749 749 msg_id = parent['msg_id']
750 750 if msg_id in self.unassigned:
751 751 self.unassigned.remove(msg_id)
752 752
753 753 header = msg['header']
754 754 md = msg['metadata']
755 755 engine_uuid = md.get('engine', u'')
756 756 eid = self.by_ident.get(cast_bytes(engine_uuid), None)
757 757
758 758 status = md.get('status', None)
759 759
760 760 if msg_id in self.pending:
761 761 self.log.info("task::task %r finished on %s", msg_id, eid)
762 762 self.pending.remove(msg_id)
763 763 self.all_completed.add(msg_id)
764 764 if eid is not None:
765 765 if status != 'aborted':
766 766 self.completed[eid].append(msg_id)
767 767 if msg_id in self.tasks[eid]:
768 768 self.tasks[eid].remove(msg_id)
769 769 completed = header['date']
770 770 started = md.get('started', None)
771 771 result = {
772 772 'result_header' : header,
773 773 'result_metadata': msg['metadata'],
774 774 'result_content': msg['content'],
775 775 'started' : started,
776 776 'completed' : completed,
777 777 'received' : datetime.now(),
778 778 'engine_uuid': engine_uuid,
779 779 }
780 780
781 781 result['result_buffers'] = msg['buffers']
782 782 try:
783 783 self.db.update_record(msg_id, result)
784 784 except Exception:
785 785 self.log.error("DB Error saving task request %r", msg_id, exc_info=True)
786 786
787 787 else:
788 788 self.log.debug("task::unknown task %r finished", msg_id)
789 789
790 790 def save_task_destination(self, idents, msg):
791 791 try:
792 792 msg = self.session.unserialize(msg, content=True)
793 793 except Exception:
794 794 self.log.error("task::invalid task tracking message", exc_info=True)
795 795 return
796 796 content = msg['content']
797 797 # print (content)
798 798 msg_id = content['msg_id']
799 799 engine_uuid = content['engine_id']
800 800 eid = self.by_ident[cast_bytes(engine_uuid)]
801 801
802 802 self.log.info("task::task %r arrived on %r", msg_id, eid)
803 803 if msg_id in self.unassigned:
804 804 self.unassigned.remove(msg_id)
805 805 # else:
806 806 # self.log.debug("task::task %r not listed as MIA?!"%(msg_id))
807 807
808 808 self.tasks[eid].append(msg_id)
809 809 # self.pending[msg_id][1].update(received=datetime.now(),engine=(eid,engine_uuid))
810 810 try:
811 811 self.db.update_record(msg_id, dict(engine_uuid=engine_uuid))
812 812 except Exception:
813 813 self.log.error("DB Error saving task destination %r", msg_id, exc_info=True)
814 814
815 815
816 816 def mia_task_request(self, idents, msg):
817 817 raise NotImplementedError
818 818 client_id = idents[0]
819 819 # content = dict(mia=self.mia,status='ok')
820 820 # self.session.send('mia_reply', content=content, idents=client_id)
821 821
822 822
823 823 #--------------------- IOPub Traffic ------------------------------
824 824
825 825 def save_iopub_message(self, topics, msg):
826 826 """save an iopub message into the db"""
827 827 # print (topics)
828 828 try:
829 829 msg = self.session.unserialize(msg, content=True)
830 830 except Exception:
831 831 self.log.error("iopub::invalid IOPub message", exc_info=True)
832 832 return
833 833
834 834 parent = msg['parent_header']
835 835 if not parent:
836 836 self.log.warn("iopub::IOPub message lacks parent: %r", msg)
837 837 return
838 838 msg_id = parent['msg_id']
839 839 msg_type = msg['header']['msg_type']
840 840 content = msg['content']
841 841
842 842 # ensure msg_id is in db
843 843 try:
844 844 rec = self.db.get_record(msg_id)
845 845 except KeyError:
846 846 rec = empty_record()
847 847 rec['msg_id'] = msg_id
848 848 self.db.add_record(msg_id, rec)
849 849 # stream
850 850 d = {}
851 851 if msg_type == 'stream':
852 852 name = content['name']
853 853 s = rec[name] or ''
854 854 d[name] = s + content['data']
855 855
856 856 elif msg_type == 'pyerr':
857 857 d['pyerr'] = content
858 858 elif msg_type == 'pyin':
859 859 d['pyin'] = content['code']
860 860 elif msg_type in ('display_data', 'pyout'):
861 861 d[msg_type] = content
862 862 elif msg_type == 'status':
863 863 pass
864 elif msg_type == 'data_pub':
865 self.log.info("ignored data_pub message for %s" % msg_id)
864 866 else:
865 867 self.log.warn("unhandled iopub msg_type: %r", msg_type)
866 868
867 869 if not d:
868 870 return
869 871
870 872 try:
871 873 self.db.update_record(msg_id, d)
872 874 except Exception:
873 875 self.log.error("DB Error saving iopub message %r", msg_id, exc_info=True)
874 876
875 877
876 878
877 879 #-------------------------------------------------------------------------
878 880 # Registration requests
879 881 #-------------------------------------------------------------------------
880 882
881 883 def connection_request(self, client_id, msg):
882 884 """Reply with connection addresses for clients."""
883 885 self.log.info("client::client %r connected", client_id)
884 886 content = dict(status='ok')
885 887 jsonable = {}
886 888 for k,v in self.keytable.iteritems():
887 889 if v not in self.dead_engines:
888 890 jsonable[str(k)] = v
889 891 content['engines'] = jsonable
890 892 self.session.send(self.query, 'connection_reply', content, parent=msg, ident=client_id)
891 893
892 894 def register_engine(self, reg, msg):
893 895 """Register a new engine."""
894 896 content = msg['content']
895 897 try:
896 898 uuid = content['uuid']
897 899 except KeyError:
898 900 self.log.error("registration::queue not specified", exc_info=True)
899 901 return
900 902
901 903 eid = self._next_id
902 904
903 905 self.log.debug("registration::register_engine(%i, %r)", eid, uuid)
904 906
905 907 content = dict(id=eid,status='ok')
906 908 # check if requesting available IDs:
907 909 if cast_bytes(uuid) in self.by_ident:
908 910 try:
909 911 raise KeyError("uuid %r in use" % uuid)
910 912 except:
911 913 content = error.wrap_exception()
912 914 self.log.error("uuid %r in use", uuid, exc_info=True)
913 915 else:
914 916 for h, ec in self.incoming_registrations.iteritems():
915 917 if uuid == h:
916 918 try:
917 919 raise KeyError("heart_id %r in use" % uuid)
918 920 except:
919 921 self.log.error("heart_id %r in use", uuid, exc_info=True)
920 922 content = error.wrap_exception()
921 923 break
922 924 elif uuid == ec.uuid:
923 925 try:
924 926 raise KeyError("uuid %r in use" % uuid)
925 927 except:
926 928 self.log.error("uuid %r in use", uuid, exc_info=True)
927 929 content = error.wrap_exception()
928 930 break
929 931
930 932 msg = self.session.send(self.query, "registration_reply",
931 933 content=content,
932 934 ident=reg)
933 935
934 936 heart = cast_bytes(uuid)
935 937
936 938 if content['status'] == 'ok':
937 939 if heart in self.heartmonitor.hearts:
938 940 # already beating
939 941 self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid)
940 942 self.finish_registration(heart)
941 943 else:
942 944 purge = lambda : self._purge_stalled_registration(heart)
943 945 dc = ioloop.DelayedCallback(purge, self.registration_timeout, self.loop)
944 946 dc.start()
945 947 self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid,stallback=dc)
946 948 else:
947 949 self.log.error("registration::registration %i failed: %r", eid, content['evalue'])
948 950
949 951 return eid
950 952
951 953 def unregister_engine(self, ident, msg):
952 954 """Unregister an engine that explicitly requested to leave."""
953 955 try:
954 956 eid = msg['content']['id']
955 957 except:
956 958 self.log.error("registration::bad engine id for unregistration: %r", ident, exc_info=True)
957 959 return
958 960 self.log.info("registration::unregister_engine(%r)", eid)
959 961 # print (eid)
960 962 uuid = self.keytable[eid]
961 963 content=dict(id=eid, uuid=uuid)
962 964 self.dead_engines.add(uuid)
963 965 # self.ids.remove(eid)
964 966 # uuid = self.keytable.pop(eid)
965 967 #
966 968 # ec = self.engines.pop(eid)
967 969 # self.hearts.pop(ec.heartbeat)
968 970 # self.by_ident.pop(ec.queue)
969 971 # self.completed.pop(eid)
970 972 handleit = lambda : self._handle_stranded_msgs(eid, uuid)
971 973 dc = ioloop.DelayedCallback(handleit, self.registration_timeout, self.loop)
972 974 dc.start()
973 975 ############## TODO: HANDLE IT ################
974 976
975 977 self._save_engine_state()
976 978
977 979 if self.notifier:
978 980 self.session.send(self.notifier, "unregistration_notification", content=content)
979 981
980 982 def _handle_stranded_msgs(self, eid, uuid):
981 983 """Handle messages known to be on an engine when the engine unregisters.
982 984
983 985 It is possible that this will fire prematurely - that is, an engine will
984 986 go down after completing a result, and the client will be notified
985 987 that the result failed and later receive the actual result.
986 988 """
987 989
988 990 outstanding = self.queues[eid]
989 991
990 992 for msg_id in outstanding:
991 993 self.pending.remove(msg_id)
992 994 self.all_completed.add(msg_id)
993 995 try:
994 996 raise error.EngineError("Engine %r died while running task %r" % (eid, msg_id))
995 997 except:
996 998 content = error.wrap_exception()
997 999 # build a fake header:
998 1000 header = {}
999 1001 header['engine'] = uuid
1000 1002 header['date'] = datetime.now()
1001 1003 rec = dict(result_content=content, result_header=header, result_buffers=[])
1002 1004 rec['completed'] = header['date']
1003 1005 rec['engine_uuid'] = uuid
1004 1006 try:
1005 1007 self.db.update_record(msg_id, rec)
1006 1008 except Exception:
1007 1009 self.log.error("DB Error handling stranded msg %r", msg_id, exc_info=True)
1008 1010
1009 1011
1010 1012 def finish_registration(self, heart):
1011 1013 """Second half of engine registration, called after our HeartMonitor
1012 1014 has received a beat from the Engine's Heart."""
1013 1015 try:
1014 1016 ec = self.incoming_registrations.pop(heart)
1015 1017 except KeyError:
1016 1018 self.log.error("registration::tried to finish nonexistant registration", exc_info=True)
1017 1019 return
1018 1020 self.log.info("registration::finished registering engine %i:%s", ec.id, ec.uuid)
1019 1021 if ec.stallback is not None:
1020 1022 ec.stallback.stop()
1021 1023 eid = ec.id
1022 1024 self.ids.add(eid)
1023 1025 self.keytable[eid] = ec.uuid
1024 1026 self.engines[eid] = ec
1025 1027 self.by_ident[cast_bytes(ec.uuid)] = ec.id
1026 1028 self.queues[eid] = list()
1027 1029 self.tasks[eid] = list()
1028 1030 self.completed[eid] = list()
1029 1031 self.hearts[heart] = eid
1030 1032 content = dict(id=eid, uuid=self.engines[eid].uuid)
1031 1033 if self.notifier:
1032 1034 self.session.send(self.notifier, "registration_notification", content=content)
1033 1035 self.log.info("engine::Engine Connected: %i", eid)
1034 1036
1035 1037 self._save_engine_state()
1036 1038
1037 1039 def _purge_stalled_registration(self, heart):
1038 1040 if heart in self.incoming_registrations:
1039 1041 ec = self.incoming_registrations.pop(heart)
1040 1042 self.log.info("registration::purging stalled registration: %i", ec.id)
1041 1043 else:
1042 1044 pass
1043 1045
1044 1046 #-------------------------------------------------------------------------
1045 1047 # Engine State
1046 1048 #-------------------------------------------------------------------------
1047 1049
1048 1050
1049 1051 def _cleanup_engine_state_file(self):
1050 1052 """cleanup engine state mapping"""
1051 1053
1052 1054 if os.path.exists(self.engine_state_file):
1053 1055 self.log.debug("cleaning up engine state: %s", self.engine_state_file)
1054 1056 try:
1055 1057 os.remove(self.engine_state_file)
1056 1058 except IOError:
1057 1059 self.log.error("Couldn't cleanup file: %s", self.engine_state_file, exc_info=True)
1058 1060
1059 1061
1060 1062 def _save_engine_state(self):
1061 1063 """save engine mapping to JSON file"""
1062 1064 if not self.engine_state_file:
1063 1065 return
1064 1066 self.log.debug("save engine state to %s" % self.engine_state_file)
1065 1067 state = {}
1066 1068 engines = {}
1067 1069 for eid, ec in self.engines.iteritems():
1068 1070 if ec.uuid not in self.dead_engines:
1069 1071 engines[eid] = ec.uuid
1070 1072
1071 1073 state['engines'] = engines
1072 1074
1073 1075 state['next_id'] = self._idcounter
1074 1076
1075 1077 with open(self.engine_state_file, 'w') as f:
1076 1078 json.dump(state, f)
1077 1079
1078 1080
1079 1081 def _load_engine_state(self):
1080 1082 """load engine mapping from JSON file"""
1081 1083 if not os.path.exists(self.engine_state_file):
1082 1084 return
1083 1085
1084 1086 self.log.info("loading engine state from %s" % self.engine_state_file)
1085 1087
1086 1088 with open(self.engine_state_file) as f:
1087 1089 state = json.load(f)
1088 1090
1089 1091 save_notifier = self.notifier
1090 1092 self.notifier = None
1091 1093 for eid, uuid in state['engines'].iteritems():
1092 1094 heart = uuid.encode('ascii')
1093 1095 # start with this heart as current and beating:
1094 1096 self.heartmonitor.responses.add(heart)
1095 1097 self.heartmonitor.hearts.add(heart)
1096 1098
1097 1099 self.incoming_registrations[heart] = EngineConnector(id=int(eid), uuid=uuid)
1098 1100 self.finish_registration(heart)
1099 1101
1100 1102 self.notifier = save_notifier
1101 1103
1102 1104 self._idcounter = state['next_id']
1103 1105
1104 1106 #-------------------------------------------------------------------------
1105 1107 # Client Requests
1106 1108 #-------------------------------------------------------------------------
1107 1109
1108 1110 def shutdown_request(self, client_id, msg):
1109 1111 """handle shutdown request."""
1110 1112 self.session.send(self.query, 'shutdown_reply', content={'status': 'ok'}, ident=client_id)
1111 1113 # also notify other clients of shutdown
1112 1114 self.session.send(self.notifier, 'shutdown_notice', content={'status': 'ok'})
1113 1115 dc = ioloop.DelayedCallback(lambda : self._shutdown(), 1000, self.loop)
1114 1116 dc.start()
1115 1117
1116 1118 def _shutdown(self):
1117 1119 self.log.info("hub::hub shutting down.")
1118 1120 time.sleep(0.1)
1119 1121 sys.exit(0)
1120 1122
1121 1123
1122 1124 def check_load(self, client_id, msg):
1123 1125 content = msg['content']
1124 1126 try:
1125 1127 targets = content['targets']
1126 1128 targets = self._validate_targets(targets)
1127 1129 except:
1128 1130 content = error.wrap_exception()
1129 1131 self.session.send(self.query, "hub_error",
1130 1132 content=content, ident=client_id)
1131 1133 return
1132 1134
1133 1135 content = dict(status='ok')
1134 1136 # loads = {}
1135 1137 for t in targets:
1136 1138 content[bytes(t)] = len(self.queues[t])+len(self.tasks[t])
1137 1139 self.session.send(self.query, "load_reply", content=content, ident=client_id)
1138 1140
1139 1141
1140 1142 def queue_status(self, client_id, msg):
1141 1143 """Return the Queue status of one or more targets.
1142 1144 if verbose: return the msg_ids
1143 1145 else: return len of each type.
1144 1146 keys: queue (pending MUX jobs)
1145 1147 tasks (pending Task jobs)
1146 1148 completed (finished jobs from both queues)"""
1147 1149 content = msg['content']
1148 1150 targets = content['targets']
1149 1151 try:
1150 1152 targets = self._validate_targets(targets)
1151 1153 except:
1152 1154 content = error.wrap_exception()
1153 1155 self.session.send(self.query, "hub_error",
1154 1156 content=content, ident=client_id)
1155 1157 return
1156 1158 verbose = content.get('verbose', False)
1157 1159 content = dict(status='ok')
1158 1160 for t in targets:
1159 1161 queue = self.queues[t]
1160 1162 completed = self.completed[t]
1161 1163 tasks = self.tasks[t]
1162 1164 if not verbose:
1163 1165 queue = len(queue)
1164 1166 completed = len(completed)
1165 1167 tasks = len(tasks)
1166 1168 content[str(t)] = {'queue': queue, 'completed': completed , 'tasks': tasks}
1167 1169 content['unassigned'] = list(self.unassigned) if verbose else len(self.unassigned)
1168 1170 # print (content)
1169 1171 self.session.send(self.query, "queue_reply", content=content, ident=client_id)
1170 1172
1171 1173 def purge_results(self, client_id, msg):
1172 1174 """Purge results from memory. This method is more valuable before we move
1173 1175 to a DB based message storage mechanism."""
1174 1176 content = msg['content']
1175 1177 self.log.info("Dropping records with %s", content)
1176 1178 msg_ids = content.get('msg_ids', [])
1177 1179 reply = dict(status='ok')
1178 1180 if msg_ids == 'all':
1179 1181 try:
1180 1182 self.db.drop_matching_records(dict(completed={'$ne':None}))
1181 1183 except Exception:
1182 1184 reply = error.wrap_exception()
1183 1185 else:
1184 1186 pending = filter(lambda m: m in self.pending, msg_ids)
1185 1187 if pending:
1186 1188 try:
1187 1189 raise IndexError("msg pending: %r" % pending[0])
1188 1190 except:
1189 1191 reply = error.wrap_exception()
1190 1192 else:
1191 1193 try:
1192 1194 self.db.drop_matching_records(dict(msg_id={'$in':msg_ids}))
1193 1195 except Exception:
1194 1196 reply = error.wrap_exception()
1195 1197
1196 1198 if reply['status'] == 'ok':
1197 1199 eids = content.get('engine_ids', [])
1198 1200 for eid in eids:
1199 1201 if eid not in self.engines:
1200 1202 try:
1201 1203 raise IndexError("No such engine: %i" % eid)
1202 1204 except:
1203 1205 reply = error.wrap_exception()
1204 1206 break
1205 1207 uid = self.engines[eid].uuid
1206 1208 try:
1207 1209 self.db.drop_matching_records(dict(engine_uuid=uid, completed={'$ne':None}))
1208 1210 except Exception:
1209 1211 reply = error.wrap_exception()
1210 1212 break
1211 1213
1212 1214 self.session.send(self.query, 'purge_reply', content=reply, ident=client_id)
1213 1215
1214 1216 def resubmit_task(self, client_id, msg):
1215 1217 """Resubmit one or more tasks."""
1216 1218 def finish(reply):
1217 1219 self.session.send(self.query, 'resubmit_reply', content=reply, ident=client_id)
1218 1220
1219 1221 content = msg['content']
1220 1222 msg_ids = content['msg_ids']
1221 1223 reply = dict(status='ok')
1222 1224 try:
1223 1225 records = self.db.find_records({'msg_id' : {'$in' : msg_ids}}, keys=[
1224 1226 'header', 'content', 'buffers'])
1225 1227 except Exception:
1226 1228 self.log.error('db::db error finding tasks to resubmit', exc_info=True)
1227 1229 return finish(error.wrap_exception())
1228 1230
1229 1231 # validate msg_ids
1230 1232 found_ids = [ rec['msg_id'] for rec in records ]
1231 1233 pending_ids = [ msg_id for msg_id in found_ids if msg_id in self.pending ]
1232 1234 if len(records) > len(msg_ids):
1233 1235 try:
1234 1236 raise RuntimeError("DB appears to be in an inconsistent state."
1235 1237 "More matching records were found than should exist")
1236 1238 except Exception:
1237 1239 return finish(error.wrap_exception())
1238 1240 elif len(records) < len(msg_ids):
1239 1241 missing = [ m for m in msg_ids if m not in found_ids ]
1240 1242 try:
1241 1243 raise KeyError("No such msg(s): %r" % missing)
1242 1244 except KeyError:
1243 1245 return finish(error.wrap_exception())
1244 1246 elif pending_ids:
1245 1247 pass
1246 1248 # no need to raise on resubmit of pending task, now that we
1247 1249 # resubmit under new ID, but do we want to raise anyway?
1248 1250 # msg_id = invalid_ids[0]
1249 1251 # try:
1250 1252 # raise ValueError("Task(s) %r appears to be inflight" % )
1251 1253 # except Exception:
1252 1254 # return finish(error.wrap_exception())
1253 1255
1254 1256 # mapping of original IDs to resubmitted IDs
1255 1257 resubmitted = {}
1256 1258
1257 1259 # send the messages
1258 1260 for rec in records:
1259 1261 header = rec['header']
1260 1262 msg = self.session.msg(header['msg_type'], parent=header)
1261 1263 msg_id = msg['msg_id']
1262 1264 msg['content'] = rec['content']
1263 1265
1264 1266 # use the old header, but update msg_id and timestamp
1265 1267 fresh = msg['header']
1266 1268 header['msg_id'] = fresh['msg_id']
1267 1269 header['date'] = fresh['date']
1268 1270 msg['header'] = header
1269 1271
1270 1272 self.session.send(self.resubmit, msg, buffers=rec['buffers'])
1271 1273
1272 1274 resubmitted[rec['msg_id']] = msg_id
1273 1275 self.pending.add(msg_id)
1274 1276 msg['buffers'] = rec['buffers']
1275 1277 try:
1276 1278 self.db.add_record(msg_id, init_record(msg))
1277 1279 except Exception:
1278 1280 self.log.error("db::DB Error updating record: %s", msg_id, exc_info=True)
1279 1281 return finish(error.wrap_exception())
1280 1282
1281 1283 finish(dict(status='ok', resubmitted=resubmitted))
1282 1284
1283 1285 # store the new IDs in the Task DB
1284 1286 for msg_id, resubmit_id in resubmitted.iteritems():
1285 1287 try:
1286 1288 self.db.update_record(msg_id, {'resubmitted' : resubmit_id})
1287 1289 except Exception:
1288 1290 self.log.error("db::DB Error updating record: %s", msg_id, exc_info=True)
1289 1291
1290 1292
1291 1293 def _extract_record(self, rec):
1292 1294 """decompose a TaskRecord dict into subsection of reply for get_result"""
1293 1295 io_dict = {}
1294 1296 for key in ('pyin', 'pyout', 'pyerr', 'stdout', 'stderr'):
1295 1297 io_dict[key] = rec[key]
1296 1298 content = {
1297 1299 'header': rec['header'],
1298 1300 'metadata': rec['metadata'],
1299 1301 'result_metadata': rec['result_metadata'],
1300 1302 'result_header' : rec['result_header'],
1301 1303 'result_content': rec['result_content'],
1302 1304 'received' : rec['received'],
1303 1305 'io' : io_dict,
1304 1306 }
1305 1307 if rec['result_buffers']:
1306 1308 buffers = map(bytes, rec['result_buffers'])
1307 1309 else:
1308 1310 buffers = []
1309 1311
1310 1312 return content, buffers
1311 1313
1312 1314 def get_results(self, client_id, msg):
1313 1315 """Get the result of 1 or more messages."""
1314 1316 content = msg['content']
1315 1317 msg_ids = sorted(set(content['msg_ids']))
1316 1318 statusonly = content.get('status_only', False)
1317 1319 pending = []
1318 1320 completed = []
1319 1321 content = dict(status='ok')
1320 1322 content['pending'] = pending
1321 1323 content['completed'] = completed
1322 1324 buffers = []
1323 1325 if not statusonly:
1324 1326 try:
1325 1327 matches = self.db.find_records(dict(msg_id={'$in':msg_ids}))
1326 1328 # turn match list into dict, for faster lookup
1327 1329 records = {}
1328 1330 for rec in matches:
1329 1331 records[rec['msg_id']] = rec
1330 1332 except Exception:
1331 1333 content = error.wrap_exception()
1332 1334 self.session.send(self.query, "result_reply", content=content,
1333 1335 parent=msg, ident=client_id)
1334 1336 return
1335 1337 else:
1336 1338 records = {}
1337 1339 for msg_id in msg_ids:
1338 1340 if msg_id in self.pending:
1339 1341 pending.append(msg_id)
1340 1342 elif msg_id in self.all_completed:
1341 1343 completed.append(msg_id)
1342 1344 if not statusonly:
1343 1345 c,bufs = self._extract_record(records[msg_id])
1344 1346 content[msg_id] = c
1345 1347 buffers.extend(bufs)
1346 1348 elif msg_id in records:
1347 1349 if rec['completed']:
1348 1350 completed.append(msg_id)
1349 1351 c,bufs = self._extract_record(records[msg_id])
1350 1352 content[msg_id] = c
1351 1353 buffers.extend(bufs)
1352 1354 else:
1353 1355 pending.append(msg_id)
1354 1356 else:
1355 1357 try:
1356 1358 raise KeyError('No such message: '+msg_id)
1357 1359 except:
1358 1360 content = error.wrap_exception()
1359 1361 break
1360 1362 self.session.send(self.query, "result_reply", content=content,
1361 1363 parent=msg, ident=client_id,
1362 1364 buffers=buffers)
1363 1365
1364 1366 def get_history(self, client_id, msg):
1365 1367 """Get a list of all msg_ids in our DB records"""
1366 1368 try:
1367 1369 msg_ids = self.db.get_history()
1368 1370 except Exception as e:
1369 1371 content = error.wrap_exception()
1370 1372 else:
1371 1373 content = dict(status='ok', history=msg_ids)
1372 1374
1373 1375 self.session.send(self.query, "history_reply", content=content,
1374 1376 parent=msg, ident=client_id)
1375 1377
1376 1378 def db_query(self, client_id, msg):
1377 1379 """Perform a raw query on the task record database."""
1378 1380 content = msg['content']
1379 1381 query = content.get('query', {})
1380 1382 keys = content.get('keys', None)
1381 1383 buffers = []
1382 1384 empty = list()
1383 1385 try:
1384 1386 records = self.db.find_records(query, keys)
1385 1387 except Exception as e:
1386 1388 content = error.wrap_exception()
1387 1389 else:
1388 1390 # extract buffers from reply content:
1389 1391 if keys is not None:
1390 1392 buffer_lens = [] if 'buffers' in keys else None
1391 1393 result_buffer_lens = [] if 'result_buffers' in keys else None
1392 1394 else:
1393 1395 buffer_lens = None
1394 1396 result_buffer_lens = None
1395 1397
1396 1398 for rec in records:
1397 1399 # buffers may be None, so double check
1398 1400 b = rec.pop('buffers', empty) or empty
1399 1401 if buffer_lens is not None:
1400 1402 buffer_lens.append(len(b))
1401 1403 buffers.extend(b)
1402 1404 rb = rec.pop('result_buffers', empty) or empty
1403 1405 if result_buffer_lens is not None:
1404 1406 result_buffer_lens.append(len(rb))
1405 1407 buffers.extend(rb)
1406 1408 content = dict(status='ok', records=records, buffer_lens=buffer_lens,
1407 1409 result_buffer_lens=result_buffer_lens)
1408 1410 # self.log.debug (content)
1409 1411 self.session.send(self.query, "db_reply", content=content,
1410 1412 parent=msg, ident=client_id,
1411 1413 buffers=buffers)
1412 1414
@@ -1,267 +1,267
1 1 """Tests for asyncresult.py
2 2
3 3 Authors:
4 4
5 5 * Min RK
6 6 """
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
19 19 import time
20 20
21 21 from IPython.utils.io import capture_output
22 22
23 23 from IPython.parallel.error import TimeoutError
24 24 from IPython.parallel import error, Client
25 25 from IPython.parallel.tests import add_engines
26 26 from .clienttest import ClusterTestCase
27 27
28 28 def setup():
29 29 add_engines(2, total=True)
30 30
31 31 def wait(n):
32 32 import time
33 33 time.sleep(n)
34 34 return n
35 35
36 36 class AsyncResultTest(ClusterTestCase):
37 37
38 38 def test_single_result_view(self):
39 39 """various one-target views get the right value for single_result"""
40 40 eid = self.client.ids[-1]
41 41 ar = self.client[eid].apply_async(lambda : 42)
42 42 self.assertEqual(ar.get(), 42)
43 43 ar = self.client[[eid]].apply_async(lambda : 42)
44 44 self.assertEqual(ar.get(), [42])
45 45 ar = self.client[-1:].apply_async(lambda : 42)
46 46 self.assertEqual(ar.get(), [42])
47 47
48 48 def test_get_after_done(self):
49 49 ar = self.client[-1].apply_async(lambda : 42)
50 50 ar.wait()
51 51 self.assertTrue(ar.ready())
52 52 self.assertEqual(ar.get(), 42)
53 53 self.assertEqual(ar.get(), 42)
54 54
55 55 def test_get_before_done(self):
56 56 ar = self.client[-1].apply_async(wait, 0.1)
57 57 self.assertRaises(TimeoutError, ar.get, 0)
58 58 ar.wait(0)
59 59 self.assertFalse(ar.ready())
60 60 self.assertEqual(ar.get(), 0.1)
61 61
62 62 def test_get_after_error(self):
63 63 ar = self.client[-1].apply_async(lambda : 1/0)
64 64 ar.wait(10)
65 65 self.assertRaisesRemote(ZeroDivisionError, ar.get)
66 66 self.assertRaisesRemote(ZeroDivisionError, ar.get)
67 67 self.assertRaisesRemote(ZeroDivisionError, ar.get_dict)
68 68
69 69 def test_get_dict(self):
70 70 n = len(self.client)
71 71 ar = self.client[:].apply_async(lambda : 5)
72 72 self.assertEqual(ar.get(), [5]*n)
73 73 d = ar.get_dict()
74 74 self.assertEqual(sorted(d.keys()), sorted(self.client.ids))
75 75 for eid,r in d.iteritems():
76 76 self.assertEqual(r, 5)
77 77
78 78 def test_list_amr(self):
79 79 ar = self.client.load_balanced_view().map_async(wait, [0.1]*5)
80 80 rlist = list(ar)
81 81
82 82 def test_getattr(self):
83 83 ar = self.client[:].apply_async(wait, 0.5)
84 self.assertEqual(ar.engine_id, [None] * len(ar))
84 85 self.assertRaises(AttributeError, lambda : ar._foo)
85 86 self.assertRaises(AttributeError, lambda : ar.__length_hint__())
86 87 self.assertRaises(AttributeError, lambda : ar.foo)
87 self.assertRaises(AttributeError, lambda : ar.engine_id)
88 88 self.assertFalse(hasattr(ar, '__length_hint__'))
89 89 self.assertFalse(hasattr(ar, 'foo'))
90 self.assertFalse(hasattr(ar, 'engine_id'))
90 self.assertTrue(hasattr(ar, 'engine_id'))
91 91 ar.get(5)
92 92 self.assertRaises(AttributeError, lambda : ar._foo)
93 93 self.assertRaises(AttributeError, lambda : ar.__length_hint__())
94 94 self.assertRaises(AttributeError, lambda : ar.foo)
95 95 self.assertTrue(isinstance(ar.engine_id, list))
96 96 self.assertEqual(ar.engine_id, ar['engine_id'])
97 97 self.assertFalse(hasattr(ar, '__length_hint__'))
98 98 self.assertFalse(hasattr(ar, 'foo'))
99 99 self.assertTrue(hasattr(ar, 'engine_id'))
100 100
101 101 def test_getitem(self):
102 102 ar = self.client[:].apply_async(wait, 0.5)
103 self.assertRaises(TimeoutError, lambda : ar['foo'])
104 self.assertRaises(TimeoutError, lambda : ar['engine_id'])
103 self.assertEqual(ar['engine_id'], [None] * len(ar))
104 self.assertRaises(KeyError, lambda : ar['foo'])
105 105 ar.get(5)
106 106 self.assertRaises(KeyError, lambda : ar['foo'])
107 107 self.assertTrue(isinstance(ar['engine_id'], list))
108 108 self.assertEqual(ar.engine_id, ar['engine_id'])
109 109
110 110 def test_single_result(self):
111 111 ar = self.client[-1].apply_async(wait, 0.5)
112 self.assertRaises(TimeoutError, lambda : ar['foo'])
113 self.assertRaises(TimeoutError, lambda : ar['engine_id'])
112 self.assertRaises(KeyError, lambda : ar['foo'])
113 self.assertEqual(ar['engine_id'], None)
114 114 self.assertTrue(ar.get(5) == 0.5)
115 115 self.assertTrue(isinstance(ar['engine_id'], int))
116 116 self.assertTrue(isinstance(ar.engine_id, int))
117 117 self.assertEqual(ar.engine_id, ar['engine_id'])
118 118
119 119 def test_abort(self):
120 120 e = self.client[-1]
121 121 ar = e.execute('import time; time.sleep(1)', block=False)
122 122 ar2 = e.apply_async(lambda : 2)
123 123 ar2.abort()
124 124 self.assertRaises(error.TaskAborted, ar2.get)
125 125 ar.get()
126 126
127 127 def test_len(self):
128 128 v = self.client.load_balanced_view()
129 129 ar = v.map_async(lambda x: x, range(10))
130 130 self.assertEqual(len(ar), 10)
131 131 ar = v.apply_async(lambda x: x, range(10))
132 132 self.assertEqual(len(ar), 1)
133 133 ar = self.client[:].apply_async(lambda x: x, range(10))
134 134 self.assertEqual(len(ar), len(self.client.ids))
135 135
136 136 def test_wall_time_single(self):
137 137 v = self.client.load_balanced_view()
138 138 ar = v.apply_async(time.sleep, 0.25)
139 139 self.assertRaises(TimeoutError, getattr, ar, 'wall_time')
140 140 ar.get(2)
141 141 self.assertTrue(ar.wall_time < 1.)
142 142 self.assertTrue(ar.wall_time > 0.2)
143 143
144 144 def test_wall_time_multi(self):
145 145 self.minimum_engines(4)
146 146 v = self.client[:]
147 147 ar = v.apply_async(time.sleep, 0.25)
148 148 self.assertRaises(TimeoutError, getattr, ar, 'wall_time')
149 149 ar.get(2)
150 150 self.assertTrue(ar.wall_time < 1.)
151 151 self.assertTrue(ar.wall_time > 0.2)
152 152
153 153 def test_serial_time_single(self):
154 154 v = self.client.load_balanced_view()
155 155 ar = v.apply_async(time.sleep, 0.25)
156 156 self.assertRaises(TimeoutError, getattr, ar, 'serial_time')
157 157 ar.get(2)
158 158 self.assertTrue(ar.serial_time < 1.)
159 159 self.assertTrue(ar.serial_time > 0.2)
160 160
161 161 def test_serial_time_multi(self):
162 162 self.minimum_engines(4)
163 163 v = self.client[:]
164 164 ar = v.apply_async(time.sleep, 0.25)
165 165 self.assertRaises(TimeoutError, getattr, ar, 'serial_time')
166 166 ar.get(2)
167 167 self.assertTrue(ar.serial_time < 2.)
168 168 self.assertTrue(ar.serial_time > 0.8)
169 169
170 170 def test_elapsed_single(self):
171 171 v = self.client.load_balanced_view()
172 172 ar = v.apply_async(time.sleep, 0.25)
173 173 while not ar.ready():
174 174 time.sleep(0.01)
175 175 self.assertTrue(ar.elapsed < 1)
176 176 self.assertTrue(ar.elapsed < 1)
177 177 ar.get(2)
178 178
179 179 def test_elapsed_multi(self):
180 180 v = self.client[:]
181 181 ar = v.apply_async(time.sleep, 0.25)
182 182 while not ar.ready():
183 183 time.sleep(0.01)
184 184 self.assertTrue(ar.elapsed < 1)
185 185 self.assertTrue(ar.elapsed < 1)
186 186 ar.get(2)
187 187
188 188 def test_hubresult_timestamps(self):
189 189 self.minimum_engines(4)
190 190 v = self.client[:]
191 191 ar = v.apply_async(time.sleep, 0.25)
192 192 ar.get(2)
193 193 rc2 = Client(profile='iptest')
194 194 # must have try/finally to close second Client, otherwise
195 195 # will have dangling sockets causing problems
196 196 try:
197 197 time.sleep(0.25)
198 198 hr = rc2.get_result(ar.msg_ids)
199 199 self.assertTrue(hr.elapsed > 0., "got bad elapsed: %s" % hr.elapsed)
200 200 hr.get(1)
201 201 self.assertTrue(hr.wall_time < ar.wall_time + 0.2, "got bad wall_time: %s > %s" % (hr.wall_time, ar.wall_time))
202 202 self.assertEqual(hr.serial_time, ar.serial_time)
203 203 finally:
204 204 rc2.close()
205 205
206 206 def test_display_empty_streams_single(self):
207 207 """empty stdout/err are not displayed (single result)"""
208 208 self.minimum_engines(1)
209 209
210 210 v = self.client[-1]
211 211 ar = v.execute("print (5555)")
212 212 ar.get(5)
213 213 with capture_output() as io:
214 214 ar.display_outputs()
215 215 self.assertEqual(io.stderr, '')
216 216 self.assertEqual('5555\n', io.stdout)
217 217
218 218 ar = v.execute("a=5")
219 219 ar.get(5)
220 220 with capture_output() as io:
221 221 ar.display_outputs()
222 222 self.assertEqual(io.stderr, '')
223 223 self.assertEqual(io.stdout, '')
224 224
225 225 def test_display_empty_streams_type(self):
226 226 """empty stdout/err are not displayed (groupby type)"""
227 227 self.minimum_engines(1)
228 228
229 229 v = self.client[:]
230 230 ar = v.execute("print (5555)")
231 231 ar.get(5)
232 232 with capture_output() as io:
233 233 ar.display_outputs()
234 234 self.assertEqual(io.stderr, '')
235 235 self.assertEqual(io.stdout.count('5555'), len(v), io.stdout)
236 236 self.assertFalse('\n\n' in io.stdout, io.stdout)
237 237 self.assertEqual(io.stdout.count('[stdout:'), len(v), io.stdout)
238 238
239 239 ar = v.execute("a=5")
240 240 ar.get(5)
241 241 with capture_output() as io:
242 242 ar.display_outputs()
243 243 self.assertEqual(io.stderr, '')
244 244 self.assertEqual(io.stdout, '')
245 245
246 246 def test_display_empty_streams_engine(self):
247 247 """empty stdout/err are not displayed (groupby engine)"""
248 248 self.minimum_engines(1)
249 249
250 250 v = self.client[:]
251 251 ar = v.execute("print (5555)")
252 252 ar.get(5)
253 253 with capture_output() as io:
254 254 ar.display_outputs('engine')
255 255 self.assertEqual(io.stderr, '')
256 256 self.assertEqual(io.stdout.count('5555'), len(v), io.stdout)
257 257 self.assertFalse('\n\n' in io.stdout, io.stdout)
258 258 self.assertEqual(io.stdout.count('[stdout:'), len(v), io.stdout)
259 259
260 260 ar = v.execute("a=5")
261 261 ar.get(5)
262 262 with capture_output() as io:
263 263 ar.display_outputs('engine')
264 264 self.assertEqual(io.stderr, '')
265 265 self.assertEqual(io.stdout, '')
266 266
267 267
@@ -1,609 +1,630
1 1 # -*- coding: utf-8 -*-
2 2 """test View objects
3 3
4 4 Authors:
5 5
6 6 * Min RK
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
19 19 import sys
20 20 import platform
21 21 import time
22 22 from tempfile import mktemp
23 23 from StringIO import StringIO
24 24
25 25 import zmq
26 26 from nose import SkipTest
27 27
28 28 from IPython.testing import decorators as dec
29 29 from IPython.testing.ipunittest import ParametricTestCase
30 30
31 31 from IPython import parallel as pmod
32 32 from IPython.parallel import error
33 33 from IPython.parallel import AsyncResult, AsyncHubResult, AsyncMapResult
34 34 from IPython.parallel import DirectView
35 35 from IPython.parallel.util import interactive
36 36
37 37 from IPython.parallel.tests import add_engines
38 38
39 39 from .clienttest import ClusterTestCase, crash, wait, skip_without
40 40
41 41 def setup():
42 42 add_engines(3, total=True)
43 43
44 44 class TestView(ClusterTestCase, ParametricTestCase):
45 45
46 46 def setUp(self):
47 47 # On Win XP, wait for resource cleanup, else parallel test group fails
48 48 if platform.system() == "Windows" and platform.win32_ver()[0] == "XP":
49 49 # 1 sec fails. 1.5 sec seems ok. Using 2 sec for margin of safety
50 50 time.sleep(2)
51 51 super(TestView, self).setUp()
52 52
53 53 def test_z_crash_mux(self):
54 54 """test graceful handling of engine death (direct)"""
55 55 raise SkipTest("crash tests disabled, due to undesirable crash reports")
56 56 # self.add_engines(1)
57 57 eid = self.client.ids[-1]
58 58 ar = self.client[eid].apply_async(crash)
59 59 self.assertRaisesRemote(error.EngineError, ar.get, 10)
60 60 eid = ar.engine_id
61 61 tic = time.time()
62 62 while eid in self.client.ids and time.time()-tic < 5:
63 63 time.sleep(.01)
64 64 self.client.spin()
65 65 self.assertFalse(eid in self.client.ids, "Engine should have died")
66 66
67 67 def test_push_pull(self):
68 68 """test pushing and pulling"""
69 69 data = dict(a=10, b=1.05, c=range(10), d={'e':(1,2),'f':'hi'})
70 70 t = self.client.ids[-1]
71 71 v = self.client[t]
72 72 push = v.push
73 73 pull = v.pull
74 74 v.block=True
75 75 nengines = len(self.client)
76 76 push({'data':data})
77 77 d = pull('data')
78 78 self.assertEqual(d, data)
79 79 self.client[:].push({'data':data})
80 80 d = self.client[:].pull('data', block=True)
81 81 self.assertEqual(d, nengines*[data])
82 82 ar = push({'data':data}, block=False)
83 83 self.assertTrue(isinstance(ar, AsyncResult))
84 84 r = ar.get()
85 85 ar = self.client[:].pull('data', block=False)
86 86 self.assertTrue(isinstance(ar, AsyncResult))
87 87 r = ar.get()
88 88 self.assertEqual(r, nengines*[data])
89 89 self.client[:].push(dict(a=10,b=20))
90 90 r = self.client[:].pull(('a','b'), block=True)
91 91 self.assertEqual(r, nengines*[[10,20]])
92 92
93 93 def test_push_pull_function(self):
94 94 "test pushing and pulling functions"
95 95 def testf(x):
96 96 return 2.0*x
97 97
98 98 t = self.client.ids[-1]
99 99 v = self.client[t]
100 100 v.block=True
101 101 push = v.push
102 102 pull = v.pull
103 103 execute = v.execute
104 104 push({'testf':testf})
105 105 r = pull('testf')
106 106 self.assertEqual(r(1.0), testf(1.0))
107 107 execute('r = testf(10)')
108 108 r = pull('r')
109 109 self.assertEqual(r, testf(10))
110 110 ar = self.client[:].push({'testf':testf}, block=False)
111 111 ar.get()
112 112 ar = self.client[:].pull('testf', block=False)
113 113 rlist = ar.get()
114 114 for r in rlist:
115 115 self.assertEqual(r(1.0), testf(1.0))
116 116 execute("def g(x): return x*x")
117 117 r = pull(('testf','g'))
118 118 self.assertEqual((r[0](10),r[1](10)), (testf(10), 100))
119 119
120 120 def test_push_function_globals(self):
121 121 """test that pushed functions have access to globals"""
122 122 @interactive
123 123 def geta():
124 124 return a
125 125 # self.add_engines(1)
126 126 v = self.client[-1]
127 127 v.block=True
128 128 v['f'] = geta
129 129 self.assertRaisesRemote(NameError, v.execute, 'b=f()')
130 130 v.execute('a=5')
131 131 v.execute('b=f()')
132 132 self.assertEqual(v['b'], 5)
133 133
134 134 def test_push_function_defaults(self):
135 135 """test that pushed functions preserve default args"""
136 136 def echo(a=10):
137 137 return a
138 138 v = self.client[-1]
139 139 v.block=True
140 140 v['f'] = echo
141 141 v.execute('b=f()')
142 142 self.assertEqual(v['b'], 10)
143 143
144 144 def test_get_result(self):
145 145 """test getting results from the Hub."""
146 146 c = pmod.Client(profile='iptest')
147 147 # self.add_engines(1)
148 148 t = c.ids[-1]
149 149 v = c[t]
150 150 v2 = self.client[t]
151 151 ar = v.apply_async(wait, 1)
152 152 # give the monitor time to notice the message
153 153 time.sleep(.25)
154 154 ahr = v2.get_result(ar.msg_ids)
155 155 self.assertTrue(isinstance(ahr, AsyncHubResult))
156 156 self.assertEqual(ahr.get(), ar.get())
157 157 ar2 = v2.get_result(ar.msg_ids)
158 158 self.assertFalse(isinstance(ar2, AsyncHubResult))
159 159 c.spin()
160 160 c.close()
161 161
162 162 def test_run_newline(self):
163 163 """test that run appends newline to files"""
164 164 tmpfile = mktemp()
165 165 with open(tmpfile, 'w') as f:
166 166 f.write("""def g():
167 167 return 5
168 168 """)
169 169 v = self.client[-1]
170 170 v.run(tmpfile, block=True)
171 171 self.assertEqual(v.apply_sync(lambda f: f(), pmod.Reference('g')), 5)
172 172
173 173 def test_apply_tracked(self):
174 174 """test tracking for apply"""
175 175 # self.add_engines(1)
176 176 t = self.client.ids[-1]
177 177 v = self.client[t]
178 178 v.block=False
179 179 def echo(n=1024*1024, **kwargs):
180 180 with v.temp_flags(**kwargs):
181 181 return v.apply(lambda x: x, 'x'*n)
182 182 ar = echo(1, track=False)
183 183 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
184 184 self.assertTrue(ar.sent)
185 185 ar = echo(track=True)
186 186 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
187 187 self.assertEqual(ar.sent, ar._tracker.done)
188 188 ar._tracker.wait()
189 189 self.assertTrue(ar.sent)
190 190
191 191 def test_push_tracked(self):
192 192 t = self.client.ids[-1]
193 193 ns = dict(x='x'*1024*1024)
194 194 v = self.client[t]
195 195 ar = v.push(ns, block=False, track=False)
196 196 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
197 197 self.assertTrue(ar.sent)
198 198
199 199 ar = v.push(ns, block=False, track=True)
200 200 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
201 201 ar._tracker.wait()
202 202 self.assertEqual(ar.sent, ar._tracker.done)
203 203 self.assertTrue(ar.sent)
204 204 ar.get()
205 205
206 206 def test_scatter_tracked(self):
207 207 t = self.client.ids
208 208 x='x'*1024*1024
209 209 ar = self.client[t].scatter('x', x, block=False, track=False)
210 210 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
211 211 self.assertTrue(ar.sent)
212 212
213 213 ar = self.client[t].scatter('x', x, block=False, track=True)
214 214 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
215 215 self.assertEqual(ar.sent, ar._tracker.done)
216 216 ar._tracker.wait()
217 217 self.assertTrue(ar.sent)
218 218 ar.get()
219 219
220 220 def test_remote_reference(self):
221 221 v = self.client[-1]
222 222 v['a'] = 123
223 223 ra = pmod.Reference('a')
224 224 b = v.apply_sync(lambda x: x, ra)
225 225 self.assertEqual(b, 123)
226 226
227 227
228 228 def test_scatter_gather(self):
229 229 view = self.client[:]
230 230 seq1 = range(16)
231 231 view.scatter('a', seq1)
232 232 seq2 = view.gather('a', block=True)
233 233 self.assertEqual(seq2, seq1)
234 234 self.assertRaisesRemote(NameError, view.gather, 'asdf', block=True)
235 235
236 236 @skip_without('numpy')
237 237 def test_scatter_gather_numpy(self):
238 238 import numpy
239 239 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
240 240 view = self.client[:]
241 241 a = numpy.arange(64)
242 242 view.scatter('a', a, block=True)
243 243 b = view.gather('a', block=True)
244 244 assert_array_equal(b, a)
245 245
246 246 def test_scatter_gather_lazy(self):
247 247 """scatter/gather with targets='all'"""
248 248 view = self.client.direct_view(targets='all')
249 249 x = range(64)
250 250 view.scatter('x', x)
251 251 gathered = view.gather('x', block=True)
252 252 self.assertEqual(gathered, x)
253 253
254 254
255 255 @dec.known_failure_py3
256 256 @skip_without('numpy')
257 257 def test_push_numpy_nocopy(self):
258 258 import numpy
259 259 view = self.client[:]
260 260 a = numpy.arange(64)
261 261 view['A'] = a
262 262 @interactive
263 263 def check_writeable(x):
264 264 return x.flags.writeable
265 265
266 266 for flag in view.apply_sync(check_writeable, pmod.Reference('A')):
267 267 self.assertFalse(flag, "array is writeable, push shouldn't have pickled it")
268 268
269 269 view.push(dict(B=a))
270 270 for flag in view.apply_sync(check_writeable, pmod.Reference('B')):
271 271 self.assertFalse(flag, "array is writeable, push shouldn't have pickled it")
272 272
273 273 @skip_without('numpy')
274 274 def test_apply_numpy(self):
275 275 """view.apply(f, ndarray)"""
276 276 import numpy
277 277 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
278 278
279 279 A = numpy.random.random((100,100))
280 280 view = self.client[-1]
281 281 for dt in [ 'int32', 'uint8', 'float32', 'float64' ]:
282 282 B = A.astype(dt)
283 283 C = view.apply_sync(lambda x:x, B)
284 284 assert_array_equal(B,C)
285 285
286 286 @skip_without('numpy')
287 287 def test_push_pull_recarray(self):
288 288 """push/pull recarrays"""
289 289 import numpy
290 290 from numpy.testing.utils import assert_array_equal
291 291
292 292 view = self.client[-1]
293 293
294 294 R = numpy.array([
295 295 (1, 'hi', 0.),
296 296 (2**30, 'there', 2.5),
297 297 (-99999, 'world', -12345.6789),
298 298 ], [('n', int), ('s', '|S10'), ('f', float)])
299 299
300 300 view['RR'] = R
301 301 R2 = view['RR']
302 302
303 303 r_dtype, r_shape = view.apply_sync(interactive(lambda : (RR.dtype, RR.shape)))
304 304 self.assertEqual(r_dtype, R.dtype)
305 305 self.assertEqual(r_shape, R.shape)
306 306 self.assertEqual(R2.dtype, R.dtype)
307 307 self.assertEqual(R2.shape, R.shape)
308 308 assert_array_equal(R2, R)
309 309
310 310 def test_map(self):
311 311 view = self.client[:]
312 312 def f(x):
313 313 return x**2
314 314 data = range(16)
315 315 r = view.map_sync(f, data)
316 316 self.assertEqual(r, map(f, data))
317 317
318 318 def test_map_iterable(self):
319 319 """test map on iterables (direct)"""
320 320 view = self.client[:]
321 321 # 101 is prime, so it won't be evenly distributed
322 322 arr = range(101)
323 323 # ensure it will be an iterator, even in Python 3
324 324 it = iter(arr)
325 325 r = view.map_sync(lambda x:x, arr)
326 326 self.assertEqual(r, list(arr))
327 327
328 328 def test_scatter_gather_nonblocking(self):
329 329 data = range(16)
330 330 view = self.client[:]
331 331 view.scatter('a', data, block=False)
332 332 ar = view.gather('a', block=False)
333 333 self.assertEqual(ar.get(), data)
334 334
335 335 @skip_without('numpy')
336 336 def test_scatter_gather_numpy_nonblocking(self):
337 337 import numpy
338 338 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
339 339 a = numpy.arange(64)
340 340 view = self.client[:]
341 341 ar = view.scatter('a', a, block=False)
342 342 self.assertTrue(isinstance(ar, AsyncResult))
343 343 amr = view.gather('a', block=False)
344 344 self.assertTrue(isinstance(amr, AsyncMapResult))
345 345 assert_array_equal(amr.get(), a)
346 346
347 347 def test_execute(self):
348 348 view = self.client[:]
349 349 # self.client.debug=True
350 350 execute = view.execute
351 351 ar = execute('c=30', block=False)
352 352 self.assertTrue(isinstance(ar, AsyncResult))
353 353 ar = execute('d=[0,1,2]', block=False)
354 354 self.client.wait(ar, 1)
355 355 self.assertEqual(len(ar.get()), len(self.client))
356 356 for c in view['c']:
357 357 self.assertEqual(c, 30)
358 358
359 359 def test_abort(self):
360 360 view = self.client[-1]
361 361 ar = view.execute('import time; time.sleep(1)', block=False)
362 362 ar2 = view.apply_async(lambda : 2)
363 363 ar3 = view.apply_async(lambda : 3)
364 364 view.abort(ar2)
365 365 view.abort(ar3.msg_ids)
366 366 self.assertRaises(error.TaskAborted, ar2.get)
367 367 self.assertRaises(error.TaskAborted, ar3.get)
368 368
369 369 def test_abort_all(self):
370 370 """view.abort() aborts all outstanding tasks"""
371 371 view = self.client[-1]
372 372 ars = [ view.apply_async(time.sleep, 0.25) for i in range(10) ]
373 373 view.abort()
374 374 view.wait(timeout=5)
375 375 for ar in ars[5:]:
376 376 self.assertRaises(error.TaskAborted, ar.get)
377 377
378 378 def test_temp_flags(self):
379 379 view = self.client[-1]
380 380 view.block=True
381 381 with view.temp_flags(block=False):
382 382 self.assertFalse(view.block)
383 383 self.assertTrue(view.block)
384 384
385 385 @dec.known_failure_py3
386 386 def test_importer(self):
387 387 view = self.client[-1]
388 388 view.clear(block=True)
389 389 with view.importer:
390 390 import re
391 391
392 392 @interactive
393 393 def findall(pat, s):
394 394 # this globals() step isn't necessary in real code
395 395 # only to prevent a closure in the test
396 396 re = globals()['re']
397 397 return re.findall(pat, s)
398 398
399 399 self.assertEqual(view.apply_sync(findall, '\w+', 'hello world'), 'hello world'.split())
400 400
401 401 def test_unicode_execute(self):
402 402 """test executing unicode strings"""
403 403 v = self.client[-1]
404 404 v.block=True
405 405 if sys.version_info[0] >= 3:
406 406 code="a='é'"
407 407 else:
408 408 code=u"a=u'é'"
409 409 v.execute(code)
410 410 self.assertEqual(v['a'], u'é')
411 411
412 412 def test_unicode_apply_result(self):
413 413 """test unicode apply results"""
414 414 v = self.client[-1]
415 415 r = v.apply_sync(lambda : u'é')
416 416 self.assertEqual(r, u'é')
417 417
418 418 def test_unicode_apply_arg(self):
419 419 """test passing unicode arguments to apply"""
420 420 v = self.client[-1]
421 421
422 422 @interactive
423 423 def check_unicode(a, check):
424 424 assert isinstance(a, unicode), "%r is not unicode"%a
425 425 assert isinstance(check, bytes), "%r is not bytes"%check
426 426 assert a.encode('utf8') == check, "%s != %s"%(a,check)
427 427
428 428 for s in [ u'é', u'ßø®∫',u'asdf' ]:
429 429 try:
430 430 v.apply_sync(check_unicode, s, s.encode('utf8'))
431 431 except error.RemoteError as e:
432 432 if e.ename == 'AssertionError':
433 433 self.fail(e.evalue)
434 434 else:
435 435 raise e
436 436
437 437 def test_map_reference(self):
438 438 """view.map(<Reference>, *seqs) should work"""
439 439 v = self.client[:]
440 440 v.scatter('n', self.client.ids, flatten=True)
441 441 v.execute("f = lambda x,y: x*y")
442 442 rf = pmod.Reference('f')
443 443 nlist = list(range(10))
444 444 mlist = nlist[::-1]
445 445 expected = [ m*n for m,n in zip(mlist, nlist) ]
446 446 result = v.map_sync(rf, mlist, nlist)
447 447 self.assertEqual(result, expected)
448 448
449 449 def test_apply_reference(self):
450 450 """view.apply(<Reference>, *args) should work"""
451 451 v = self.client[:]
452 452 v.scatter('n', self.client.ids, flatten=True)
453 453 v.execute("f = lambda x: n*x")
454 454 rf = pmod.Reference('f')
455 455 result = v.apply_sync(rf, 5)
456 456 expected = [ 5*id for id in self.client.ids ]
457 457 self.assertEqual(result, expected)
458 458
459 459 def test_eval_reference(self):
460 460 v = self.client[self.client.ids[0]]
461 461 v['g'] = range(5)
462 462 rg = pmod.Reference('g[0]')
463 463 echo = lambda x:x
464 464 self.assertEqual(v.apply_sync(echo, rg), 0)
465 465
466 466 def test_reference_nameerror(self):
467 467 v = self.client[self.client.ids[0]]
468 468 r = pmod.Reference('elvis_has_left')
469 469 echo = lambda x:x
470 470 self.assertRaisesRemote(NameError, v.apply_sync, echo, r)
471 471
472 472 def test_single_engine_map(self):
473 473 e0 = self.client[self.client.ids[0]]
474 474 r = range(5)
475 475 check = [ -1*i for i in r ]
476 476 result = e0.map_sync(lambda x: -1*x, r)
477 477 self.assertEqual(result, check)
478 478
479 479 def test_len(self):
480 480 """len(view) makes sense"""
481 481 e0 = self.client[self.client.ids[0]]
482 482 yield self.assertEqual(len(e0), 1)
483 483 v = self.client[:]
484 484 yield self.assertEqual(len(v), len(self.client.ids))
485 485 v = self.client.direct_view('all')
486 486 yield self.assertEqual(len(v), len(self.client.ids))
487 487 v = self.client[:2]
488 488 yield self.assertEqual(len(v), 2)
489 489 v = self.client[:1]
490 490 yield self.assertEqual(len(v), 1)
491 491 v = self.client.load_balanced_view()
492 492 yield self.assertEqual(len(v), len(self.client.ids))
493 493 # parametric tests seem to require manual closing?
494 494 self.client.close()
495 495
496 496
497 497 # begin execute tests
498 498
499 499 def test_execute_reply(self):
500 500 e0 = self.client[self.client.ids[0]]
501 501 e0.block = True
502 502 ar = e0.execute("5", silent=False)
503 503 er = ar.get()
504 504 self.assertEqual(str(er), "<ExecuteReply[%i]: 5>" % er.execution_count)
505 505 self.assertEqual(er.pyout['data']['text/plain'], '5')
506 506
507 507 def test_execute_reply_stdout(self):
508 508 e0 = self.client[self.client.ids[0]]
509 509 e0.block = True
510 510 ar = e0.execute("print (5)", silent=False)
511 511 er = ar.get()
512 512 self.assertEqual(er.stdout.strip(), '5')
513 513
514 514 def test_execute_pyout(self):
515 515 """execute triggers pyout with silent=False"""
516 516 view = self.client[:]
517 517 ar = view.execute("5", silent=False, block=True)
518 518
519 519 expected = [{'text/plain' : '5'}] * len(view)
520 520 mimes = [ out['data'] for out in ar.pyout ]
521 521 self.assertEqual(mimes, expected)
522 522
523 523 def test_execute_silent(self):
524 524 """execute does not trigger pyout with silent=True"""
525 525 view = self.client[:]
526 526 ar = view.execute("5", block=True)
527 527 expected = [None] * len(view)
528 528 self.assertEqual(ar.pyout, expected)
529 529
530 530 def test_execute_magic(self):
531 531 """execute accepts IPython commands"""
532 532 view = self.client[:]
533 533 view.execute("a = 5")
534 534 ar = view.execute("%whos", block=True)
535 535 # this will raise, if that failed
536 536 ar.get(5)
537 537 for stdout in ar.stdout:
538 538 lines = stdout.splitlines()
539 539 self.assertEqual(lines[0].split(), ['Variable', 'Type', 'Data/Info'])
540 540 found = False
541 541 for line in lines[2:]:
542 542 split = line.split()
543 543 if split == ['a', 'int', '5']:
544 544 found = True
545 545 break
546 546 self.assertTrue(found, "whos output wrong: %s" % stdout)
547 547
548 548 def test_execute_displaypub(self):
549 549 """execute tracks display_pub output"""
550 550 view = self.client[:]
551 551 view.execute("from IPython.core.display import *")
552 552 ar = view.execute("[ display(i) for i in range(5) ]", block=True)
553 553
554 554 expected = [ {u'text/plain' : unicode(j)} for j in range(5) ]
555 555 for outputs in ar.outputs:
556 556 mimes = [ out['data'] for out in outputs ]
557 557 self.assertEqual(mimes, expected)
558 558
559 559 def test_apply_displaypub(self):
560 560 """apply tracks display_pub output"""
561 561 view = self.client[:]
562 562 view.execute("from IPython.core.display import *")
563 563
564 564 @interactive
565 565 def publish():
566 566 [ display(i) for i in range(5) ]
567 567
568 568 ar = view.apply_async(publish)
569 569 ar.get(5)
570 570 expected = [ {u'text/plain' : unicode(j)} for j in range(5) ]
571 571 for outputs in ar.outputs:
572 572 mimes = [ out['data'] for out in outputs ]
573 573 self.assertEqual(mimes, expected)
574 574
575 575 def test_execute_raises(self):
576 576 """exceptions in execute requests raise appropriately"""
577 577 view = self.client[-1]
578 578 ar = view.execute("1/0")
579 579 self.assertRaisesRemote(ZeroDivisionError, ar.get, 2)
580 580
581 581 @dec.skipif_not_matplotlib
582 582 def test_magic_pylab(self):
583 583 """%pylab works on engines"""
584 584 view = self.client[-1]
585 585 ar = view.execute("%pylab inline")
586 586 # at least check if this raised:
587 587 reply = ar.get(5)
588 588 # include imports, in case user config
589 589 ar = view.execute("plot(rand(100))", silent=False)
590 590 reply = ar.get(5)
591 591 self.assertEqual(len(reply.outputs), 1)
592 592 output = reply.outputs[0]
593 593 self.assertTrue("data" in output)
594 594 data = output['data']
595 595 self.assertTrue("image/png" in data)
596 596
597 597 def test_func_default_func(self):
598 598 """interactively defined function as apply func default"""
599 599 def foo():
600 600 return 'foo'
601 601
602 602 def bar(f=foo):
603 603 return f()
604 604
605 605 view = self.client[-1]
606 606 ar = view.apply_async(bar)
607 607 r = ar.get(10)
608 608 self.assertEquals(r, 'foo')
609 def test_data_pub_single(self):
610 view = self.client[-1]
611 ar = view.execute('\n'.join([
612 'from IPython.zmq.datapub import publish_data',
613 'for i in range(5):',
614 ' publish_data(dict(i=i))'
615 ]), block=False)
616 self.assertTrue(isinstance(ar.data, dict))
617 ar.get(5)
618 self.assertEqual(ar.data, dict(i=4))
619
620 def test_data_pub(self):
621 view = self.client[:]
622 ar = view.execute('\n'.join([
623 'from IPython.zmq.datapub import publish_data',
624 'for i in range(5):',
625 ' publish_data(dict(i=i))'
626 ]), block=False)
627 self.assertTrue(all(isinstance(d, dict) for d in ar.data))
628 ar.get(5)
629 self.assertEqual(ar.data, [dict(i=4)] * len(ar))
609 630
@@ -1,926 +1,930
1 1 #!/usr/bin/env python
2 2 """A simple interactive kernel that talks to a frontend over 0MQ.
3 3
4 4 Things to do:
5 5
6 6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 7 call set_parent on all the PUB objects with the message about to be executed.
8 8 * Implement random port and security key logic.
9 9 * Implement control messages.
10 10 * Implement event loop and poll version.
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Standard library imports
19 19 import __builtin__
20 20 import atexit
21 21 import sys
22 22 import time
23 23 import traceback
24 24 import logging
25 25 import uuid
26 26
27 27 from datetime import datetime
28 28 from signal import (
29 29 signal, getsignal, default_int_handler, SIGINT, SIG_IGN
30 30 )
31 31
32 32 # System library imports
33 33 import zmq
34 34 from zmq.eventloop import ioloop
35 35 from zmq.eventloop.zmqstream import ZMQStream
36 36
37 37 # Local imports
38 38 from IPython.config.configurable import Configurable
39 39 from IPython.config.application import boolean_flag, catch_config_error
40 40 from IPython.core.application import ProfileDir
41 41 from IPython.core.error import StdinNotImplementedError
42 42 from IPython.core.shellapp import (
43 43 InteractiveShellApp, shell_flags, shell_aliases
44 44 )
45 45 from IPython.utils import io
46 46 from IPython.utils import py3compat
47 47 from IPython.utils.frame import extract_module_locals
48 48 from IPython.utils.jsonutil import json_clean
49 49 from IPython.utils.traitlets import (
50 50 Any, Instance, Float, Dict, CaselessStrEnum, List, Set, Integer, Unicode
51 51 )
52 52
53 53 from entry_point import base_launch_kernel
54 54 from kernelapp import KernelApp, kernel_flags, kernel_aliases
55 55 from serialize import serialize_object, unpack_apply_message
56 56 from session import Session, Message
57 57 from zmqshell import ZMQInteractiveShell
58 58
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Main kernel class
62 62 #-----------------------------------------------------------------------------
63 63
64 64 class Kernel(Configurable):
65 65
66 66 #---------------------------------------------------------------------------
67 67 # Kernel interface
68 68 #---------------------------------------------------------------------------
69 69
70 70 # attribute to override with a GUI
71 71 eventloop = Any(None)
72 72 def _eventloop_changed(self, name, old, new):
73 73 """schedule call to eventloop from IOLoop"""
74 74 loop = ioloop.IOLoop.instance()
75 75 loop.add_timeout(time.time()+0.1, self.enter_eventloop)
76 76
77 77 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
78 78 session = Instance(Session)
79 79 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
80 80 shell_streams = List()
81 81 control_stream = Instance(ZMQStream)
82 82 iopub_socket = Instance(zmq.Socket)
83 83 stdin_socket = Instance(zmq.Socket)
84 84 log = Instance(logging.Logger)
85 85
86 86 user_module = Any()
87 87 def _user_module_changed(self, name, old, new):
88 88 if self.shell is not None:
89 89 self.shell.user_module = new
90 90
91 91 user_ns = Dict(default_value=None)
92 92 def _user_ns_changed(self, name, old, new):
93 93 if self.shell is not None:
94 94 self.shell.user_ns = new
95 95 self.shell.init_user_ns()
96 96
97 97 # identities:
98 98 int_id = Integer(-1)
99 99 ident = Unicode()
100 100
101 101 def _ident_default(self):
102 102 return unicode(uuid.uuid4())
103 103
104 104
105 105 # Private interface
106 106
107 107 # Time to sleep after flushing the stdout/err buffers in each execute
108 108 # cycle. While this introduces a hard limit on the minimal latency of the
109 109 # execute cycle, it helps prevent output synchronization problems for
110 110 # clients.
111 111 # Units are in seconds. The minimum zmq latency on local host is probably
112 112 # ~150 microseconds, set this to 500us for now. We may need to increase it
113 113 # a little if it's not enough after more interactive testing.
114 114 _execute_sleep = Float(0.0005, config=True)
115 115
116 116 # Frequency of the kernel's event loop.
117 117 # Units are in seconds, kernel subclasses for GUI toolkits may need to
118 118 # adapt to milliseconds.
119 119 _poll_interval = Float(0.05, config=True)
120 120
121 121 # If the shutdown was requested over the network, we leave here the
122 122 # necessary reply message so it can be sent by our registered atexit
123 123 # handler. This ensures that the reply is only sent to clients truly at
124 124 # the end of our shutdown process (which happens after the underlying
125 125 # IPython shell's own shutdown).
126 126 _shutdown_message = None
127 127
128 128 # This is a dict of port number that the kernel is listening on. It is set
129 129 # by record_ports and used by connect_request.
130 130 _recorded_ports = Dict()
131 131
132 132 # set of aborted msg_ids
133 133 aborted = Set()
134 134
135 135
136 136 def __init__(self, **kwargs):
137 137 super(Kernel, self).__init__(**kwargs)
138 138
139 139 # Initialize the InteractiveShell subclass
140 140 self.shell = ZMQInteractiveShell.instance(config=self.config,
141 141 profile_dir = self.profile_dir,
142 142 user_module = self.user_module,
143 143 user_ns = self.user_ns,
144 144 )
145 145 self.shell.displayhook.session = self.session
146 146 self.shell.displayhook.pub_socket = self.iopub_socket
147 147 self.shell.displayhook.topic = self._topic('pyout')
148 148 self.shell.display_pub.session = self.session
149 149 self.shell.display_pub.pub_socket = self.iopub_socket
150 self.shell.data_pub.session = self.session
151 self.shell.data_pub.pub_socket = self.iopub_socket
150 152
151 153 # TMP - hack while developing
152 154 self.shell._reply_content = None
153 155
154 156 # Build dict of handlers for message types
155 157 msg_types = [ 'execute_request', 'complete_request',
156 158 'object_info_request', 'history_request',
157 159 'connect_request', 'shutdown_request',
158 160 'apply_request',
159 161 ]
160 162 self.shell_handlers = {}
161 163 for msg_type in msg_types:
162 164 self.shell_handlers[msg_type] = getattr(self, msg_type)
163 165
164 166 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
165 167 self.control_handlers = {}
166 168 for msg_type in control_msg_types:
167 169 self.control_handlers[msg_type] = getattr(self, msg_type)
168 170
169 171 def dispatch_control(self, msg):
170 172 """dispatch control requests"""
171 173 idents,msg = self.session.feed_identities(msg, copy=False)
172 174 try:
173 175 msg = self.session.unserialize(msg, content=True, copy=False)
174 176 except:
175 177 self.log.error("Invalid Control Message", exc_info=True)
176 178 return
177 179
178 180 self.log.debug("Control received: %s", msg)
179 181
180 182 header = msg['header']
181 183 msg_id = header['msg_id']
182 184 msg_type = header['msg_type']
183 185
184 186 handler = self.control_handlers.get(msg_type, None)
185 187 if handler is None:
186 188 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
187 189 else:
188 190 try:
189 191 handler(self.control_stream, idents, msg)
190 192 except Exception:
191 193 self.log.error("Exception in control handler:", exc_info=True)
192 194
193 195 def dispatch_shell(self, stream, msg):
194 196 """dispatch shell requests"""
195 197 # flush control requests first
196 198 if self.control_stream:
197 199 self.control_stream.flush()
198 200
199 201 idents,msg = self.session.feed_identities(msg, copy=False)
200 202 try:
201 203 msg = self.session.unserialize(msg, content=True, copy=False)
202 204 except:
203 205 self.log.error("Invalid Message", exc_info=True)
204 206 return
205 207
206 208 header = msg['header']
207 209 msg_id = header['msg_id']
208 210 msg_type = msg['header']['msg_type']
209 211
210 212 # Print some info about this message and leave a '--->' marker, so it's
211 213 # easier to trace visually the message chain when debugging. Each
212 214 # handler prints its message at the end.
213 215 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
214 216 self.log.debug(' Content: %s\n --->\n ', msg['content'])
215 217
216 218 if msg_id in self.aborted:
217 219 self.aborted.remove(msg_id)
218 220 # is it safe to assume a msg_id will not be resubmitted?
219 221 reply_type = msg_type.split('_')[0] + '_reply'
220 222 status = {'status' : 'aborted'}
221 223 md = {'engine' : self.ident}
222 224 md.update(status)
223 225 reply_msg = self.session.send(stream, reply_type, metadata=md,
224 226 content=status, parent=msg, ident=idents)
225 227 return
226 228
227 229 handler = self.shell_handlers.get(msg_type, None)
228 230 if handler is None:
229 231 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
230 232 else:
231 233 # ensure default_int_handler during handler call
232 234 sig = signal(SIGINT, default_int_handler)
233 235 try:
234 236 handler(stream, idents, msg)
235 237 except Exception:
236 238 self.log.error("Exception in message handler:", exc_info=True)
237 239 finally:
238 240 signal(SIGINT, sig)
239 241
240 242 def enter_eventloop(self):
241 243 """enter eventloop"""
242 244 self.log.info("entering eventloop")
243 245 # restore default_int_handler
244 246 signal(SIGINT, default_int_handler)
245 247 while self.eventloop is not None:
246 248 try:
247 249 self.eventloop(self)
248 250 except KeyboardInterrupt:
249 251 # Ctrl-C shouldn't crash the kernel
250 252 self.log.error("KeyboardInterrupt caught in kernel")
251 253 continue
252 254 else:
253 255 # eventloop exited cleanly, this means we should stop (right?)
254 256 self.eventloop = None
255 257 break
256 258 self.log.info("exiting eventloop")
257 259
258 260 def start(self):
259 261 """register dispatchers for streams"""
260 262 self.shell.exit_now = False
261 263 if self.control_stream:
262 264 self.control_stream.on_recv(self.dispatch_control, copy=False)
263 265
264 266 def make_dispatcher(stream):
265 267 def dispatcher(msg):
266 268 return self.dispatch_shell(stream, msg)
267 269 return dispatcher
268 270
269 271 for s in self.shell_streams:
270 272 s.on_recv(make_dispatcher(s), copy=False)
271 273
272 274 def do_one_iteration(self):
273 275 """step eventloop just once"""
274 276 if self.control_stream:
275 277 self.control_stream.flush()
276 278 for stream in self.shell_streams:
277 279 # handle at most one request per iteration
278 280 stream.flush(zmq.POLLIN, 1)
279 281 stream.flush(zmq.POLLOUT)
280 282
281 283
282 284 def record_ports(self, ports):
283 285 """Record the ports that this kernel is using.
284 286
285 287 The creator of the Kernel instance must call this methods if they
286 288 want the :meth:`connect_request` method to return the port numbers.
287 289 """
288 290 self._recorded_ports = ports
289 291
290 292 #---------------------------------------------------------------------------
291 293 # Kernel request handlers
292 294 #---------------------------------------------------------------------------
293 295
294 296 def _make_metadata(self, other=None):
295 297 """init metadata dict, for execute/apply_reply"""
296 298 new_md = {
297 299 'dependencies_met' : True,
298 300 'engine' : self.ident,
299 301 'started': datetime.now(),
300 302 }
301 303 if other:
302 304 new_md.update(other)
303 305 return new_md
304 306
305 307 def _publish_pyin(self, code, parent, execution_count):
306 308 """Publish the code request on the pyin stream."""
307 309
308 310 self.session.send(self.iopub_socket, u'pyin',
309 311 {u'code':code, u'execution_count': execution_count},
310 312 parent=parent, ident=self._topic('pyin')
311 313 )
312 314
313 315 def _publish_status(self, status, parent=None):
314 316 """send status (busy/idle) on IOPub"""
315 317 self.session.send(self.iopub_socket,
316 318 u'status',
317 319 {u'execution_state': status},
318 320 parent=parent,
319 321 ident=self._topic('status'),
320 322 )
321 323
322 324
323 325 def execute_request(self, stream, ident, parent):
324 326 """handle an execute_request"""
325 327
326 328 self._publish_status(u'busy', parent)
327 329
328 330 try:
329 331 content = parent[u'content']
330 332 code = content[u'code']
331 333 silent = content[u'silent']
332 334 except:
333 335 self.log.error("Got bad msg: ")
334 336 self.log.error("%s", parent)
335 337 return
336 338
337 339 md = self._make_metadata(parent['metadata'])
338 340
339 341 shell = self.shell # we'll need this a lot here
340 342
341 343 # Replace raw_input. Note that is not sufficient to replace
342 344 # raw_input in the user namespace.
343 345 if content.get('allow_stdin', False):
344 346 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
345 347 else:
346 348 raw_input = lambda prompt='' : self._no_raw_input()
347 349
348 350 if py3compat.PY3:
349 351 __builtin__.input = raw_input
350 352 else:
351 353 __builtin__.raw_input = raw_input
352 354
353 355 # Set the parent message of the display hook and out streams.
354 356 shell.displayhook.set_parent(parent)
355 357 shell.display_pub.set_parent(parent)
358 shell.data_pub.set_parent(parent)
356 359 sys.stdout.set_parent(parent)
357 360 sys.stderr.set_parent(parent)
358 361
359 362 # Re-broadcast our input for the benefit of listening clients, and
360 363 # start computing output
361 364 if not silent:
362 365 self._publish_pyin(code, parent, shell.execution_count)
363 366
364 367 reply_content = {}
365 368 try:
366 369 # FIXME: the shell calls the exception handler itself.
367 370 shell.run_cell(code, store_history=not silent, silent=silent)
368 371 except:
369 372 status = u'error'
370 373 # FIXME: this code right now isn't being used yet by default,
371 374 # because the run_cell() call above directly fires off exception
372 375 # reporting. This code, therefore, is only active in the scenario
373 376 # where runlines itself has an unhandled exception. We need to
374 377 # uniformize this, for all exception construction to come from a
375 378 # single location in the codbase.
376 379 etype, evalue, tb = sys.exc_info()
377 380 tb_list = traceback.format_exception(etype, evalue, tb)
378 381 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
379 382 else:
380 383 status = u'ok'
381 384
382 385 reply_content[u'status'] = status
383 386
384 387 # Return the execution counter so clients can display prompts
385 388 reply_content['execution_count'] = shell.execution_count - 1
386 389
387 390 # FIXME - fish exception info out of shell, possibly left there by
388 391 # runlines. We'll need to clean up this logic later.
389 392 if shell._reply_content is not None:
390 393 reply_content.update(shell._reply_content)
391 394 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
392 395 reply_content['engine_info'] = e_info
393 396 # reset after use
394 397 shell._reply_content = None
395 398
396 399 # At this point, we can tell whether the main code execution succeeded
397 400 # or not. If it did, we proceed to evaluate user_variables/expressions
398 401 if reply_content['status'] == 'ok':
399 402 reply_content[u'user_variables'] = \
400 403 shell.user_variables(content.get(u'user_variables', []))
401 404 reply_content[u'user_expressions'] = \
402 405 shell.user_expressions(content.get(u'user_expressions', {}))
403 406 else:
404 407 # If there was an error, don't even try to compute variables or
405 408 # expressions
406 409 reply_content[u'user_variables'] = {}
407 410 reply_content[u'user_expressions'] = {}
408 411
409 412 # Payloads should be retrieved regardless of outcome, so we can both
410 413 # recover partial output (that could have been generated early in a
411 414 # block, before an error) and clear the payload system always.
412 415 reply_content[u'payload'] = shell.payload_manager.read_payload()
413 416 # Be agressive about clearing the payload because we don't want
414 417 # it to sit in memory until the next execute_request comes in.
415 418 shell.payload_manager.clear_payload()
416 419
417 420 # Flush output before sending the reply.
418 421 sys.stdout.flush()
419 422 sys.stderr.flush()
420 423 # FIXME: on rare occasions, the flush doesn't seem to make it to the
421 424 # clients... This seems to mitigate the problem, but we definitely need
422 425 # to better understand what's going on.
423 426 if self._execute_sleep:
424 427 time.sleep(self._execute_sleep)
425 428
426 429 # Send the reply.
427 430 reply_content = json_clean(reply_content)
428 431
429 432 md['status'] = reply_content['status']
430 433 if reply_content['status'] == 'error' and \
431 434 reply_content['ename'] == 'UnmetDependency':
432 435 md['dependencies_met'] = False
433 436
434 437 reply_msg = self.session.send(stream, u'execute_reply',
435 438 reply_content, parent, metadata=md,
436 439 ident=ident)
437 440
438 441 self.log.debug("%s", reply_msg)
439 442
440 443 if not silent and reply_msg['content']['status'] == u'error':
441 444 self._abort_queues()
442 445
443 446 self._publish_status(u'idle', parent)
444 447
445 448 def complete_request(self, stream, ident, parent):
446 449 txt, matches = self._complete(parent)
447 450 matches = {'matches' : matches,
448 451 'matched_text' : txt,
449 452 'status' : 'ok'}
450 453 matches = json_clean(matches)
451 454 completion_msg = self.session.send(stream, 'complete_reply',
452 455 matches, parent, ident)
453 456 self.log.debug("%s", completion_msg)
454 457
455 458 def object_info_request(self, stream, ident, parent):
456 459 content = parent['content']
457 460 object_info = self.shell.object_inspect(content['oname'],
458 461 detail_level = content.get('detail_level', 0)
459 462 )
460 463 # Before we send this object over, we scrub it for JSON usage
461 464 oinfo = json_clean(object_info)
462 465 msg = self.session.send(stream, 'object_info_reply',
463 466 oinfo, parent, ident)
464 467 self.log.debug("%s", msg)
465 468
466 469 def history_request(self, stream, ident, parent):
467 470 # We need to pull these out, as passing **kwargs doesn't work with
468 471 # unicode keys before Python 2.6.5.
469 472 hist_access_type = parent['content']['hist_access_type']
470 473 raw = parent['content']['raw']
471 474 output = parent['content']['output']
472 475 if hist_access_type == 'tail':
473 476 n = parent['content']['n']
474 477 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
475 478 include_latest=True)
476 479
477 480 elif hist_access_type == 'range':
478 481 session = parent['content']['session']
479 482 start = parent['content']['start']
480 483 stop = parent['content']['stop']
481 484 hist = self.shell.history_manager.get_range(session, start, stop,
482 485 raw=raw, output=output)
483 486
484 487 elif hist_access_type == 'search':
485 488 pattern = parent['content']['pattern']
486 489 hist = self.shell.history_manager.search(pattern, raw=raw,
487 490 output=output)
488 491
489 492 else:
490 493 hist = []
491 494 hist = list(hist)
492 495 content = {'history' : hist}
493 496 content = json_clean(content)
494 497 msg = self.session.send(stream, 'history_reply',
495 498 content, parent, ident)
496 499 self.log.debug("Sending history reply with %i entries", len(hist))
497 500
498 501 def connect_request(self, stream, ident, parent):
499 502 if self._recorded_ports is not None:
500 503 content = self._recorded_ports.copy()
501 504 else:
502 505 content = {}
503 506 msg = self.session.send(stream, 'connect_reply',
504 507 content, parent, ident)
505 508 self.log.debug("%s", msg)
506 509
507 510 def shutdown_request(self, stream, ident, parent):
508 511 self.shell.exit_now = True
509 512 content = dict(status='ok')
510 513 content.update(parent['content'])
511 514 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
512 515 # same content, but different msg_id for broadcasting on IOPub
513 516 self._shutdown_message = self.session.msg(u'shutdown_reply',
514 517 content, parent
515 518 )
516 519
517 520 self._at_shutdown()
518 521 # call sys.exit after a short delay
519 522 loop = ioloop.IOLoop.instance()
520 523 loop.add_timeout(time.time()+0.1, loop.stop)
521 524
522 525 #---------------------------------------------------------------------------
523 526 # Engine methods
524 527 #---------------------------------------------------------------------------
525 528
526 529 def apply_request(self, stream, ident, parent):
527 530 try:
528 531 content = parent[u'content']
529 532 bufs = parent[u'buffers']
530 533 msg_id = parent['header']['msg_id']
531 534 except:
532 535 self.log.error("Got bad msg: %s", parent, exc_info=True)
533 536 return
534 537
535 538 self._publish_status(u'busy', parent)
536 539
537 540 # Set the parent message of the display hook and out streams.
538 541 shell = self.shell
539 542 shell.displayhook.set_parent(parent)
540 543 shell.display_pub.set_parent(parent)
544 shell.data_pub.set_parent(parent)
541 545 sys.stdout.set_parent(parent)
542 546 sys.stderr.set_parent(parent)
543 547
544 548 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
545 549 # self.iopub_socket.send(pyin_msg)
546 550 # self.session.send(self.iopub_socket, u'pyin', {u'code':code},parent=parent)
547 551 md = self._make_metadata(parent['metadata'])
548 552 try:
549 553 working = shell.user_ns
550 554
551 555 prefix = "_"+str(msg_id).replace("-","")+"_"
552 556
553 557 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
554 558
555 559 fname = getattr(f, '__name__', 'f')
556 560
557 561 fname = prefix+"f"
558 562 argname = prefix+"args"
559 563 kwargname = prefix+"kwargs"
560 564 resultname = prefix+"result"
561 565
562 566 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
563 567 # print ns
564 568 working.update(ns)
565 569 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
566 570 try:
567 571 exec code in shell.user_global_ns, shell.user_ns
568 572 result = working.get(resultname)
569 573 finally:
570 574 for key in ns.iterkeys():
571 575 working.pop(key)
572 576
573 577 result_buf = serialize_object(result,
574 578 buffer_threshold=self.session.buffer_threshold,
575 579 item_threshold=self.session.item_threshold,
576 580 )
577 581
578 582 except:
579 583 # invoke IPython traceback formatting
580 584 shell.showtraceback()
581 585 # FIXME - fish exception info out of shell, possibly left there by
582 586 # run_code. We'll need to clean up this logic later.
583 587 reply_content = {}
584 588 if shell._reply_content is not None:
585 589 reply_content.update(shell._reply_content)
586 590 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
587 591 reply_content['engine_info'] = e_info
588 592 # reset after use
589 593 shell._reply_content = None
590 594
591 595 self.session.send(self.iopub_socket, u'pyerr', reply_content, parent=parent,
592 596 ident=self._topic('pyerr'))
593 597 result_buf = []
594 598
595 599 if reply_content['ename'] == 'UnmetDependency':
596 600 md['dependencies_met'] = False
597 601 else:
598 602 reply_content = {'status' : 'ok'}
599 603
600 604 # put 'ok'/'error' status in header, for scheduler introspection:
601 605 md['status'] = reply_content['status']
602 606
603 607 # flush i/o
604 608 sys.stdout.flush()
605 609 sys.stderr.flush()
606 610
607 611 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
608 612 parent=parent, ident=ident,buffers=result_buf, metadata=md)
609 613
610 614 self._publish_status(u'idle', parent)
611 615
612 616 #---------------------------------------------------------------------------
613 617 # Control messages
614 618 #---------------------------------------------------------------------------
615 619
616 620 def abort_request(self, stream, ident, parent):
617 621 """abort a specifig msg by id"""
618 622 msg_ids = parent['content'].get('msg_ids', None)
619 623 if isinstance(msg_ids, basestring):
620 624 msg_ids = [msg_ids]
621 625 if not msg_ids:
622 626 self.abort_queues()
623 627 for mid in msg_ids:
624 628 self.aborted.add(str(mid))
625 629
626 630 content = dict(status='ok')
627 631 reply_msg = self.session.send(stream, 'abort_reply', content=content,
628 632 parent=parent, ident=ident)
629 633 self.log.debug("%s", reply_msg)
630 634
631 635 def clear_request(self, stream, idents, parent):
632 636 """Clear our namespace."""
633 637 self.shell.reset(False)
634 638 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
635 639 content = dict(status='ok'))
636 640
637 641
638 642 #---------------------------------------------------------------------------
639 643 # Protected interface
640 644 #---------------------------------------------------------------------------
641 645
642 646
643 647 def _wrap_exception(self, method=None):
644 648 # import here, because _wrap_exception is only used in parallel,
645 649 # and parallel has higher min pyzmq version
646 650 from IPython.parallel.error import wrap_exception
647 651 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
648 652 content = wrap_exception(e_info)
649 653 return content
650 654
651 655 def _topic(self, topic):
652 656 """prefixed topic for IOPub messages"""
653 657 if self.int_id >= 0:
654 658 base = "engine.%i" % self.int_id
655 659 else:
656 660 base = "kernel.%s" % self.ident
657 661
658 662 return py3compat.cast_bytes("%s.%s" % (base, topic))
659 663
660 664 def _abort_queues(self):
661 665 for stream in self.shell_streams:
662 666 if stream:
663 667 self._abort_queue(stream)
664 668
665 669 def _abort_queue(self, stream):
666 670 poller = zmq.Poller()
667 671 poller.register(stream.socket, zmq.POLLIN)
668 672 while True:
669 673 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
670 674 if msg is None:
671 675 return
672 676
673 677 self.log.info("Aborting:")
674 678 self.log.info("%s", msg)
675 679 msg_type = msg['header']['msg_type']
676 680 reply_type = msg_type.split('_')[0] + '_reply'
677 681
678 682 status = {'status' : 'aborted'}
679 683 md = {'engine' : self.ident}
680 684 md.update(status)
681 685 reply_msg = self.session.send(stream, reply_type, metadata=md,
682 686 content=status, parent=msg, ident=idents)
683 687 self.log.debug("%s", reply_msg)
684 688 # We need to wait a bit for requests to come in. This can probably
685 689 # be set shorter for true asynchronous clients.
686 690 poller.poll(50)
687 691
688 692
689 693 def _no_raw_input(self):
690 694 """Raise StdinNotImplentedError if active frontend doesn't support
691 695 stdin."""
692 696 raise StdinNotImplementedError("raw_input was called, but this "
693 697 "frontend does not support stdin.")
694 698
695 699 def _raw_input(self, prompt, ident, parent):
696 700 # Flush output before making the request.
697 701 sys.stderr.flush()
698 702 sys.stdout.flush()
699 703
700 704 # Send the input request.
701 705 content = json_clean(dict(prompt=prompt))
702 706 self.session.send(self.stdin_socket, u'input_request', content, parent,
703 707 ident=ident)
704 708
705 709 # Await a response.
706 710 while True:
707 711 try:
708 712 ident, reply = self.session.recv(self.stdin_socket, 0)
709 713 except Exception:
710 714 self.log.warn("Invalid Message:", exc_info=True)
711 715 else:
712 716 break
713 717 try:
714 718 value = reply['content']['value']
715 719 except:
716 720 self.log.error("Got bad raw_input reply: ")
717 721 self.log.error("%s", parent)
718 722 value = ''
719 723 if value == '\x04':
720 724 # EOF
721 725 raise EOFError
722 726 return value
723 727
724 728 def _complete(self, msg):
725 729 c = msg['content']
726 730 try:
727 731 cpos = int(c['cursor_pos'])
728 732 except:
729 733 # If we don't get something that we can convert to an integer, at
730 734 # least attempt the completion guessing the cursor is at the end of
731 735 # the text, if there's any, and otherwise of the line
732 736 cpos = len(c['text'])
733 737 if cpos==0:
734 738 cpos = len(c['line'])
735 739 return self.shell.complete(c['text'], c['line'], cpos)
736 740
737 741 def _object_info(self, context):
738 742 symbol, leftover = self._symbol_from_context(context)
739 743 if symbol is not None and not leftover:
740 744 doc = getattr(symbol, '__doc__', '')
741 745 else:
742 746 doc = ''
743 747 object_info = dict(docstring = doc)
744 748 return object_info
745 749
746 750 def _symbol_from_context(self, context):
747 751 if not context:
748 752 return None, context
749 753
750 754 base_symbol_string = context[0]
751 755 symbol = self.shell.user_ns.get(base_symbol_string, None)
752 756 if symbol is None:
753 757 symbol = __builtin__.__dict__.get(base_symbol_string, None)
754 758 if symbol is None:
755 759 return None, context
756 760
757 761 context = context[1:]
758 762 for i, name in enumerate(context):
759 763 new_symbol = getattr(symbol, name, None)
760 764 if new_symbol is None:
761 765 return symbol, context[i:]
762 766 else:
763 767 symbol = new_symbol
764 768
765 769 return symbol, []
766 770
767 771 def _at_shutdown(self):
768 772 """Actions taken at shutdown by the kernel, called by python's atexit.
769 773 """
770 774 # io.rprint("Kernel at_shutdown") # dbg
771 775 if self._shutdown_message is not None:
772 776 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
773 777 self.log.debug("%s", self._shutdown_message)
774 778 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
775 779
776 780 #-----------------------------------------------------------------------------
777 781 # Aliases and Flags for the IPKernelApp
778 782 #-----------------------------------------------------------------------------
779 783
780 784 flags = dict(kernel_flags)
781 785 flags.update(shell_flags)
782 786
783 787 addflag = lambda *args: flags.update(boolean_flag(*args))
784 788
785 789 flags['pylab'] = (
786 790 {'IPKernelApp' : {'pylab' : 'auto'}},
787 791 """Pre-load matplotlib and numpy for interactive use with
788 792 the default matplotlib backend."""
789 793 )
790 794
791 795 aliases = dict(kernel_aliases)
792 796 aliases.update(shell_aliases)
793 797
794 798 #-----------------------------------------------------------------------------
795 799 # The IPKernelApp class
796 800 #-----------------------------------------------------------------------------
797 801
798 802 class IPKernelApp(KernelApp, InteractiveShellApp):
799 803 name = 'ipkernel'
800 804
801 805 aliases = Dict(aliases)
802 806 flags = Dict(flags)
803 807 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
804 808
805 809 @catch_config_error
806 810 def initialize(self, argv=None):
807 811 super(IPKernelApp, self).initialize(argv)
808 812 self.init_path()
809 813 self.init_shell()
810 814 self.init_gui_pylab()
811 815 self.init_extensions()
812 816 self.init_code()
813 817
814 818 def init_kernel(self):
815 819
816 820 shell_stream = ZMQStream(self.shell_socket)
817 821
818 822 kernel = Kernel(config=self.config, session=self.session,
819 823 shell_streams=[shell_stream],
820 824 iopub_socket=self.iopub_socket,
821 825 stdin_socket=self.stdin_socket,
822 826 log=self.log,
823 827 profile_dir=self.profile_dir,
824 828 )
825 829 self.kernel = kernel
826 830 kernel.record_ports(self.ports)
827 831 shell = kernel.shell
828 832
829 833 def init_gui_pylab(self):
830 834 """Enable GUI event loop integration, taking pylab into account."""
831 835
832 836 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
833 837 # to ensure that any exception is printed straight to stderr.
834 838 # Normally _showtraceback associates the reply with an execution,
835 839 # which means frontends will never draw it, as this exception
836 840 # is not associated with any execute request.
837 841
838 842 shell = self.shell
839 843 _showtraceback = shell._showtraceback
840 844 try:
841 845 # replace pyerr-sending traceback with stderr
842 846 def print_tb(etype, evalue, stb):
843 847 print ("GUI event loop or pylab initialization failed",
844 848 file=io.stderr)
845 849 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
846 850 shell._showtraceback = print_tb
847 851 InteractiveShellApp.init_gui_pylab(self)
848 852 finally:
849 853 shell._showtraceback = _showtraceback
850 854
851 855 def init_shell(self):
852 856 self.shell = self.kernel.shell
853 857 self.shell.configurables.append(self)
854 858
855 859
856 860 #-----------------------------------------------------------------------------
857 861 # Kernel main and launch functions
858 862 #-----------------------------------------------------------------------------
859 863
860 864 def launch_kernel(*args, **kwargs):
861 865 """Launches a localhost IPython kernel, binding to the specified ports.
862 866
863 867 This function simply calls entry_point.base_launch_kernel with the right
864 868 first command to start an ipkernel. See base_launch_kernel for arguments.
865 869
866 870 Returns
867 871 -------
868 872 A tuple of form:
869 873 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
870 874 where kernel_process is a Popen object and the ports are integers.
871 875 """
872 876 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
873 877 *args, **kwargs)
874 878
875 879
876 880 def embed_kernel(module=None, local_ns=None, **kwargs):
877 881 """Embed and start an IPython kernel in a given scope.
878 882
879 883 Parameters
880 884 ----------
881 885 module : ModuleType, optional
882 886 The module to load into IPython globals (default: caller)
883 887 local_ns : dict, optional
884 888 The namespace to load into IPython user namespace (default: caller)
885 889
886 890 kwargs : various, optional
887 891 Further keyword args are relayed to the KernelApp constructor,
888 892 allowing configuration of the Kernel. Will only have an effect
889 893 on the first embed_kernel call for a given process.
890 894
891 895 """
892 896 # get the app if it exists, or set it up if it doesn't
893 897 if IPKernelApp.initialized():
894 898 app = IPKernelApp.instance()
895 899 else:
896 900 app = IPKernelApp.instance(**kwargs)
897 901 app.initialize([])
898 902 # Undo unnecessary sys module mangling from init_sys_modules.
899 903 # This would not be necessary if we could prevent it
900 904 # in the first place by using a different InteractiveShell
901 905 # subclass, as in the regular embed case.
902 906 main = app.kernel.shell._orig_sys_modules_main_mod
903 907 if main is not None:
904 908 sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main
905 909
906 910 # load the calling scope if not given
907 911 (caller_module, caller_locals) = extract_module_locals(1)
908 912 if module is None:
909 913 module = caller_module
910 914 if local_ns is None:
911 915 local_ns = caller_locals
912 916
913 917 app.kernel.user_module = module
914 918 app.kernel.user_ns = local_ns
915 919 app.shell.set_completer_frame()
916 920 app.start()
917 921
918 922 def main():
919 923 """Run an IPKernel as an application"""
920 924 app = IPKernelApp.instance()
921 925 app.initialize()
922 926 app.start()
923 927
924 928
925 929 if __name__ == '__main__':
926 930 main()
@@ -1,582 +1,584
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, encode_images
42 42 from IPython.utils.process import arg_split
43 43 from IPython.utils import py3compat
44 44 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
45 45 from IPython.utils.warn import warn, error
46 46 from IPython.zmq.displayhook import ZMQShellDisplayHook
47 from IPython.zmq.datapub import ZMQDataPublisher
47 48 from IPython.zmq.session import extract_header
48 49 from session import Session
49 50
50 51 #-----------------------------------------------------------------------------
51 52 # Functions and classes
52 53 #-----------------------------------------------------------------------------
53 54
54 55 class ZMQDisplayPublisher(DisplayPublisher):
55 56 """A display publisher that publishes data using a ZeroMQ PUB socket."""
56 57
57 58 session = Instance(Session)
58 59 pub_socket = Instance('zmq.Socket')
59 60 parent_header = Dict({})
60 61 topic = CBytes(b'displaypub')
61 62
62 63 def set_parent(self, parent):
63 64 """Set the parent for outbound messages."""
64 65 self.parent_header = extract_header(parent)
65 66
66 67 def _flush_streams(self):
67 68 """flush IO Streams prior to display"""
68 69 sys.stdout.flush()
69 70 sys.stderr.flush()
70 71
71 72 def publish(self, source, data, metadata=None):
72 73 self._flush_streams()
73 74 if metadata is None:
74 75 metadata = {}
75 76 self._validate_data(source, data, metadata)
76 77 content = {}
77 78 content['source'] = source
78 79 content['data'] = encode_images(data)
79 80 content['metadata'] = metadata
80 81 self.session.send(
81 82 self.pub_socket, u'display_data', json_clean(content),
82 83 parent=self.parent_header, ident=self.topic,
83 84 )
84 85
85 86 def clear_output(self, stdout=True, stderr=True, other=True):
86 87 content = dict(stdout=stdout, stderr=stderr, other=other)
87 88
88 89 if stdout:
89 90 print('\r', file=sys.stdout, end='')
90 91 if stderr:
91 92 print('\r', file=sys.stderr, end='')
92 93
93 94 self._flush_streams()
94 95
95 96 self.session.send(
96 97 self.pub_socket, u'clear_output', content,
97 98 parent=self.parent_header, ident=self.topic,
98 99 )
99 100
100 101 @magics_class
101 102 class KernelMagics(Magics):
102 103 #------------------------------------------------------------------------
103 104 # Magic overrides
104 105 #------------------------------------------------------------------------
105 106 # Once the base class stops inheriting from magic, this code needs to be
106 107 # moved into a separate machinery as well. For now, at least isolate here
107 108 # the magics which this class needs to implement differently from the base
108 109 # class, or that are unique to it.
109 110
110 111 @line_magic
111 112 def doctest_mode(self, parameter_s=''):
112 113 """Toggle doctest mode on and off.
113 114
114 115 This mode is intended to make IPython behave as much as possible like a
115 116 plain Python shell, from the perspective of how its prompts, exceptions
116 117 and output look. This makes it easy to copy and paste parts of a
117 118 session into doctests. It does so by:
118 119
119 120 - Changing the prompts to the classic ``>>>`` ones.
120 121 - Changing the exception reporting mode to 'Plain'.
121 122 - Disabling pretty-printing of output.
122 123
123 124 Note that IPython also supports the pasting of code snippets that have
124 125 leading '>>>' and '...' prompts in them. This means that you can paste
125 126 doctests from files or docstrings (even if they have leading
126 127 whitespace), and the code will execute correctly. You can then use
127 128 '%history -t' to see the translated history; this will give you the
128 129 input after removal of all the leading prompts and whitespace, which
129 130 can be pasted back into an editor.
130 131
131 132 With these features, you can switch into this mode easily whenever you
132 133 need to do testing and changes to doctests, without having to leave
133 134 your existing IPython session.
134 135 """
135 136
136 137 from IPython.utils.ipstruct import Struct
137 138
138 139 # Shorthands
139 140 shell = self.shell
140 141 disp_formatter = self.shell.display_formatter
141 142 ptformatter = disp_formatter.formatters['text/plain']
142 143 # dstore is a data store kept in the instance metadata bag to track any
143 144 # changes we make, so we can undo them later.
144 145 dstore = shell.meta.setdefault('doctest_mode', Struct())
145 146 save_dstore = dstore.setdefault
146 147
147 148 # save a few values we'll need to recover later
148 149 mode = save_dstore('mode', False)
149 150 save_dstore('rc_pprint', ptformatter.pprint)
150 151 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
151 152 save_dstore('xmode', shell.InteractiveTB.mode)
152 153
153 154 if mode == False:
154 155 # turn on
155 156 ptformatter.pprint = False
156 157 disp_formatter.plain_text_only = True
157 158 shell.magic('xmode Plain')
158 159 else:
159 160 # turn off
160 161 ptformatter.pprint = dstore.rc_pprint
161 162 disp_formatter.plain_text_only = dstore.rc_plain_text_only
162 163 shell.magic("xmode " + dstore.xmode)
163 164
164 165 # Store new mode and inform on console
165 166 dstore.mode = bool(1-int(mode))
166 167 mode_label = ['OFF','ON'][dstore.mode]
167 168 print('Doctest mode is:', mode_label)
168 169
169 170 # Send the payload back so that clients can modify their prompt display
170 171 payload = dict(
171 172 source='IPython.zmq.zmqshell.ZMQInteractiveShell.doctest_mode',
172 173 mode=dstore.mode)
173 174 shell.payload_manager.write_payload(payload)
174 175
175 176
176 177 _find_edit_target = CodeMagics._find_edit_target
177 178
178 179 @skip_doctest
179 180 @line_magic
180 181 def edit(self, parameter_s='', last_call=['','']):
181 182 """Bring up an editor and execute the resulting code.
182 183
183 184 Usage:
184 185 %edit [options] [args]
185 186
186 187 %edit runs an external text editor. You will need to set the command for
187 188 this editor via the ``TerminalInteractiveShell.editor`` option in your
188 189 configuration file before it will work.
189 190
190 191 This command allows you to conveniently edit multi-line code right in
191 192 your IPython session.
192 193
193 194 If called without arguments, %edit opens up an empty editor with a
194 195 temporary file and will execute the contents of this file when you
195 196 close it (don't forget to save it!).
196 197
197 198
198 199 Options:
199 200
200 201 -n <number>: open the editor at a specified line number. By default,
201 202 the IPython editor hook uses the unix syntax 'editor +N filename', but
202 203 you can configure this by providing your own modified hook if your
203 204 favorite editor supports line-number specifications with a different
204 205 syntax.
205 206
206 207 -p: this will call the editor with the same data as the previous time
207 208 it was used, regardless of how long ago (in your current session) it
208 209 was.
209 210
210 211 -r: use 'raw' input. This option only applies to input taken from the
211 212 user's history. By default, the 'processed' history is used, so that
212 213 magics are loaded in their transformed version to valid Python. If
213 214 this option is given, the raw input as typed as the command line is
214 215 used instead. When you exit the editor, it will be executed by
215 216 IPython's own processor.
216 217
217 218 -x: do not execute the edited code immediately upon exit. This is
218 219 mainly useful if you are editing programs which need to be called with
219 220 command line arguments, which you can then do using %run.
220 221
221 222
222 223 Arguments:
223 224
224 225 If arguments are given, the following possibilites exist:
225 226
226 227 - The arguments are numbers or pairs of colon-separated numbers (like
227 228 1 4:8 9). These are interpreted as lines of previous input to be
228 229 loaded into the editor. The syntax is the same of the %macro command.
229 230
230 231 - If the argument doesn't start with a number, it is evaluated as a
231 232 variable and its contents loaded into the editor. You can thus edit
232 233 any string which contains python code (including the result of
233 234 previous edits).
234 235
235 236 - If the argument is the name of an object (other than a string),
236 237 IPython will try to locate the file where it was defined and open the
237 238 editor at the point where it is defined. You can use `%edit function`
238 239 to load an editor exactly at the point where 'function' is defined,
239 240 edit it and have the file be executed automatically.
240 241
241 242 If the object is a macro (see %macro for details), this opens up your
242 243 specified editor with a temporary file containing the macro's data.
243 244 Upon exit, the macro is reloaded with the contents of the file.
244 245
245 246 Note: opening at an exact line is only supported under Unix, and some
246 247 editors (like kedit and gedit up to Gnome 2.8) do not understand the
247 248 '+NUMBER' parameter necessary for this feature. Good editors like
248 249 (X)Emacs, vi, jed, pico and joe all do.
249 250
250 251 - If the argument is not found as a variable, IPython will look for a
251 252 file with that name (adding .py if necessary) and load it into the
252 253 editor. It will execute its contents with execfile() when you exit,
253 254 loading any code in the file into your interactive namespace.
254 255
255 256 After executing your code, %edit will return as output the code you
256 257 typed in the editor (except when it was an existing file). This way
257 258 you can reload the code in further invocations of %edit as a variable,
258 259 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
259 260 the output.
260 261
261 262 Note that %edit is also available through the alias %ed.
262 263
263 264 This is an example of creating a simple function inside the editor and
264 265 then modifying it. First, start up the editor:
265 266
266 267 In [1]: ed
267 268 Editing... done. Executing edited code...
268 269 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
269 270
270 271 We can then call the function foo():
271 272
272 273 In [2]: foo()
273 274 foo() was defined in an editing session
274 275
275 276 Now we edit foo. IPython automatically loads the editor with the
276 277 (temporary) file where foo() was previously defined:
277 278
278 279 In [3]: ed foo
279 280 Editing... done. Executing edited code...
280 281
281 282 And if we call foo() again we get the modified version:
282 283
283 284 In [4]: foo()
284 285 foo() has now been changed!
285 286
286 287 Here is an example of how to edit a code snippet successive
287 288 times. First we call the editor:
288 289
289 290 In [5]: ed
290 291 Editing... done. Executing edited code...
291 292 hello
292 293 Out[5]: "print 'hello'n"
293 294
294 295 Now we call it again with the previous output (stored in _):
295 296
296 297 In [6]: ed _
297 298 Editing... done. Executing edited code...
298 299 hello world
299 300 Out[6]: "print 'hello world'n"
300 301
301 302 Now we call it with the output #8 (stored in _8, also as Out[8]):
302 303
303 304 In [7]: ed _8
304 305 Editing... done. Executing edited code...
305 306 hello again
306 307 Out[7]: "print 'hello again'n"
307 308 """
308 309
309 310 opts,args = self.parse_options(parameter_s,'prn:')
310 311
311 312 try:
312 313 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
313 314 except MacroToEdit as e:
314 315 # TODO: Implement macro editing over 2 processes.
315 316 print("Macro editing not yet implemented in 2-process model.")
316 317 return
317 318
318 319 # Make sure we send to the client an absolute path, in case the working
319 320 # directory of client and kernel don't match
320 321 filename = os.path.abspath(filename)
321 322
322 323 payload = {
323 324 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
324 325 'filename' : filename,
325 326 'line_number' : lineno
326 327 }
327 328 self.shell.payload_manager.write_payload(payload)
328 329
329 330 # A few magics that are adapted to the specifics of using pexpect and a
330 331 # remote terminal
331 332
332 333 @line_magic
333 334 def clear(self, arg_s):
334 335 """Clear the terminal."""
335 336 if os.name == 'posix':
336 337 self.shell.system("clear")
337 338 else:
338 339 self.shell.system("cls")
339 340
340 341 if os.name == 'nt':
341 342 # This is the usual name in windows
342 343 cls = line_magic('cls')(clear)
343 344
344 345 # Terminal pagers won't work over pexpect, but we do have our own pager
345 346
346 347 @line_magic
347 348 def less(self, arg_s):
348 349 """Show a file through the pager.
349 350
350 351 Files ending in .py are syntax-highlighted."""
351 352 cont = open(arg_s).read()
352 353 if arg_s.endswith('.py'):
353 354 cont = self.shell.pycolorize(cont)
354 355 page.page(cont)
355 356
356 357 more = line_magic('more')(less)
357 358
358 359 # Man calls a pager, so we also need to redefine it
359 360 if os.name == 'posix':
360 361 @line_magic
361 362 def man(self, arg_s):
362 363 """Find the man page for the given command and display in pager."""
363 364 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
364 365 split=False))
365 366
366 367 @line_magic
367 368 def connect_info(self, arg_s):
368 369 """Print information for connecting other clients to this kernel
369 370
370 371 It will print the contents of this session's connection file, as well as
371 372 shortcuts for local clients.
372 373
373 374 In the simplest case, when called from the most recently launched kernel,
374 375 secondary clients can be connected, simply with:
375 376
376 377 $> ipython <app> --existing
377 378
378 379 """
379 380
380 381 from IPython.core.application import BaseIPythonApplication as BaseIPApp
381 382
382 383 if BaseIPApp.initialized():
383 384 app = BaseIPApp.instance()
384 385 security_dir = app.profile_dir.security_dir
385 386 profile = app.profile
386 387 else:
387 388 profile = 'default'
388 389 security_dir = ''
389 390
390 391 try:
391 392 connection_file = get_connection_file()
392 393 info = get_connection_info(unpack=False)
393 394 except Exception as e:
394 395 error("Could not get connection info: %r" % e)
395 396 return
396 397
397 398 # add profile flag for non-default profile
398 399 profile_flag = "--profile %s" % profile if profile != 'default' else ""
399 400
400 401 # if it's in the security dir, truncate to basename
401 402 if security_dir == os.path.dirname(connection_file):
402 403 connection_file = os.path.basename(connection_file)
403 404
404 405
405 406 print (info + '\n')
406 407 print ("Paste the above JSON into a file, and connect with:\n"
407 408 " $> ipython <app> --existing <file>\n"
408 409 "or, if you are local, you can connect with just:\n"
409 410 " $> ipython <app> --existing {0} {1}\n"
410 411 "or even just:\n"
411 412 " $> ipython <app> --existing {1}\n"
412 413 "if this is the most recent IPython session you have started.".format(
413 414 connection_file, profile_flag
414 415 )
415 416 )
416 417
417 418 @line_magic
418 419 def qtconsole(self, arg_s):
419 420 """Open a qtconsole connected to this kernel.
420 421
421 422 Useful for connecting a qtconsole to running notebooks, for better
422 423 debugging.
423 424 """
424 425
425 426 # %qtconsole should imply bind_kernel for engines:
426 427 try:
427 428 from IPython.parallel import bind_kernel
428 429 except ImportError:
429 430 # technically possible, because parallel has higher pyzmq min-version
430 431 pass
431 432 else:
432 433 bind_kernel()
433 434
434 435 try:
435 436 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
436 437 except Exception as e:
437 438 error("Could not start qtconsole: %r" % e)
438 439 return
439 440
440 441 def safe_unicode(e):
441 442 """unicode(e) with various fallbacks. Used for exceptions, which may not be
442 443 safe to call unicode() on.
443 444 """
444 445 try:
445 446 return unicode(e)
446 447 except UnicodeError:
447 448 pass
448 449
449 450 try:
450 451 return py3compat.str_to_unicode(str(e))
451 452 except UnicodeError:
452 453 pass
453 454
454 455 try:
455 456 return py3compat.str_to_unicode(repr(e))
456 457 except UnicodeError:
457 458 pass
458 459
459 460 return u'Unrecoverably corrupt evalue'
460 461
461 462
462 463 class ZMQInteractiveShell(InteractiveShell):
463 464 """A subclass of InteractiveShell for ZMQ."""
464 465
465 466 displayhook_class = Type(ZMQShellDisplayHook)
466 467 display_pub_class = Type(ZMQDisplayPublisher)
468 data_pub_class = Type(ZMQDataPublisher)
467 469
468 470 # Override the traitlet in the parent class, because there's no point using
469 471 # readline for the kernel. Can be removed when the readline code is moved
470 472 # to the terminal frontend.
471 473 colors_force = CBool(True)
472 474 readline_use = CBool(False)
473 475 # autoindent has no meaning in a zmqshell, and attempting to enable it
474 476 # will print a warning in the absence of readline.
475 477 autoindent = CBool(False)
476 478
477 479 exiter = Instance(ZMQExitAutocall)
478 480 def _exiter_default(self):
479 481 return ZMQExitAutocall(self)
480 482
481 483 def _exit_now_changed(self, name, old, new):
482 484 """stop eventloop when exit_now fires"""
483 485 if new:
484 486 loop = ioloop.IOLoop.instance()
485 487 loop.add_timeout(time.time()+0.1, loop.stop)
486 488
487 489 keepkernel_on_exit = None
488 490
489 491 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
490 492 # interactive input being read; we provide event loop support in ipkernel
491 493 from .eventloops import enable_gui
492 494 enable_gui = staticmethod(enable_gui)
493 495
494 496 def init_environment(self):
495 497 """Configure the user's environment.
496 498
497 499 """
498 500 env = os.environ
499 501 # These two ensure 'ls' produces nice coloring on BSD-derived systems
500 502 env['TERM'] = 'xterm-color'
501 503 env['CLICOLOR'] = '1'
502 504 # Since normal pagers don't work at all (over pexpect we don't have
503 505 # single-key control of the subprocess), try to disable paging in
504 506 # subprocesses as much as possible.
505 507 env['PAGER'] = 'cat'
506 508 env['GIT_PAGER'] = 'cat'
507 509
508 510 # And install the payload version of page.
509 511 install_payload_page()
510 512
511 513 def auto_rewrite_input(self, cmd):
512 514 """Called to show the auto-rewritten input for autocall and friends.
513 515
514 516 FIXME: this payload is currently not correctly processed by the
515 517 frontend.
516 518 """
517 519 new = self.prompt_manager.render('rewrite') + cmd
518 520 payload = dict(
519 521 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
520 522 transformed_input=new,
521 523 )
522 524 self.payload_manager.write_payload(payload)
523 525
524 526 def ask_exit(self):
525 527 """Engage the exit actions."""
526 528 self.exit_now = True
527 529 payload = dict(
528 530 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
529 531 exit=True,
530 532 keepkernel=self.keepkernel_on_exit,
531 533 )
532 534 self.payload_manager.write_payload(payload)
533 535
534 536 def _showtraceback(self, etype, evalue, stb):
535 537
536 538 exc_content = {
537 539 u'traceback' : stb,
538 540 u'ename' : unicode(etype.__name__),
539 541 u'evalue' : safe_unicode(evalue)
540 542 }
541 543
542 544 dh = self.displayhook
543 545 # Send exception info over pub socket for other clients than the caller
544 546 # to pick up
545 547 topic = None
546 548 if dh.topic:
547 549 topic = dh.topic.replace(b'pyout', b'pyerr')
548 550
549 551 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
550 552
551 553 # FIXME - Hack: store exception info in shell object. Right now, the
552 554 # caller is reading this info after the fact, we need to fix this logic
553 555 # to remove this hack. Even uglier, we need to store the error status
554 556 # here, because in the main loop, the logic that sets it is being
555 557 # skipped because runlines swallows the exceptions.
556 558 exc_content[u'status'] = u'error'
557 559 self._reply_content = exc_content
558 560 # /FIXME
559 561
560 562 return exc_content
561 563
562 564 def set_next_input(self, text):
563 565 """Send the specified text to the frontend to be presented at the next
564 566 input cell."""
565 567 payload = dict(
566 568 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
567 569 text=text
568 570 )
569 571 self.payload_manager.write_payload(payload)
570 572
571 573 #-------------------------------------------------------------------------
572 574 # Things related to magics
573 575 #-------------------------------------------------------------------------
574 576
575 577 def init_magics(self):
576 578 super(ZMQInteractiveShell, self).init_magics()
577 579 self.register_magics(KernelMagics)
578 580 self.magics_manager.register_alias('ed', 'edit')
579 581
580 582
581 583
582 584 InteractiveShellABC.register(ZMQInteractiveShell)
@@ -1,959 +1,997
1 1 .. _messaging:
2 2
3 3 ======================
4 4 Messaging in IPython
5 5 ======================
6 6
7 7
8 8 Introduction
9 9 ============
10 10
11 11 This document explains the basic communications design and messaging
12 12 specification for how the various IPython objects interact over a network
13 13 transport. The current implementation uses the ZeroMQ_ library for messaging
14 14 within and between hosts.
15 15
16 16 .. Note::
17 17
18 18 This document should be considered the authoritative description of the
19 19 IPython messaging protocol, and all developers are strongly encouraged to
20 20 keep it updated as the implementation evolves, so that we have a single
21 21 common reference for all protocol details.
22 22
23 23 The basic design is explained in the following diagram:
24 24
25 25 .. image:: figs/frontend-kernel.png
26 26 :width: 450px
27 27 :alt: IPython kernel/frontend messaging architecture.
28 28 :align: center
29 29 :target: ../_images/frontend-kernel.png
30 30
31 31 A single kernel can be simultaneously connected to one or more frontends. The
32 32 kernel has three sockets that serve the following functions:
33 33
34 34 1. stdin: this ROUTER socket is connected to all frontends, and it allows
35 35 the kernel to request input from the active frontend when :func:`raw_input` is called.
36 36 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
37 37 for the kernel while this communication is happening (illustrated in the
38 38 figure by the black outline around the central keyboard). In practice,
39 39 frontends may display such kernel requests using a special input widget or
40 40 otherwise indicating that the user is to type input for the kernel instead
41 41 of normal commands in the frontend.
42 42
43 43 2. Shell: this single ROUTER socket allows multiple incoming connections from
44 44 frontends, and this is the socket where requests for code execution, object
45 45 information, prompts, etc. are made to the kernel by any frontend. The
46 46 communication on this socket is a sequence of request/reply actions from
47 47 each frontend and the kernel.
48 48
49 49 3. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
50 50 side effects (stdout, stderr, etc.) as well as the requests coming from any
51 51 client over the shell socket and its own requests on the stdin socket. There
52 52 are a number of actions in Python which generate side effects: :func:`print`
53 53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
54 54 a multi-client scenario, we want all frontends to be able to know what each
55 55 other has sent to the kernel (this can be useful in collaborative scenarios,
56 56 for example). This socket allows both side effects and the information
57 57 about communications taking place with one client over the shell channel
58 58 to be made available to all clients in a uniform manner.
59 59
60 60 All messages are tagged with enough information (details below) for clients
61 61 to know which messages come from their own interaction with the kernel and
62 62 which ones are from other clients, so they can display each type
63 63 appropriately.
64 64
65 65 The actual format of the messages allowed on each of these channels is
66 66 specified below. Messages are dicts of dicts with string keys and values that
67 67 are reasonably representable in JSON. Our current implementation uses JSON
68 68 explicitly as its message format, but this shouldn't be considered a permanent
69 69 feature. As we've discovered that JSON has non-trivial performance issues due
70 70 to excessive copying, we may in the future move to a pure pickle-based raw
71 71 message format. However, it should be possible to easily convert from the raw
72 72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
73 73 As long as it's easy to make a JSON version of the objects that is a faithful
74 74 representation of all the data, we can communicate with such clients.
75 75
76 76 .. Note::
77 77
78 78 Not all of these have yet been fully fleshed out, but the key ones are, see
79 79 kernel and frontend files for actual implementation details.
80 80
81 81 General Message Format
82 82 ======================
83 83
84 84 A message is defined by the following four-dictionary structure::
85 85
86 86 {
87 87 # The message header contains a pair of unique identifiers for the
88 88 # originating session and the actual message id, in addition to the
89 89 # username for the process that generated the message. This is useful in
90 90 # collaborative settings where multiple users may be interacting with the
91 91 # same kernel simultaneously, so that frontends can label the various
92 92 # messages in a meaningful way.
93 93 'header' : {
94 94 'msg_id' : uuid,
95 95 'username' : str,
96 96 'session' : uuid
97 97 # All recognized message type strings are listed below.
98 98 'msg_type' : str,
99 99 },
100 100
101 101 # In a chain of messages, the header from the parent is copied so that
102 102 # clients can track where messages come from.
103 103 'parent_header' : dict,
104 104
105 105 # The actual content of the message must be a dict, whose structure
106 106 # depends on the message type.
107 107 'content' : dict,
108 108
109 109 # Any metadata associated with the message.
110 110 'metadata' : dict,
111 111 }
112 112
113 113
114 114 Python functional API
115 115 =====================
116 116
117 117 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
118 118 should develop, at a few key points, functional forms of all the requests that
119 119 take arguments in this manner and automatically construct the necessary dict
120 120 for sending.
121 121
122 122 In addition, the Python implementation of the message specification extends
123 123 messages upon deserialization to the following form for convenience::
124 124
125 125 {
126 126 'header' : dict,
127 127 # The msg's unique identifier and type are always stored in the header,
128 128 # but the Python implementation copies them to the top level.
129 129 'msg_id' : uuid,
130 130 'msg_type' : str,
131 131 'parent_header' : dict,
132 132 'content' : dict,
133 133 'metadata' : dict,
134 134 }
135 135
136 136 All messages sent to or received by any IPython process should have this
137 137 extended structure.
138 138
139 139
140 140 Messages on the shell ROUTER/DEALER sockets
141 141 ===========================================
142 142
143 143 .. _execute:
144 144
145 145 Execute
146 146 -------
147 147
148 148 This message type is used by frontends to ask the kernel to execute code on
149 149 behalf of the user, in a namespace reserved to the user's variables (and thus
150 150 separate from the kernel's own internal code and variables).
151 151
152 152 Message type: ``execute_request``::
153 153
154 154 content = {
155 155 # Source code to be executed by the kernel, one or more lines.
156 156 'code' : str,
157 157
158 158 # A boolean flag which, if True, signals the kernel to execute
159 159 # this code as quietly as possible. This means that the kernel
160 160 # will compile the code with 'exec' instead of 'single' (so
161 161 # sys.displayhook will not fire), and will *not*:
162 162 # - broadcast exceptions on the PUB socket
163 163 # - do any logging
164 164 # - populate any history
165 165 #
166 166 # The default is False.
167 167 'silent' : bool,
168 168
169 169 # A list of variable names from the user's namespace to be retrieved. What
170 170 # returns is a JSON string of the variable's repr(), not a python object.
171 171 'user_variables' : list,
172 172
173 173 # Similarly, a dict mapping names to expressions to be evaluated in the
174 174 # user's dict.
175 175 'user_expressions' : dict,
176 176
177 177 # Some frontends (e.g. the Notebook) do not support stdin requests. If
178 178 # raw_input is called from code executed from such a frontend, a
179 179 # StdinNotImplementedError will be raised.
180 180 'allow_stdin' : True,
181 181
182 182 }
183 183
184 184 The ``code`` field contains a single string (possibly multiline). The kernel
185 185 is responsible for splitting this into one or more independent execution blocks
186 186 and deciding whether to compile these in 'single' or 'exec' mode (see below for
187 187 detailed execution semantics).
188 188
189 189 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
190 190 the notion of a prompt string that allowed arbitrary code to be evaluated, and
191 191 this was put to good use by many in creating prompts that displayed system
192 192 status, path information, and even more esoteric uses like remote instrument
193 193 status aqcuired over the network. But now that IPython has a clean separation
194 194 between the kernel and the clients, the kernel has no prompt knowledge; prompts
195 195 are a frontend-side feature, and it should be even possible for different
196 196 frontends to display different prompts while interacting with the same kernel.
197 197
198 198 The kernel now provides the ability to retrieve data from the user's namespace
199 199 after the execution of the main ``code``, thanks to two fields in the
200 200 ``execute_request`` message:
201 201
202 202 - ``user_variables``: If only variables from the user's namespace are needed, a
203 203 list of variable names can be passed and a dict with these names as keys and
204 204 their :func:`repr()` as values will be returned.
205 205
206 206 - ``user_expressions``: For more complex expressions that require function
207 207 evaluations, a dict can be provided with string keys and arbitrary python
208 208 expressions as values. The return message will contain also a dict with the
209 209 same keys and the :func:`repr()` of the evaluated expressions as value.
210 210
211 211 With this information, frontends can display any status information they wish
212 212 in the form that best suits each frontend (a status line, a popup, inline for a
213 213 terminal, etc).
214 214
215 215 .. Note::
216 216
217 217 In order to obtain the current execution counter for the purposes of
218 218 displaying input prompts, frontends simply make an execution request with an
219 219 empty code string and ``silent=True``.
220 220
221 221 Execution semantics
222 222 ~~~~~~~~~~~~~~~~~~~
223 223
224 224 When the silent flag is false, the execution of use code consists of the
225 225 following phases (in silent mode, only the ``code`` field is executed):
226 226
227 227 1. Run the ``pre_runcode_hook``.
228 228
229 229 2. Execute the ``code`` field, see below for details.
230 230
231 231 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
232 232 computed. This ensures that any error in the latter don't harm the main
233 233 code execution.
234 234
235 235 4. Call any method registered with :meth:`register_post_execute`.
236 236
237 237 .. warning::
238 238
239 239 The API for running code before/after the main code block is likely to
240 240 change soon. Both the ``pre_runcode_hook`` and the
241 241 :meth:`register_post_execute` are susceptible to modification, as we find a
242 242 consistent model for both.
243 243
244 244 To understand how the ``code`` field is executed, one must know that Python
245 245 code can be compiled in one of three modes (controlled by the ``mode`` argument
246 246 to the :func:`compile` builtin):
247 247
248 248 *single*
249 249 Valid for a single interactive statement (though the source can contain
250 250 multiple lines, such as a for loop). When compiled in this mode, the
251 251 generated bytecode contains special instructions that trigger the calling of
252 252 :func:`sys.displayhook` for any expression in the block that returns a value.
253 253 This means that a single statement can actually produce multiple calls to
254 254 :func:`sys.displayhook`, if for example it contains a loop where each
255 255 iteration computes an unassigned expression would generate 10 calls::
256 256
257 257 for i in range(10):
258 258 i**2
259 259
260 260 *exec*
261 261 An arbitrary amount of source code, this is how modules are compiled.
262 262 :func:`sys.displayhook` is *never* implicitly called.
263 263
264 264 *eval*
265 265 A single expression that returns a value. :func:`sys.displayhook` is *never*
266 266 implicitly called.
267 267
268 268
269 269 The ``code`` field is split into individual blocks each of which is valid for
270 270 execution in 'single' mode, and then:
271 271
272 272 - If there is only a single block: it is executed in 'single' mode.
273 273
274 274 - If there is more than one block:
275 275
276 276 * if the last one is a single line long, run all but the last in 'exec' mode
277 277 and the very last one in 'single' mode. This makes it easy to type simple
278 278 expressions at the end to see computed values.
279 279
280 280 * if the last one is no more than two lines long, run all but the last in
281 281 'exec' mode and the very last one in 'single' mode. This makes it easy to
282 282 type simple expressions at the end to see computed values. - otherwise
283 283 (last one is also multiline), run all in 'exec' mode
284 284
285 285 * otherwise (last one is also multiline), run all in 'exec' mode as a single
286 286 unit.
287 287
288 288 Any error in retrieving the ``user_variables`` or evaluating the
289 289 ``user_expressions`` will result in a simple error message in the return fields
290 290 of the form::
291 291
292 292 [ERROR] ExceptionType: Exception message
293 293
294 294 The user can simply send the same variable name or expression for evaluation to
295 295 see a regular traceback.
296 296
297 297 Errors in any registered post_execute functions are also reported similarly,
298 298 and the failing function is removed from the post_execution set so that it does
299 299 not continue triggering failures.
300 300
301 301 Upon completion of the execution request, the kernel *always* sends a reply,
302 302 with a status code indicating what happened and additional data depending on
303 303 the outcome. See :ref:`below <execution_results>` for the possible return
304 304 codes and associated data.
305 305
306 306
307 307 Execution counter (old prompt number)
308 308 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
309 309
310 310 The kernel has a single, monotonically increasing counter of all execution
311 311 requests that are made with ``silent=False``. This counter is used to populate
312 312 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
313 313 display it in some form to the user, which will typically (but not necessarily)
314 314 be done in the prompts. The value of this counter will be returned as the
315 315 ``execution_count`` field of all ``execute_reply`` messages.
316 316
317 317 .. _execution_results:
318 318
319 319 Execution results
320 320 ~~~~~~~~~~~~~~~~~
321 321
322 322 Message type: ``execute_reply``::
323 323
324 324 content = {
325 325 # One of: 'ok' OR 'error' OR 'abort'
326 326 'status' : str,
327 327
328 328 # The global kernel counter that increases by one with each non-silent
329 329 # executed request. This will typically be used by clients to display
330 330 # prompt numbers to the user. If the request was a silent one, this will
331 331 # be the current value of the counter in the kernel.
332 332 'execution_count' : int,
333 333 }
334 334
335 335 When status is 'ok', the following extra fields are present::
336 336
337 337 {
338 338 # 'payload' will be a list of payload dicts.
339 339 # Each execution payload is a dict with string keys that may have been
340 340 # produced by the code being executed. It is retrieved by the kernel at
341 341 # the end of the execution and sent back to the front end, which can take
342 342 # action on it as needed. See main text for further details.
343 343 'payload' : list(dict),
344 344
345 345 # Results for the user_variables and user_expressions.
346 346 'user_variables' : dict,
347 347 'user_expressions' : dict,
348 348 }
349 349
350 350 .. admonition:: Execution payloads
351 351
352 352 The notion of an 'execution payload' is different from a return value of a
353 353 given set of code, which normally is just displayed on the pyout stream
354 354 through the PUB socket. The idea of a payload is to allow special types of
355 355 code, typically magics, to populate a data container in the IPython kernel
356 356 that will be shipped back to the caller via this channel. The kernel
357 357 has an API for this in the PayloadManager::
358 358
359 359 ip.payload_manager.write_payload(payload_dict)
360 360
361 361 which appends a dictionary to the list of payloads.
362 362
363 363
364 364 When status is 'error', the following extra fields are present::
365 365
366 366 {
367 367 'ename' : str, # Exception name, as a string
368 368 'evalue' : str, # Exception value, as a string
369 369
370 370 # The traceback will contain a list of frames, represented each as a
371 371 # string. For now we'll stick to the existing design of ultraTB, which
372 372 # controls exception level of detail statefully. But eventually we'll
373 373 # want to grow into a model where more information is collected and
374 374 # packed into the traceback object, with clients deciding how little or
375 375 # how much of it to unpack. But for now, let's start with a simple list
376 376 # of strings, since that requires only minimal changes to ultratb as
377 377 # written.
378 378 'traceback' : list,
379 379 }
380 380
381 381
382 382 When status is 'abort', there are for now no additional data fields. This
383 383 happens when the kernel was interrupted by a signal.
384 384
385 385 Kernel attribute access
386 386 -----------------------
387 387
388 388 .. warning::
389 389
390 390 This part of the messaging spec is not actually implemented in the kernel
391 391 yet.
392 392
393 393 While this protocol does not specify full RPC access to arbitrary methods of
394 394 the kernel object, the kernel does allow read (and in some cases write) access
395 395 to certain attributes.
396 396
397 397 The policy for which attributes can be read is: any attribute of the kernel, or
398 398 its sub-objects, that belongs to a :class:`Configurable` object and has been
399 399 declared at the class-level with Traits validation, is in principle accessible
400 400 as long as its name does not begin with a leading underscore. The attribute
401 401 itself will have metadata indicating whether it allows remote read and/or write
402 402 access. The message spec follows for attribute read and write requests.
403 403
404 404 Message type: ``getattr_request``::
405 405
406 406 content = {
407 407 # The (possibly dotted) name of the attribute
408 408 'name' : str,
409 409 }
410 410
411 411 When a ``getattr_request`` fails, there are two possible error types:
412 412
413 413 - AttributeError: this type of error was raised when trying to access the
414 414 given name by the kernel itself. This means that the attribute likely
415 415 doesn't exist.
416 416
417 417 - AccessError: the attribute exists but its value is not readable remotely.
418 418
419 419
420 420 Message type: ``getattr_reply``::
421 421
422 422 content = {
423 423 # One of ['ok', 'AttributeError', 'AccessError'].
424 424 'status' : str,
425 425 # If status is 'ok', a JSON object.
426 426 'value' : object,
427 427 }
428 428
429 429 Message type: ``setattr_request``::
430 430
431 431 content = {
432 432 # The (possibly dotted) name of the attribute
433 433 'name' : str,
434 434
435 435 # A JSON-encoded object, that will be validated by the Traits
436 436 # information in the kernel
437 437 'value' : object,
438 438 }
439 439
440 440 When a ``setattr_request`` fails, there are also two possible error types with
441 441 similar meanings as those of the ``getattr_request`` case, but for writing.
442 442
443 443 Message type: ``setattr_reply``::
444 444
445 445 content = {
446 446 # One of ['ok', 'AttributeError', 'AccessError'].
447 447 'status' : str,
448 448 }
449 449
450 450
451 451
452 452 Object information
453 453 ------------------
454 454
455 455 One of IPython's most used capabilities is the introspection of Python objects
456 456 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
457 457 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
458 458 enough that it warrants an explicit message type, especially because frontends
459 459 may want to get object information in response to user keystrokes (like Tab or
460 460 F1) besides from the user explicitly typing code like ``x??``.
461 461
462 462 Message type: ``object_info_request``::
463 463
464 464 content = {
465 465 # The (possibly dotted) name of the object to be searched in all
466 466 # relevant namespaces
467 467 'name' : str,
468 468
469 469 # The level of detail desired. The default (0) is equivalent to typing
470 470 # 'x?' at the prompt, 1 is equivalent to 'x??'.
471 471 'detail_level' : int,
472 472 }
473 473
474 474 The returned information will be a dictionary with keys very similar to the
475 475 field names that IPython prints at the terminal.
476 476
477 477 Message type: ``object_info_reply``::
478 478
479 479 content = {
480 480 # The name the object was requested under
481 481 'name' : str,
482 482
483 483 # Boolean flag indicating whether the named object was found or not. If
484 484 # it's false, all other fields will be empty.
485 485 'found' : bool,
486 486
487 487 # Flags for magics and system aliases
488 488 'ismagic' : bool,
489 489 'isalias' : bool,
490 490
491 491 # The name of the namespace where the object was found ('builtin',
492 492 # 'magics', 'alias', 'interactive', etc.)
493 493 'namespace' : str,
494 494
495 495 # The type name will be type.__name__ for normal Python objects, but it
496 496 # can also be a string like 'Magic function' or 'System alias'
497 497 'type_name' : str,
498 498
499 499 # The string form of the object, possibly truncated for length if
500 500 # detail_level is 0
501 501 'string_form' : str,
502 502
503 503 # For objects with a __class__ attribute this will be set
504 504 'base_class' : str,
505 505
506 506 # For objects with a __len__ attribute this will be set
507 507 'length' : int,
508 508
509 509 # If the object is a function, class or method whose file we can find,
510 510 # we give its full path
511 511 'file' : str,
512 512
513 513 # For pure Python callable objects, we can reconstruct the object
514 514 # definition line which provides its call signature. For convenience this
515 515 # is returned as a single 'definition' field, but below the raw parts that
516 516 # compose it are also returned as the argspec field.
517 517 'definition' : str,
518 518
519 519 # The individual parts that together form the definition string. Clients
520 520 # with rich display capabilities may use this to provide a richer and more
521 521 # precise representation of the definition line (e.g. by highlighting
522 522 # arguments based on the user's cursor position). For non-callable
523 523 # objects, this field is empty.
524 524 'argspec' : { # The names of all the arguments
525 525 args : list,
526 526 # The name of the varargs (*args), if any
527 527 varargs : str,
528 528 # The name of the varkw (**kw), if any
529 529 varkw : str,
530 530 # The values (as strings) of all default arguments. Note
531 531 # that these must be matched *in reverse* with the 'args'
532 532 # list above, since the first positional args have no default
533 533 # value at all.
534 534 defaults : list,
535 535 },
536 536
537 537 # For instances, provide the constructor signature (the definition of
538 538 # the __init__ method):
539 539 'init_definition' : str,
540 540
541 541 # Docstrings: for any object (function, method, module, package) with a
542 542 # docstring, we show it. But in addition, we may provide additional
543 543 # docstrings. For example, for instances we will show the constructor
544 544 # and class docstrings as well, if available.
545 545 'docstring' : str,
546 546
547 547 # For instances, provide the constructor and class docstrings
548 548 'init_docstring' : str,
549 549 'class_docstring' : str,
550 550
551 551 # If it's a callable object whose call method has a separate docstring and
552 552 # definition line:
553 553 'call_def' : str,
554 554 'call_docstring' : str,
555 555
556 556 # If detail_level was 1, we also try to find the source code that
557 557 # defines the object, if possible. The string 'None' will indicate
558 558 # that no source was found.
559 559 'source' : str,
560 560 }
561 561
562 562
563 563 Complete
564 564 --------
565 565
566 566 Message type: ``complete_request``::
567 567
568 568 content = {
569 569 # The text to be completed, such as 'a.is'
570 570 'text' : str,
571 571
572 572 # The full line, such as 'print a.is'. This allows completers to
573 573 # make decisions that may require information about more than just the
574 574 # current word.
575 575 'line' : str,
576 576
577 577 # The entire block of text where the line is. This may be useful in the
578 578 # case of multiline completions where more context may be needed. Note: if
579 579 # in practice this field proves unnecessary, remove it to lighten the
580 580 # messages.
581 581
582 582 'block' : str,
583 583
584 584 # The position of the cursor where the user hit 'TAB' on the line.
585 585 'cursor_pos' : int,
586 586 }
587 587
588 588 Message type: ``complete_reply``::
589 589
590 590 content = {
591 591 # The list of all matches to the completion request, such as
592 592 # ['a.isalnum', 'a.isalpha'] for the above example.
593 593 'matches' : list
594 594 }
595 595
596 596
597 597 History
598 598 -------
599 599
600 600 For clients to explicitly request history from a kernel. The kernel has all
601 601 the actual execution history stored in a single location, so clients can
602 602 request it from the kernel when needed.
603 603
604 604 Message type: ``history_request``::
605 605
606 606 content = {
607 607
608 608 # If True, also return output history in the resulting dict.
609 609 'output' : bool,
610 610
611 611 # If True, return the raw input history, else the transformed input.
612 612 'raw' : bool,
613 613
614 614 # So far, this can be 'range', 'tail' or 'search'.
615 615 'hist_access_type' : str,
616 616
617 617 # If hist_access_type is 'range', get a range of input cells. session can
618 618 # be a positive session number, or a negative number to count back from
619 619 # the current session.
620 620 'session' : int,
621 621 # start and stop are line numbers within that session.
622 622 'start' : int,
623 623 'stop' : int,
624 624
625 625 # If hist_access_type is 'tail', get the last n cells.
626 626 'n' : int,
627 627
628 628 # If hist_access_type is 'search', get cells matching the specified glob
629 629 # pattern (with * and ? as wildcards).
630 630 'pattern' : str,
631 631
632 632 }
633 633
634 634 Message type: ``history_reply``::
635 635
636 636 content = {
637 637 # A list of 3 tuples, either:
638 638 # (session, line_number, input) or
639 639 # (session, line_number, (input, output)),
640 640 # depending on whether output was False or True, respectively.
641 641 'history' : list,
642 642 }
643 643
644 644
645 645 Connect
646 646 -------
647 647
648 648 When a client connects to the request/reply socket of the kernel, it can issue
649 649 a connect request to get basic information about the kernel, such as the ports
650 650 the other ZeroMQ sockets are listening on. This allows clients to only have
651 651 to know about a single port (the shell channel) to connect to a kernel.
652 652
653 653 Message type: ``connect_request``::
654 654
655 655 content = {
656 656 }
657 657
658 658 Message type: ``connect_reply``::
659 659
660 660 content = {
661 661 'shell_port' : int # The port the shell ROUTER socket is listening on.
662 662 'iopub_port' : int # The port the PUB socket is listening on.
663 663 'stdin_port' : int # The port the stdin ROUTER socket is listening on.
664 664 'hb_port' : int # The port the heartbeat socket is listening on.
665 665 }
666 666
667 667
668 668
669 669 Kernel shutdown
670 670 ---------------
671 671
672 672 The clients can request the kernel to shut itself down; this is used in
673 673 multiple cases:
674 674
675 675 - when the user chooses to close the client application via a menu or window
676 676 control.
677 677 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
678 678 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
679 679 IPythonQt client) to force a kernel restart to get a clean kernel without
680 680 losing client-side state like history or inlined figures.
681 681
682 682 The client sends a shutdown request to the kernel, and once it receives the
683 683 reply message (which is otherwise empty), it can assume that the kernel has
684 684 completed shutdown safely.
685 685
686 686 Upon their own shutdown, client applications will typically execute a last
687 687 minute sanity check and forcefully terminate any kernel that is still alive, to
688 688 avoid leaving stray processes in the user's machine.
689 689
690 690 For both shutdown request and reply, there is no actual content that needs to
691 691 be sent, so the content dict is empty.
692 692
693 693 Message type: ``shutdown_request``::
694 694
695 695 content = {
696 696 'restart' : bool # whether the shutdown is final, or precedes a restart
697 697 }
698 698
699 699 Message type: ``shutdown_reply``::
700 700
701 701 content = {
702 702 'restart' : bool # whether the shutdown is final, or precedes a restart
703 703 }
704 704
705 705 .. Note::
706 706
707 707 When the clients detect a dead kernel thanks to inactivity on the heartbeat
708 708 socket, they simply send a forceful process termination signal, since a dead
709 709 process is unlikely to respond in any useful way to messages.
710 710
711 711
712 712 Messages on the PUB/SUB socket
713 713 ==============================
714 714
715 715 Streams (stdout, stderr, etc)
716 716 ------------------------------
717 717
718 718 Message type: ``stream``::
719 719
720 720 content = {
721 721 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
722 722 'name' : str,
723 723
724 724 # The data is an arbitrary string to be written to that stream
725 725 'data' : str,
726 726 }
727 727
728 728 When a kernel receives a raw_input call, it should also broadcast it on the pub
729 729 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
730 730 to monitor/display kernel interactions and possibly replay them to their user
731 731 or otherwise expose them.
732 732
733 733 Display Data
734 734 ------------
735 735
736 736 This type of message is used to bring back data that should be diplayed (text,
737 737 html, svg, etc.) in the frontends. This data is published to all frontends.
738 738 Each message can have multiple representations of the data; it is up to the
739 739 frontend to decide which to use and how. A single message should contain all
740 740 possible representations of the same information. Each representation should
741 741 be a JSON'able data structure, and should be a valid MIME type.
742 742
743 743 Some questions remain about this design:
744 744
745 745 * Do we use this message type for pyout/displayhook? Probably not, because
746 746 the displayhook also has to handle the Out prompt display. On the other hand
747 747 we could put that information into the metadata secion.
748 748
749 749 Message type: ``display_data``::
750 750
751 751 content = {
752 752
753 753 # Who create the data
754 754 'source' : str,
755 755
756 756 # The data dict contains key/value pairs, where the kids are MIME
757 757 # types and the values are the raw data of the representation in that
758 758 # format. The data dict must minimally contain the ``text/plain``
759 759 # MIME type which is used as a backup representation.
760 760 'data' : dict,
761 761
762 762 # Any metadata that describes the data
763 763 'metadata' : dict
764 764 }
765 765
766
767 Raw Data Publication
768 --------------------
769
770 ``display_data`` lets you publish *representations* of data, such as images and html.
771 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
772
773 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
774
775 .. sourcecode:: python
776
777 from IPython.zmq.datapub import publish_data
778 ns = dict(x=my_array)
779 publish_data(ns)
780
781
782 Message type: ``data_pub``::
783
784 content = {
785 # the keys of the data dict, after it has been unserialized
786 keys = ['a', 'b']
787 }
788 # the namespace dict will be serialized in the message buffers,
789 # which will have a length of at least one
790 buffers = ['pdict', ...]
791
792
793 The interpretation of a sequence of data_pub messages for a given parent request should be
794 to update a single namespace with subsequent results.
795
796 .. note::
797
798 No frontends directly handle data_pub messages at this time.
799 It is currently only used by the client/engines in :mod:`IPython.parallel`,
800 where engines may publish *data* to the Client,
801 of which the Client can then publish *representations* via ``display_data``
802 to various frontends.
803
766 804 Python inputs
767 805 -------------
768 806
769 807 These messages are the re-broadcast of the ``execute_request``.
770 808
771 809 Message type: ``pyin``::
772 810
773 811 content = {
774 812 'code' : str, # Source code to be executed, one or more lines
775 813
776 814 # The counter for this execution is also provided so that clients can
777 815 # display it, since IPython automatically creates variables called _iN
778 816 # (for input prompt In[N]).
779 817 'execution_count' : int
780 818 }
781 819
782 820 Python outputs
783 821 --------------
784 822
785 823 When Python produces output from code that has been compiled in with the
786 824 'single' flag to :func:`compile`, any expression that produces a value (such as
787 825 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
788 826 this value whatever it wants. The default behavior of ``sys.displayhook`` in
789 827 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
790 828 the value as long as it is not ``None`` (which isn't printed at all). In our
791 829 case, the kernel instantiates as ``sys.displayhook`` an object which has
792 830 similar behavior, but which instead of printing to stdout, broadcasts these
793 831 values as ``pyout`` messages for clients to display appropriately.
794 832
795 833 IPython's displayhook can handle multiple simultaneous formats depending on its
796 834 configuration. The default pretty-printed repr text is always given with the
797 835 ``data`` entry in this message. Any other formats are provided in the
798 836 ``extra_formats`` list. Frontends are free to display any or all of these
799 837 according to its capabilities. ``extra_formats`` list contains 3-tuples of an ID
800 838 string, a type string, and the data. The ID is unique to the formatter
801 839 implementation that created the data. Frontends will typically ignore the ID
802 840 unless if it has requested a particular formatter. The type string tells the
803 841 frontend how to interpret the data. It is often, but not always a MIME type.
804 842 Frontends should ignore types that it does not understand. The data itself is
805 843 any JSON object and depends on the format. It is often, but not always a string.
806 844
807 845 Message type: ``pyout``::
808 846
809 847 content = {
810 848
811 849 # The counter for this execution is also provided so that clients can
812 850 # display it, since IPython automatically creates variables called _N
813 851 # (for prompt N).
814 852 'execution_count' : int,
815 853
816 854 # The data dict contains key/value pairs, where the kids are MIME
817 855 # types and the values are the raw data of the representation in that
818 856 # format. The data dict must minimally contain the ``text/plain``
819 857 # MIME type which is used as a backup representation.
820 858 'data' : dict,
821 859
822 860 }
823 861
824 862 Python errors
825 863 -------------
826 864
827 865 When an error occurs during code execution
828 866
829 867 Message type: ``pyerr``::
830 868
831 869 content = {
832 870 # Similar content to the execute_reply messages for the 'error' case,
833 871 # except the 'status' field is omitted.
834 872 }
835 873
836 874 Kernel status
837 875 -------------
838 876
839 877 This message type is used by frontends to monitor the status of the kernel.
840 878
841 879 Message type: ``status``::
842 880
843 881 content = {
844 882 # When the kernel starts to execute code, it will enter the 'busy'
845 883 # state and when it finishes, it will enter the 'idle' state.
846 884 execution_state : ('busy', 'idle')
847 885 }
848 886
849 887 Kernel crashes
850 888 --------------
851 889
852 890 When the kernel has an unexpected exception, caught by the last-resort
853 891 sys.excepthook, we should broadcast the crash handler's output before exiting.
854 892 This will allow clients to notice that a kernel died, inform the user and
855 893 propose further actions.
856 894
857 895 Message type: ``crash``::
858 896
859 897 content = {
860 898 # Similarly to the 'error' case for execute_reply messages, this will
861 899 # contain ename, etype and traceback fields.
862 900
863 901 # An additional field with supplementary information such as where to
864 902 # send the crash message
865 903 'info' : str,
866 904 }
867 905
868 906
869 907 Future ideas
870 908 ------------
871 909
872 910 Other potential message types, currently unimplemented, listed below as ideas.
873 911
874 912 Message type: ``file``::
875 913
876 914 content = {
877 915 'path' : 'cool.jpg',
878 916 'mimetype' : str,
879 917 'data' : str,
880 918 }
881 919
882 920
883 921 Messages on the stdin ROUTER/DEALER sockets
884 922 ===========================================
885 923
886 924 This is a socket where the request/reply pattern goes in the opposite direction:
887 925 from the kernel to a *single* frontend, and its purpose is to allow
888 926 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
889 927 to be fulfilled by the client. The request should be made to the frontend that
890 928 made the execution request that prompted ``raw_input`` to be called. For now we
891 929 will keep these messages as simple as possible, since they only mean to convey
892 930 the ``raw_input(prompt)`` call.
893 931
894 932 Message type: ``input_request``::
895 933
896 934 content = { 'prompt' : str }
897 935
898 936 Message type: ``input_reply``::
899 937
900 938 content = { 'value' : str }
901 939
902 940 .. Note::
903 941
904 942 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
905 943 practice the kernel should behave like an interactive program. When a
906 944 program is opened on the console, the keyboard effectively takes over the
907 945 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
908 946 Since the IPython kernel effectively behaves like a console program (albeit
909 947 one whose "keyboard" is actually living in a separate process and
910 948 transported over the zmq connection), raw ``stdin`` isn't expected to be
911 949 available.
912 950
913 951
914 952 Heartbeat for kernels
915 953 =====================
916 954
917 955 Initially we had considered using messages like those above over ZMQ for a
918 956 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
919 957 alive at all, even if it may be busy executing user code). But this has the
920 958 problem that if the kernel is locked inside extension code, it wouldn't execute
921 959 the python heartbeat code. But it turns out that we can implement a basic
922 960 heartbeat with pure ZMQ, without using any Python messaging at all.
923 961
924 962 The monitor sends out a single zmq message (right now, it is a str of the
925 963 monitor's lifetime in seconds), and gets the same message right back, prefixed
926 964 with the zmq identity of the DEALER socket in the heartbeat process. This can be
927 965 a uuid, or even a full message, but there doesn't seem to be a need for packing
928 966 up a message when the sender and receiver are the exact same Python object.
929 967
930 968 The model is this::
931 969
932 970 monitor.send(str(self.lifetime)) # '1.2345678910'
933 971
934 972 and the monitor receives some number of messages of the form::
935 973
936 974 ['uuid-abcd-dead-beef', '1.2345678910']
937 975
938 976 where the first part is the zmq.IDENTITY of the heart's DEALER on the engine, and
939 977 the rest is the message sent by the monitor. No Python code ever has any
940 978 access to the message between the monitor's send, and the monitor's recv.
941 979
942 980
943 981 ToDo
944 982 ====
945 983
946 984 Missing things include:
947 985
948 986 * Important: finish thinking through the payload concept and API.
949 987
950 988 * Important: ensure that we have a good solution for magics like %edit. It's
951 989 likely that with the payload concept we can build a full solution, but not
952 990 100% clear yet.
953 991
954 992 * Finishing the details of the heartbeat protocol.
955 993
956 994 * Signal handling: specify what kind of information kernel should broadcast (or
957 995 not) when it receives signals.
958 996
959 997 .. include:: ../links.rst
@@ -1,368 +1,367
1 1 .. _parallel_messages:
2 2
3 3 Messaging for Parallel Computing
4 4 ================================
5 5
6 6 This is an extension of the :ref:`messaging <messaging>` doc. Diagrams of the connections
7 7 can be found in the :ref:`parallel connections <parallel_connections>` doc.
8 8
9 9
10 10 ZMQ messaging is also used in the parallel computing IPython system. All messages to/from
11 11 kernels remain the same as the single kernel model, and are forwarded through a ZMQ Queue
12 12 device. The controller receives all messages and replies in these channels, and saves
13 13 results for future use.
14 14
15 15 The Controller
16 16 --------------
17 17
18 18 The controller is the central collection of processes in the IPython parallel computing
19 19 model. It has two major components:
20 20
21 21 * The Hub
22 22 * A collection of Schedulers
23 23
24 24 The Hub
25 25 -------
26 26
27 27 The Hub is the central process for monitoring the state of the engines, and all task
28 28 requests and results. It has no role in execution and does no relay of messages, so
29 29 large blocking requests or database actions in the Hub do not have the ability to impede
30 30 job submission and results.
31 31
32 32 Registration (``ROUTER``)
33 ***********************
33 *************************
34 34
35 35 The first function of the Hub is to facilitate and monitor connections of clients
36 36 and engines. Both client and engine registration are handled by the same socket, so only
37 37 one ip/port pair is needed to connect any number of connections and clients.
38 38
39 39 Engines register with the ``zmq.IDENTITY`` of their two ``DEALER`` sockets, one for the
40 40 queue, which receives execute requests, and one for the heartbeat, which is used to
41 41 monitor the survival of the Engine process.
42 42
43 43 Message type: ``registration_request``::
44 44
45 45 content = {
46 46 'uuid' : 'abcd-1234-...', # the zmq.IDENTITY of the engine's sockets
47 47 }
48 48
49 49 .. note::
50 50
51 51 these are always the same, at least for now.
52 52
53 53 The Controller replies to an Engine's registration request with the engine's integer ID,
54 54 and all the remaining connection information for connecting the heartbeat process, and
55 55 kernel queue socket(s). The message status will be an error if the Engine requests IDs that
56 56 already in use.
57 57
58 58 Message type: ``registration_reply``::
59 59
60 60 content = {
61 61 'status' : 'ok', # or 'error'
62 62 # if ok:
63 63 'id' : 0, # int, the engine id
64 64 }
65 65
66 66 Clients use the same socket as engines to start their connections. Connection requests
67 67 from clients need no information:
68 68
69 69 Message type: ``connection_request``::
70 70
71 71 content = {}
72 72
73 73 The reply to a Client registration request contains the connection information for the
74 74 multiplexer and load balanced queues, as well as the address for direct hub
75 75 queries. If any of these addresses is `None`, that functionality is not available.
76 76
77 77 Message type: ``connection_reply``::
78 78
79 79 content = {
80 80 'status' : 'ok', # or 'error'
81 81 }
82 82
83 83 Heartbeat
84 84 *********
85 85
86 86 The hub uses a heartbeat system to monitor engines, and track when they become
87 87 unresponsive. As described in :ref:`messaging <messaging>`, and shown in :ref:`connections
88 88 <parallel_connections>`.
89 89
90 90 Notification (``PUB``)
91 91 **********************
92 92
93 93 The hub publishes all engine registration/unregistration events on a ``PUB`` socket.
94 94 This allows clients to have up-to-date engine ID sets without polling. Registration
95 95 notifications contain both the integer engine ID and the queue ID, which is necessary for
96 96 sending messages via the Multiplexer Queue and Control Queues.
97 97
98 98 Message type: ``registration_notification``::
99 99
100 100 content = {
101 101 'id' : 0, # engine ID that has been registered
102 102 'uuid' : 'engine_id' # the IDENT for the engine's sockets
103 103 }
104 104
105 105 Message type : ``unregistration_notification``::
106 106
107 107 content = {
108 108 'id' : 0 # engine ID that has been unregistered
109 109 'uuid' : 'engine_id' # the IDENT for the engine's sockets
110 110 }
111 111
112 112
113 113 Client Queries (``ROUTER``)
114 *************************
114 ***************************
115 115
116 116 The hub monitors and logs all queue traffic, so that clients can retrieve past
117 117 results or monitor pending tasks. This information may reside in-memory on the Hub, or
118 118 on disk in a database (SQLite and MongoDB are currently supported). These requests are
119 119 handled by the same socket as registration.
120 120
121 121
122 122 :func:`queue_request` requests can specify multiple engines to query via the `targets`
123 123 element. A verbose flag can be passed, to determine whether the result should be the list
124 124 of `msg_ids` in the queue or simply the length of each list.
125 125
126 126 Message type: ``queue_request``::
127 127
128 128 content = {
129 129 'verbose' : True, # whether return should be lists themselves or just lens
130 130 'targets' : [0,3,1] # list of ints
131 131 }
132 132
133 133 The content of a reply to a :func:`queue_request` request is a dict, keyed by the engine
134 134 IDs. Note that they will be the string representation of the integer keys, since JSON
135 135 cannot handle number keys. The three keys of each dict are::
136 136
137 137 'completed' : messages submitted via any queue that ran on the engine
138 138 'queue' : jobs submitted via MUX queue, whose results have not been received
139 139 'tasks' : tasks that are known to have been submitted to the engine, but
140 140 have not completed. Note that with the pure zmq scheduler, this will
141 141 always be 0/[].
142 142
143 143 Message type: ``queue_reply``::
144 144
145 145 content = {
146 146 'status' : 'ok', # or 'error'
147 147 # if verbose=False:
148 148 '0' : {'completed' : 1, 'queue' : 7, 'tasks' : 0},
149 149 # if verbose=True:
150 150 '1' : {'completed' : ['abcd-...','1234-...'], 'queue' : ['58008-'], 'tasks' : []},
151 151 }
152 152
153 153 Clients can request individual results directly from the hub. This is primarily for
154 154 gathering results of executions not submitted by the requesting client, as the client
155 155 will have all its own results already. Requests are made by msg_id, and can contain one or
156 156 more msg_id. An additional boolean key 'statusonly' can be used to not request the
157 157 results, but simply poll the status of the jobs.
158 158
159 159 Message type: ``result_request``::
160 160
161 161 content = {
162 162 'msg_ids' : ['uuid','...'], # list of strs
163 163 'targets' : [1,2,3], # list of int ids or uuids
164 164 'statusonly' : False, # bool
165 165 }
166 166
167 167 The :func:`result_request` reply contains the content objects of the actual execution
168 168 reply messages. If `statusonly=True`, then there will be only the 'pending' and
169 169 'completed' lists.
170 170
171 171
172 172 Message type: ``result_reply``::
173 173
174 174 content = {
175 175 'status' : 'ok', # else error
176 176 # if ok:
177 177 'acbd-...' : msg, # the content dict is keyed by msg_ids,
178 178 # values are the result messages
179 179 # there will be none of these if `statusonly=True`
180 180 'pending' : ['msg_id','...'], # msg_ids still pending
181 181 'completed' : ['msg_id','...'], # list of completed msg_ids
182 182 }
183 183 buffers = ['bufs','...'] # the buffers that contained the results of the objects.
184 184 # this will be empty if no messages are complete, or if
185 185 # statusonly is True.
186 186
187 187 For memory management purposes, Clients can also instruct the hub to forget the
188 188 results of messages. This can be done by message ID or engine ID. Individual messages are
189 189 dropped by msg_id, and all messages completed on an engine are dropped by engine ID. This
190 190 may no longer be necessary with the mongodb-based message logging backend.
191 191
192 192 If the msg_ids element is the string ``'all'`` instead of a list, then all completed
193 193 results are forgotten.
194 194
195 195 Message type: ``purge_request``::
196 196
197 197 content = {
198 198 'msg_ids' : ['id1', 'id2',...], # list of msg_ids or 'all'
199 199 'engine_ids' : [0,2,4] # list of engine IDs
200 200 }
201 201
202 202 The reply to a purge request is simply the status 'ok' if the request succeeded, or an
203 203 explanation of why it failed, such as requesting the purge of a nonexistent or pending
204 204 message.
205 205
206 206 Message type: ``purge_reply``::
207 207
208 208 content = {
209 209 'status' : 'ok', # or 'error'
210 210 }
211 211
212 212
213 213 Schedulers
214 214 ----------
215 215
216 216 There are three basic schedulers:
217 217
218 218 * Task Scheduler
219 219 * MUX Scheduler
220 220 * Control Scheduler
221 221
222 222 The MUX and Control schedulers are simple MonitoredQueue ØMQ devices, with ``ROUTER``
223 223 sockets on either side. This allows the queue to relay individual messages to particular
224 224 targets via ``zmq.IDENTITY`` routing. The Task scheduler may be a MonitoredQueue ØMQ
225 225 device, in which case the client-facing socket is ``ROUTER``, and the engine-facing socket
226 226 is ``DEALER``. The result of this is that client-submitted messages are load-balanced via
227 227 the ``DEALER`` socket, but the engine's replies to each message go to the requesting client.
228 228
229 229 Raw ``DEALER`` scheduling is quite primitive, and doesn't allow message introspection, so
230 230 there are also Python Schedulers that can be used. These Schedulers behave in much the
231 231 same way as a MonitoredQueue does from the outside, but have rich internal logic to
232 232 determine destinations, as well as handle dependency graphs Their sockets are always
233 233 ``ROUTER`` on both sides.
234 234
235 235 The Python task schedulers have an additional message type, which informs the Hub of
236 236 the destination of a task as soon as that destination is known.
237 237
238 238 Message type: ``task_destination``::
239 239
240 240 content = {
241 241 'msg_id' : 'abcd-1234-...', # the msg's uuid
242 242 'engine_id' : '1234-abcd-...', # the destination engine's zmq.IDENTITY
243 243 }
244 244
245 :func:`apply` and :func:`apply_bound`
246 *************************************
245 :func:`apply`
246 *************
247 247
248 248 In terms of message classes, the MUX scheduler and Task scheduler relay the exact same
249 249 message types. Their only difference lies in how the destination is selected.
250 250
251 251 The `Namespace <http://gist.github.com/483294>`_ model suggests that execution be able to
252 252 use the model::
253 253
254 254 ns.apply(f, *args, **kwargs)
255 255
256 256 which takes `f`, a function in the user's namespace, and executes ``f(*args, **kwargs)``
257 257 on a remote engine, returning the result (or, for non-blocking, information facilitating
258 258 later retrieval of the result). This model, unlike the execute message which just uses a
259 259 code string, must be able to send arbitrary (pickleable) Python objects. And ideally, copy
260 260 as little data as we can. The `buffers` property of a Message was introduced for this
261 261 purpose.
262 262
263 Utility method :func:`build_apply_message` in :mod:`IPython.zmq.streamsession` wraps a
263 Utility method :func:`build_apply_message` in :mod:`IPython.zmq.serialize` wraps a
264 264 function signature and builds a sendable buffer format for minimal data copying (exactly
265 265 zero copies of numpy array data or buffers or large strings).
266 266
267 267 Message type: ``apply_request``::
268 268
269 content = {
270 'bound' : True, # whether to execute in the engine's namespace or unbound
269 metadata = {
271 270 'after' : ['msg_id',...], # list of msg_ids or output of Dependency.as_dict()
272 271 'follow' : ['msg_id',...], # list of msg_ids or output of Dependency.as_dict()
273
274 272 }
273 content = {}
275 274 buffers = ['...'] # at least 3 in length
276 275 # as built by build_apply_message(f,args,kwargs)
277 276
278 277 after/follow represent task dependencies. 'after' corresponds to a time dependency. The
279 278 request will not arrive at an engine until the 'after' dependency tasks have completed.
280 279 'follow' corresponds to a location dependency. The task will be submitted to the same
281 280 engine as these msg_ids (see :class:`Dependency` docs for details).
282 281
283 282 Message type: ``apply_reply``::
284 283
285 284 content = {
286 285 'status' : 'ok' # 'ok' or 'error'
287 286 # other error info here, as in other messages
288 287 }
289 288 buffers = ['...'] # either 1 or 2 in length
290 289 # a serialization of the return value of f(*args,**kwargs)
291 290 # only populated if status is 'ok'
292 291
293 292 All engine execution and data movement is performed via apply messages.
294 293
295 294 Control Messages
296 295 ----------------
297 296
298 297 Messages that interact with the engines, but are not meant to execute code, are submitted
299 298 via the Control queue. These messages have high priority, and are thus received and
300 299 handled before any execution requests.
301 300
302 301 Clients may want to clear the namespace on the engine. There are no arguments nor
303 302 information involved in this request, so the content is empty.
304 303
305 304 Message type: ``clear_request``::
306 305
307 306 content = {}
308 307
309 308 Message type: ``clear_reply``::
310 309
311 310 content = {
312 311 'status' : 'ok' # 'ok' or 'error'
313 312 # other error info here, as in other messages
314 313 }
315 314
316 315 Clients may want to abort tasks that have not yet run. This can by done by message id, or
317 316 all enqueued messages can be aborted if None is specified.
318 317
319 318 Message type: ``abort_request``::
320 319
321 320 content = {
322 321 'msg_ids' : ['1234-...', '...'] # list of msg_ids or None
323 322 }
324 323
325 324 Message type: ``abort_reply``::
326 325
327 326 content = {
328 327 'status' : 'ok' # 'ok' or 'error'
329 328 # other error info here, as in other messages
330 329 }
331 330
332 331 The last action a client may want to do is shutdown the kernel. If a kernel receives a
333 332 shutdown request, then it aborts all queued messages, replies to the request, and exits.
334 333
335 334 Message type: ``shutdown_request``::
336 335
337 336 content = {}
338 337
339 338 Message type: ``shutdown_reply``::
340 339
341 340 content = {
342 341 'status' : 'ok' # 'ok' or 'error'
343 342 # other error info here, as in other messages
344 343 }
345 344
346 345
347 346 Implementation
348 347 --------------
349 348
350 349 There are a few differences in implementation between the `StreamSession` object used in
351 350 the newparallel branch and the `Session` object, the main one being that messages are
352 351 sent in parts, rather than as a single serialized object. `StreamSession` objects also
353 352 take pack/unpack functions, which are to be used when serializing/deserializing objects.
354 353 These can be any functions that translate to/from formats that ZMQ sockets can send
355 354 (buffers,bytes, etc.).
356 355
357 356 Split Sends
358 357 ***********
359 358
360 359 Previously, messages were bundled as a single json object and one call to
361 360 :func:`socket.send_json`. Since the hub inspects all messages, and doesn't need to
362 361 see the content of the messages, which can be large, messages are now serialized and sent in
363 pieces. All messages are sent in at least 3 parts: the header, the parent header, and the
364 content. This allows the controller to unpack and inspect the (always small) header,
362 pieces. All messages are sent in at least 4 parts: the header, the parent header, the metadata and the content.
363 This allows the controller to unpack and inspect the (always small) header,
365 364 without spending time unpacking the content unless the message is bound for the
366 365 controller. Buffers are added on to the end of the message, and can be any objects that
367 366 present the buffer interface.
368 367
General Comments 0
You need to be logged in to leave comments. Login now