error.py
341 lines
| 8.6 KiB
| text/x-python
|
PythonLexer
|
r3583 | # encoding: utf-8 | ||
|
r4018 | """Classes and functions for kernel related errors and exceptions. | ||
Authors: | ||||
* Brian Granger | ||||
* Min RK | ||||
""" | ||||
|
r3583 | from __future__ import print_function | ||
|
r3644 | import sys | ||
import traceback | ||||
|
r3583 | __docformat__ = "restructuredtext en" | ||
# Tell nose to skip this module | ||||
__test__ = {} | ||||
#------------------------------------------------------------------------------- | ||||
|
r4018 | # Copyright (C) 2008-2011 The IPython Development Team | ||
|
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 | ||||
# Exceptions associated with the controller objects | ||||
class ControllerError(IPythonError): pass | ||||
class ControllerCreationError(ControllerError): pass | ||||
# Exceptions associated with the Engines | ||||
class EngineError(IPythonError): pass | ||||
class EngineCreationError(EngineError): pass | ||||
class KernelError(IPythonError): | ||||
pass | ||||
class NotDefined(KernelError): | ||||
def __init__(self, name): | ||||
self.name = name | ||||
self.args = (name,) | ||||
def __repr__(self): | ||||
return '<NotDefined: %s>' % self.name | ||||
|
r4872 | |||
|
r3583 | __str__ = __repr__ | ||
class QueueCleared(KernelError): | ||||
pass | ||||
class IdInUse(KernelError): | ||||
pass | ||||
class ProtocolError(KernelError): | ||||
pass | ||||
class ConnectionError(KernelError): | ||||
pass | ||||
class InvalidEngineID(KernelError): | ||||
pass | ||||
class NoEnginesRegistered(KernelError): | ||||
pass | ||||
class InvalidClientID(KernelError): | ||||
pass | ||||
class InvalidDeferredID(KernelError): | ||||
pass | ||||
class SerializationError(KernelError): | ||||
pass | ||||
class MessageSizeError(KernelError): | ||||
pass | ||||
class PBMessageSizeError(MessageSizeError): | ||||
pass | ||||
class ResultNotCompleted(KernelError): | ||||
pass | ||||
class ResultAlreadyRetrieved(KernelError): | ||||
pass | ||||
class ClientError(KernelError): | ||||
pass | ||||
class TaskAborted(KernelError): | ||||
pass | ||||
class TaskTimeout(KernelError): | ||||
pass | ||||
class NotAPendingResult(KernelError): | ||||
pass | ||||
class UnpickleableException(KernelError): | ||||
pass | ||||
class AbortedPendingDeferredError(KernelError): | ||||
pass | ||||
class InvalidProperty(KernelError): | ||||
pass | ||||
class MissingBlockArgument(KernelError): | ||||
pass | ||||
class StopLocalExecution(KernelError): | ||||
pass | ||||
class SecurityError(KernelError): | ||||
pass | ||||
class FileTimeoutError(KernelError): | ||||
pass | ||||
|
r3589 | class TimeoutError(KernelError): | ||
pass | ||||
|
r3607 | class UnmetDependency(KernelError): | ||
pass | ||||
class ImpossibleDependency(UnmetDependency): | ||||
pass | ||||
|
r3624 | class DependencyTimeout(ImpossibleDependency): | ||
pass | ||||
class InvalidDependency(ImpossibleDependency): | ||||
|
r3611 | pass | ||
|
r3583 | class RemoteError(KernelError): | ||
"""Error raised elsewhere""" | ||||
ename=None | ||||
evalue=None | ||||
traceback=None | ||||
engine_info=None | ||||
|
r4872 | |||
|
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) | ||||
|
r4872 | |||
|
r3583 | def __repr__(self): | ||
|
r3641 | engineid = self.engine_info.get('engine_id', ' ') | ||
|
r3583 | return "<Remote[%s]:%s(%s)>"%(engineid, self.ename, self.evalue) | ||
|
r4872 | |||
|
r3583 | def __str__(self): | ||
|
r7468 | return "%s(%s)" % (self.ename, self.evalue) | ||
|
r8084 | def render_traceback(self): | ||
|
r7468 | """render traceback to a list of lines""" | ||
return (self.traceback or "No traceback available").splitlines() | ||||
|
r8082 | |||
|
r8227 | def _render_traceback_(self): | ||
|
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. | ||||
""" | ||||
|
r8227 | return self.render_traceback() | ||
|
r7468 | def print_traceback(self, excid=None): | ||
"""print my traceback""" | ||||
print('\n'.join(self.render_traceback())) | ||||
|
r4872 | |||
|
r3583 | |||
class TaskRejectError(KernelError): | ||||
"""Exception to raise when a task should be rejected by an engine. | ||||
|
r4872 | |||
|
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. | ||||
|
r4872 | |||
|
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. | ||||
""" | ||||
|
r3638 | class CompositeError(RemoteError): | ||
|
r3583 | """Error for representing possibly multiple errors on engines""" | ||
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: | ||||
|
r3642 | return '[%s:%s]: ' % (ei['engine_id'], ei['method']) | ||
|
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) | ||||
for en, ev, etb, ei in self.elist: | ||||
engine_str = self._get_engine_str(ei) | ||||
s = s + '\n' + engine_str + en + ': ' + str(ev) | ||||
return s | ||||
|
r4872 | |||
|
r3583 | def __repr__(self): | ||
return "CompositeError(%i)"%len(self.elist) | ||||
|
r7468 | |||
def render_traceback(self, excid=None): | ||||
"""render one or all of my tracebacks to a list of lines""" | ||||
lines = [] | ||||
|
r3583 | if excid is None: | ||
for (en,ev,etb,ei) in self.elist: | ||||
|
r7468 | lines.append(self._get_engine_str(ei)) | ||
lines.extend((etb or 'No traceback available').splitlines()) | ||||
lines.append('') | ||||
|
r3583 | else: | ||
try: | ||||
en,ev,etb,ei = self.elist[excid] | ||||
except: | ||||
raise IndexError("an exception with index %i does not exist"%excid) | ||||
else: | ||||
|
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))) | ||||
|
r4872 | |||
|
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: | ||||
|
r3638 | raise RemoteError(en, ev, etb, ei) | ||
|
r3583 | |||
|
r3601 | def collect_exceptions(rdict_or_list, method='unspecified'): | ||
|
r3583 | """check a result dict for errors, and raise CompositeError if any exist. | ||
Passthrough otherwise.""" | ||||
elist = [] | ||||
|
r3587 | if isinstance(rdict_or_list, dict): | ||
rlist = rdict_or_list.values() | ||||
else: | ||||
rlist = rdict_or_list | ||||
for r in rlist: | ||||
|
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 | ||||
|
r4872 | # the errors out of them and put them in our new list. This | ||
|
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: | ||||
|
r3587 | return rdict_or_list | ||
|
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) | ||||
|
r3638 | except CompositeError as e: | ||
|
r3583 | raise e | ||
|
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, | ||||
'ename' : unicode(etype.__name__), | ||||
'evalue' : unicode(evalue), | ||||
'engine_info' : engine_info | ||||
} | ||||
return exc_content | ||||
def unwrap_exception(content): | ||||
|
r4872 | err = RemoteError(content['ename'], content['evalue'], | ||
|
r3644 | ''.join(content['traceback']), | ||
content.get('engine_info', {})) | ||||
return err | ||||