1# encoding: utf-8 2 3"""Classes and functions for kernel related errors and exceptions. 4 5Inheritance diagram: 6 7.. inheritance-diagram:: ipyparallel.error 8 :parts: 3 9 10Authors: 11 12* Brian Granger 13* Min RK 14""" 15from __future__ import print_function 16 17import sys 18import traceback 19 20from ipython_genutils.py3compat import unicode_type 21 22__docformat__ = "restructuredtext en" 23 24# Tell nose to skip this module 25__test__ = {} 26 27#------------------------------------------------------------------------------- 28# Copyright (C) 2008-2011 The IPython Development Team 29# 30# Distributed under the terms of the BSD License. The full license is in 31# the file COPYING, distributed as part of this software. 32#------------------------------------------------------------------------------- 33 34#------------------------------------------------------------------------------- 35# Error classes 36#------------------------------------------------------------------------------- 37class IPythonError(Exception): 38 """Base exception that all of our exceptions inherit from. 39 40 This can be raised by code that doesn't have any more specific 41 information.""" 42 43 pass 44 45class KernelError(IPythonError): 46 pass 47 48class EngineError(KernelError): 49 pass 50 51class NoEnginesRegistered(KernelError): 52 pass 53 54class TaskAborted(KernelError): 55 pass 56 57class TaskTimeout(KernelError): 58 pass 59 60class TimeoutError(KernelError): 61 pass 62 63class UnmetDependency(KernelError): 64 pass 65 66class ImpossibleDependency(UnmetDependency): 67 pass 68 69class DependencyTimeout(ImpossibleDependency): 70 pass 71 72class InvalidDependency(ImpossibleDependency): 73 pass 74 75class RemoteError(KernelError): 76 """Error raised elsewhere""" 77 ename=None 78 evalue=None 79 traceback=None 80 engine_info=None 81 82 def __init__(self, ename, evalue, traceback, engine_info=None): 83 self.ename=ename 84 self.evalue=evalue 85 self.traceback=traceback 86 self.engine_info=engine_info or {} 87 self.args=(ename, evalue) 88 89 def __repr__(self): 90 engineid = self.engine_info.get('engine_id', ' ') 91 return "<Remote[%s]:%s(%s)>"%(engineid, self.ename, self.evalue) 92 93 def __str__(self): 94 return "%s(%s)" % (self.ename, self.evalue) 95 96 def render_traceback(self): 97 """render traceback to a list of lines""" 98 return (self.traceback or "No traceback available").splitlines() 99 100 def _render_traceback_(self): 101 """Special method for custom tracebacks within IPython. 102 103 This will be called by IPython instead of displaying the local traceback. 104 105 It should return a traceback rendered as a list of lines. 106 """ 107 return self.render_traceback() 108 109 def print_traceback(self, excid=None): 110 """print my traceback""" 111 print('\n'.join(self.render_traceback())) 112 113 114 115 116class TaskRejectError(KernelError): 117 """Exception to raise when a task should be rejected by an engine. 118 119 This exception can be used to allow a task running on an engine to test 120 if the engine (or the user's namespace on the engine) has the needed 121 task dependencies. If not, the task should raise this exception. For 122 the task to be retried on another engine, the task should be created 123 with the `retries` argument > 1. 124 125 The advantage of this approach over our older properties system is that 126 tasks have full access to the user's namespace on the engines and the 127 properties don't have to be managed or tested by the controller. 128 """ 129 130 131class CompositeError(RemoteError): 132 """Error for representing possibly multiple errors on engines""" 133 tb_limit = 4 # limit on how many tracebacks to draw 134 135 def __init__(self, message, elist): 136 Exception.__init__(self, *(message, elist)) 137 # Don't use pack_exception because it will conflict with the .message 138 # attribute that is being deprecated in 2.6 and beyond. 139 self.msg = message 140 self.elist = elist 141 self.args = [ e[0] for e in elist ] 142 143 def _get_engine_str(self, ei): 144 if not ei: 145 return '[Engine Exception]' 146 else: 147 return '[%s:%s]: ' % (ei['engine_id'], ei['method']) 148 149 def _get_traceback(self, ev): 150 try: 151 tb = ev._ipython_traceback_text 152 except AttributeError: 153 return 'No traceback available' 154 else: 155 return tb 156 157 def __str__(self): 158 s = str(self.msg) 159 for en, ev, etb, ei in self.elist[:self.tb_limit]: 160 engine_str = self._get_engine_str(ei) 161 s = s + '\n' + engine_str + en + ': ' + str(ev) 162 if len(self.elist) > self.tb_limit: 163 s = s + '\n.... %i more exceptions ...' % (len(self.elist) - self.tb_limit) 164 return s 165 166 def __repr__(self): 167 return "CompositeError(%i)" % len(self.elist) 168 169 def render_traceback(self, excid=None): 170 """render one or all of my tracebacks to a list of lines""" 171 lines = [] 172 if excid is None: 173 for (en,ev,etb,ei) in self.elist[:self.tb_limit]: 174 lines.append(self._get_engine_str(ei)) 175 lines.extend((etb or 'No traceback available').splitlines()) 176 lines.append('') 177 if len(self.elist) > self.tb_limit: 178 lines.append( 179 '... %i more exceptions ...' % (len(self.elist) - self.tb_limit) 180 ) 181 else: 182 try: 183 en,ev,etb,ei = self.elist[excid] 184 except: 185 raise IndexError("an exception with index %i does not exist"%excid) 186 else: 187 lines.append(self._get_engine_str(ei)) 188 lines.extend((etb or 'No traceback available').splitlines()) 189 190 return lines 191 192 def print_traceback(self, excid=None): 193 print('\n'.join(self.render_traceback(excid))) 194 195 def raise_exception(self, excid=0): 196 try: 197 en,ev,etb,ei = self.elist[excid] 198 except: 199 raise IndexError("an exception with index %i does not exist"%excid) 200 else: 201 raise RemoteError(en, ev, etb, ei) 202 203 204def collect_exceptions(rdict_or_list, method='unspecified'): 205 """check a result dict for errors, and raise CompositeError if any exist. 206 Passthrough otherwise.""" 207 elist = [] 208 if isinstance(rdict_or_list, dict): 209 rlist = rdict_or_list.values() 210 else: 211 rlist = rdict_or_list 212 for r in rlist: 213 if isinstance(r, RemoteError): 214 en, ev, etb, ei = r.ename, r.evalue, r.traceback, r.engine_info 215 # Sometimes we could have CompositeError in our list. Just take 216 # the errors out of them and put them in our new list. This 217 # has the effect of flattening lists of CompositeErrors into one 218 # CompositeError 219 if en=='CompositeError': 220 for e in ev.elist: 221 elist.append(e) 222 else: 223 elist.append((en, ev, etb, ei)) 224 if len(elist)==0: 225 return rdict_or_list 226 else: 227 msg = "one or more exceptions from call to method: %s" % (method) 228 # This silliness is needed so the debugger has access to the exception 229 # instance (e in this case) 230 try: 231 raise CompositeError(msg, elist) 232 except CompositeError as e: 233 raise e 234 235def wrap_exception(engine_info={}): 236 etype, evalue, tb = sys.exc_info() 237 stb = traceback.format_exception(etype, evalue, tb) 238 exc_content = { 239 'status' : 'error', 240 'traceback' : stb, 241 'ename' : unicode_type(etype.__name__), 242 'evalue' : unicode_type(evalue), 243 'engine_info' : engine_info 244 } 245 return exc_content 246 247def unwrap_exception(content): 248 err = RemoteError(content['ename'], content['evalue'], 249 ''.join(content['traceback']), 250 content.get('engine_info', {})) 251 return err 252 253