#!/usr/bin/env python
import traceback
import re
import sys
class CausedException(Exception):
def __init__(self, *args, **kwargs):
if len(args) == 1 and not kwargs and isinstance(args[0], Exception):
# we shall just wrap a non-caused exception
self.stack = (
traceback.format_stack()[:-2] +
traceback.format_tb(sys.exc_info()[2]))
# ^^^ let's hope the information is still there; caller must take
# care of this.
self.wrapped = args[0]
self.cause = ()
super(CausedException, self).__init__(repr(args[0]))
# ^^^ to display what it is wrapping, in case it gets printed or similar
return
self.wrapped = None
self.stack = traceback.format_stack()[:-1] # cut off current frame
try:
cause = kwargs['cause']
del kwargs['cause']
except:
cause = ()
self.cause = cause if isinstance(cause, tuple) else (cause,)
super(CausedException, self).__init__(*args, **kwargs)
def causeTree(self, indentation=' ', alreadyMentionedTree=[]):
yield "Traceback (most recent call last):\n"
ellipsed = 0
for i, line in enumerate(self.stack):
if (ellipsed is not False and i < len(alreadyMentionedTree) and
line == alreadyMentionedTree[i]):
ellipsed += 1
else:
if ellipsed:
yield " ... (%d frame%s repeated)\n" % (
ellipsed, "" if ellipsed == 1 else "s")
ellipsed = False # marker for "given out"
yield line
exc = self if self.wrapped is None else self.wrapped
for line in traceback.format_exception_only(exc.__class__, exc):
yield line
if self.cause:
yield ("caused by: %d exception%s\n" %
(len(self.cause), "" if len(self.cause) == 1 else "s"))
for causePart in self.cause:
for line in causePart.causeTree(indentation, self.stack):
yield re.sub(r'([^\n]*\n)', indentation + r'\1', line)
def write(self, stream=None, indentation=' '):
stream = sys.stderr if stream is None else stream
for line in self.causeTree(indentation):
stream.write(line)
if __name__ == '__main__':
def deeplib(i):
if i == 3:
1 / 0 # raise non-caused exception
else:
raise CausedException("deeplib error %d" % i)
def library(i):
if i == 0:
return "no problem"
elif i == 1:
raise CausedException("lib error one %d" % i)
elif i == 2:
try:
deeplib(i)
except CausedException, e:
raise CausedException("lib error two %d" % i, cause=e)
except Exception, e: # non-caused exception?
raise CausedException("lib error two %d" % i,
cause=CausedException(e)) # wrap non-caused exception
elif i == 3:
try:
deeplib(i)
except CausedException, e:
raise CausedException("lib error three %d" % i, cause=e)
except Exception, e: # non-caused exception?
wrappedException = CausedException(e) # wrap it for fitting in
try:
deeplib(i-1) # try again
except CausedException, e:
raise CausedException("lib error three %d" % i,
cause=(wrappedException, CausedException(e)))
else:
raise CausedException("lib error unexpected %d" % i)
def application():
e0 = e1 = e2 = e3 = None
try: library(0)
except CausedException, e: e0 = e
try: library(1)
except CausedException, e: e1 = e
try: library(2)
except CausedException, e: e2 = e
try: library(3)
except CausedException, e: e3 = e
if e0 or e1 or e2 or e3:
raise CausedException("application error",
cause=tuple(e for e in (e0, e1, e2, e3) if e is not None))
try:
application()
except CausedException, e:
e.write()
print >>sys.stderr, "NOW WITH MORE OBVIOUS INDENTATION"
e.write(indentation='|| ')
print >>sys.stderr, "NOW THE DEFAULT HANDLER"
application()
Diff to Previous Revision
--- revision 1 2012-09-04 15:57:51
+++ revision 2 2013-02-04 15:15:22
@@ -15,6 +15,8 @@
# care of this.
self.wrapped = args[0]
self.cause = ()
+ super(CausedException, self).__init__(repr(args[0]))
+ # ^^^ to display what it is wrapping, in case it gets printed or similar
return
self.wrapped = None
self.stack = traceback.format_stack()[:-1] # cut off current frame