error.py
252 lines
| 7.7 KiB
| text/x-python
|
PythonLexer
MinRK
|
r3583 | # encoding: utf-8 | ||
MinRK
|
r4018 | """Classes and functions for kernel related errors and exceptions. | ||
Thomas Kluyver
|
r8795 | Inheritance diagram: | ||
.. inheritance-diagram:: IPython.parallel.error | ||||
:parts: 3 | ||||
MinRK
|
r4018 | Authors: | ||
* Brian Granger | ||||
* Min RK | ||||
""" | ||||
MinRK
|
r3583 | from __future__ import print_function | ||
MinRK
|
r3644 | import sys | ||
import traceback | ||||
Thomas Kluyver
|
r13353 | from IPython.utils.py3compat import unicode_type | ||
MinRK
|
r3583 | __docformat__ = "restructuredtext en" | ||
# Tell nose to skip this module | ||||
__test__ = {} | ||||
#------------------------------------------------------------------------------- | ||||
MinRK
|
r4018 | # Copyright (C) 2008-2011 The IPython Development Team | ||
MinRK
|
r3583 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Error classes | ||||
#------------------------------------------------------------------------------- | ||||
class IPythonError(Exception): | ||||
"""Base exception that all of our exceptions inherit from. | ||||
This can be raised by code that doesn't have any more specific | ||||
information.""" | ||||
pass | ||||
class KernelError(IPythonError): | ||||
pass | ||||
MinRK
|
r12285 | class EngineError(KernelError): | ||
pass | ||||
MinRK
|
r3583 | class NoEnginesRegistered(KernelError): | ||
pass | ||||
class TaskAborted(KernelError): | ||||
pass | ||||
MinRK
|
r11417 | class TaskTimeout(KernelError): | ||
pass | ||||
MinRK
|
r3583 | |||
MinRK
|
r3589 | class TimeoutError(KernelError): | ||
pass | ||||
MinRK
|
r3607 | class UnmetDependency(KernelError): | ||
pass | ||||
class ImpossibleDependency(UnmetDependency): | ||||
pass | ||||
MinRK
|
r3624 | class DependencyTimeout(ImpossibleDependency): | ||
pass | ||||
class InvalidDependency(ImpossibleDependency): | ||||
MinRK
|
r3611 | pass | ||
MinRK
|
r3583 | class RemoteError(KernelError): | ||
"""Error raised elsewhere""" | ||||
ename=None | ||||
evalue=None | ||||
traceback=None | ||||
engine_info=None | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | def __init__(self, ename, evalue, traceback, engine_info=None): | ||
self.ename=ename | ||||
self.evalue=evalue | ||||
self.traceback=traceback | ||||
self.engine_info=engine_info or {} | ||||
self.args=(ename, evalue) | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | def __repr__(self): | ||
MinRK
|
r3641 | engineid = self.engine_info.get('engine_id', ' ') | ||
MinRK
|
r3583 | return "<Remote[%s]:%s(%s)>"%(engineid, self.ename, self.evalue) | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | def __str__(self): | ||
MinRK
|
r7468 | return "%s(%s)" % (self.ename, self.evalue) | ||
Thomas Kluyver
|
r8084 | def render_traceback(self): | ||
MinRK
|
r7468 | """render traceback to a list of lines""" | ||
return (self.traceback or "No traceback available").splitlines() | ||||
Thomas Kluyver
|
r8082 | |||
MinRK
|
r8227 | def _render_traceback_(self): | ||
MinRK
|
r8264 | """Special method for custom tracebacks within IPython. | ||
This will be called by IPython instead of displaying the local traceback. | ||||
It should return a traceback rendered as a list of lines. | ||||
""" | ||||
MinRK
|
r8227 | return self.render_traceback() | ||
MinRK
|
r7468 | def print_traceback(self, excid=None): | ||
"""print my traceback""" | ||||
print('\n'.join(self.render_traceback())) | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | |||
class TaskRejectError(KernelError): | ||||
"""Exception to raise when a task should be rejected by an engine. | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | This exception can be used to allow a task running on an engine to test | ||
if the engine (or the user's namespace on the engine) has the needed | ||||
task dependencies. If not, the task should raise this exception. For | ||||
the task to be retried on another engine, the task should be created | ||||
with the `retries` argument > 1. | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | The advantage of this approach over our older properties system is that | ||
tasks have full access to the user's namespace on the engines and the | ||||
properties don't have to be managed or tested by the controller. | ||||
""" | ||||
MinRK
|
r3638 | class CompositeError(RemoteError): | ||
MinRK
|
r3583 | """Error for representing possibly multiple errors on engines""" | ||
MinRK
|
r9411 | tb_limit = 4 # limit on how many tracebacks to draw | ||
MinRK
|
r3583 | def __init__(self, message, elist): | ||
Exception.__init__(self, *(message, elist)) | ||||
# Don't use pack_exception because it will conflict with the .message | ||||
# attribute that is being deprecated in 2.6 and beyond. | ||||
self.msg = message | ||||
self.elist = elist | ||||
self.args = [ e[0] for e in elist ] | ||||
def _get_engine_str(self, ei): | ||||
if not ei: | ||||
return '[Engine Exception]' | ||||
else: | ||||
MinRK
|
r3642 | return '[%s:%s]: ' % (ei['engine_id'], ei['method']) | ||
MinRK
|
r3583 | |||
def _get_traceback(self, ev): | ||||
try: | ||||
tb = ev._ipython_traceback_text | ||||
except AttributeError: | ||||
return 'No traceback available' | ||||
else: | ||||
return tb | ||||
def __str__(self): | ||||
s = str(self.msg) | ||||
MinRK
|
r9411 | for en, ev, etb, ei in self.elist[:self.tb_limit]: | ||
MinRK
|
r3583 | engine_str = self._get_engine_str(ei) | ||
s = s + '\n' + engine_str + en + ': ' + str(ev) | ||||
MinRK
|
r9411 | if len(self.elist) > self.tb_limit: | ||
s = s + '\n.... %i more exceptions ...' % (len(self.elist) - self.tb_limit) | ||||
MinRK
|
r3583 | return s | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | def __repr__(self): | ||
MinRK
|
r9411 | return "CompositeError(%i)" % len(self.elist) | ||
MinRK
|
r7468 | |||
def render_traceback(self, excid=None): | ||||
"""render one or all of my tracebacks to a list of lines""" | ||||
lines = [] | ||||
MinRK
|
r3583 | if excid is None: | ||
MinRK
|
r9411 | for (en,ev,etb,ei) in self.elist[:self.tb_limit]: | ||
MinRK
|
r7468 | lines.append(self._get_engine_str(ei)) | ||
lines.extend((etb or 'No traceback available').splitlines()) | ||||
lines.append('') | ||||
MinRK
|
r9411 | if len(self.elist) > self.tb_limit: | ||
lines.append( | ||||
'... %i more exceptions ...' % (len(self.elist) - self.tb_limit) | ||||
) | ||||
MinRK
|
r3583 | else: | ||
try: | ||||
en,ev,etb,ei = self.elist[excid] | ||||
except: | ||||
raise IndexError("an exception with index %i does not exist"%excid) | ||||
else: | ||||
MinRK
|
r7468 | lines.append(self._get_engine_str(ei)) | ||
lines.extend((etb or 'No traceback available').splitlines()) | ||||
return lines | ||||
def print_traceback(self, excid=None): | ||||
print('\n'.join(self.render_traceback(excid))) | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3583 | def raise_exception(self, excid=0): | ||
try: | ||||
en,ev,etb,ei = self.elist[excid] | ||||
except: | ||||
raise IndexError("an exception with index %i does not exist"%excid) | ||||
else: | ||||
MinRK
|
r3638 | raise RemoteError(en, ev, etb, ei) | ||
MinRK
|
r3583 | |||
MinRK
|
r3601 | def collect_exceptions(rdict_or_list, method='unspecified'): | ||
MinRK
|
r3583 | """check a result dict for errors, and raise CompositeError if any exist. | ||
Passthrough otherwise.""" | ||||
elist = [] | ||||
MinRK
|
r3587 | if isinstance(rdict_or_list, dict): | ||
rlist = rdict_or_list.values() | ||||
else: | ||||
rlist = rdict_or_list | ||||
for r in rlist: | ||||
MinRK
|
r3583 | if isinstance(r, RemoteError): | ||
en, ev, etb, ei = r.ename, r.evalue, r.traceback, r.engine_info | ||||
# Sometimes we could have CompositeError in our list. Just take | ||||
Bernardo B. Marques
|
r4872 | # the errors out of them and put them in our new list. This | ||
MinRK
|
r3583 | # has the effect of flattening lists of CompositeErrors into one | ||
# CompositeError | ||||
if en=='CompositeError': | ||||
for e in ev.elist: | ||||
elist.append(e) | ||||
else: | ||||
elist.append((en, ev, etb, ei)) | ||||
if len(elist)==0: | ||||
MinRK
|
r3587 | return rdict_or_list | ||
MinRK
|
r3583 | else: | ||
msg = "one or more exceptions from call to method: %s" % (method) | ||||
# This silliness is needed so the debugger has access to the exception | ||||
# instance (e in this case) | ||||
try: | ||||
raise CompositeError(msg, elist) | ||||
MinRK
|
r3638 | except CompositeError as e: | ||
MinRK
|
r3583 | raise e | ||
MinRK
|
r3644 | def wrap_exception(engine_info={}): | ||
etype, evalue, tb = sys.exc_info() | ||||
stb = traceback.format_exception(etype, evalue, tb) | ||||
exc_content = { | ||||
'status' : 'error', | ||||
'traceback' : stb, | ||||
Thomas Kluyver
|
r13353 | 'ename' : unicode_type(etype.__name__), | ||
'evalue' : unicode_type(evalue), | ||||
MinRK
|
r3644 | 'engine_info' : engine_info | ||
} | ||||
return exc_content | ||||
def unwrap_exception(content): | ||||
Bernardo B. Marques
|
r4872 | err = RemoteError(content['ename'], content['evalue'], | ||
MinRK
|
r3644 | ''.join(content['traceback']), | ||
content.get('engine_info', {})) | ||||
return err | ||||