1
# -*- test-case-name: twisted.test.test_failure -*-
2
# See also test suite twisted.test.test_pbfailure
4
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
5
# See LICENSE for details.
9
Asynchronous-friendly error mechanism.
19
from cStringIO import StringIO
21
from twisted.python import reflect
26
class DefaultException(Exception):
29
def format_frames(frames, write, detail="default"):
30
"""Format and write frames.
32
@param frames: is a list of frames as used by Failure.frames, with
33
each frame being a list of
34
(funcName, fileName, lineNumber, locals.items(), globals.items())
36
@param write: this will be called with formatted strings.
38
@param detail: Three detail levels are available:
39
default, brief, and verbose.
42
if detail not in ('default', 'brief', 'verbose'):
43
raise ValueError, "Detail must be default, brief, or verbose. (not %r)" % (detail,)
46
for method, filename, lineno, localVars, globalVars in frames:
47
w('%s:%s:%s\n' % (filename, lineno, method))
48
elif detail == "default":
49
for method, filename, lineno, localVars, globalVars in frames:
50
w( ' File "%s", line %s, in %s\n' % (filename, lineno, method))
51
w( ' %s\n' % linecache.getline(filename, lineno).strip())
52
elif detail == "verbose":
53
for method, filename, lineno, localVars, globalVars in frames:
54
w("%s:%d: %s(...)\n" % (filename, lineno, method))
56
# Note: the repr(val) was (self.pickled and val) or repr(val)))
57
for name, val in localVars:
58
w(" %s : %s\n" % (name, repr(val)))
60
for name, val in globalVars:
61
w(" %s : %s\n" % (name, repr(val)))
63
# slyphon: i have a need to check for this value in trial
64
# so I made it a module-level constant
65
EXCEPTION_CAUGHT_HERE = "--- <exception caught here> ---"
69
class NoCurrentExceptionError(Exception):
71
Raised when trying to create a Failure from the current interpreter
72
exception state and there is no current exception state.
76
class _Traceback(object):
78
Fake traceback object which can be passed to functions in the standard
79
library L{traceback} module.
82
def __init__(self, frames):
84
Construct a fake traceback object using a list of frames. Note that
85
although frames generally include locals and globals, this information
86
is not kept by this object, since locals and globals are not used in
89
@param frames: [(methodname, filename, lineno, locals, globals), ...]
91
assert len(frames) > 0, "Must pass some frames"
92
head, frames = frames[0], frames[1:]
93
name, filename, lineno, localz, globalz = head
94
self.tb_frame = _Frame(name, filename)
95
self.tb_lineno = lineno
99
self.tb_next = _Traceback(frames)
102
class _Frame(object):
104
A fake frame object, used by L{_Traceback}.
107
def __init__(self, name, filename):
108
self.f_code = _Code(name, filename)
114
A fake code object, used by L{_Traceback} via L{_Frame}.
116
def __init__(self, name, filename):
118
self.co_filename = filename
123
A basic abstraction for an error that has occurred.
125
This is necessary because Python's built-in error mechanisms are
126
inconvenient for asynchronous communication.
128
@ivar value: The exception instance responsible for this failure.
129
@ivar type: The exception's class.
135
# The opcode of "yield" in Python bytecode. We need this in _findFailure in
136
# order to identify whether an exception was thrown by a
137
# throwExceptionIntoGenerator.
138
_yieldOpcode = chr(opcode.opmap["YIELD_VALUE"])
140
def __init__(self, exc_value=None, exc_type=None, exc_tb=None):
142
Initialize me with an explanation of the error.
144
By default, this will use the current C{exception}
145
(L{sys.exc_info}()). However, if you want to specify a
146
particular kind of failure, you can pass an exception as an
149
If no C{exc_value} is passed, then an "original" C{Failure} will
150
be searched for. If the current exception handler that this
151
C{Failure} is being constructed in is handling an exception
152
raised by L{raiseException}, then this C{Failure} will act like
153
the original C{Failure}.
155
For C{exc_tb} only L{traceback} instances or C{None} are allowed.
156
If C{None} is supplied for C{exc_value}, the value of C{exc_tb} is
157
ignored, otherwise if C{exc_tb} is C{None}, it will be found from
158
execution context (ie, L{sys.exc_info}).
163
self.type = self.value = tb = None
165
#strings Exceptions/Failures are bad, mmkay?
166
if isinstance(exc_value, (str, unicode)) and exc_type is None:
169
"Don't pass strings (like %r) to failure.Failure (replacing with a DefaultException)." %
170
exc_value, DeprecationWarning, stacklevel=2)
171
exc_value = DefaultException(exc_value)
175
if exc_value is None:
176
exc_value = self._findFailure()
178
if exc_value is None:
179
self.type, self.value, tb = sys.exc_info()
180
if self.type is None:
181
raise NoCurrentExceptionError()
183
elif exc_type is None:
184
if isinstance(exc_value, Exception):
185
self.type = exc_value.__class__
186
else: #allow arbitrary objects.
187
self.type = type(exc_value)
188
self.value = exc_value
191
self.value = exc_value
192
if isinstance(self.value, Failure):
193
self.__dict__ = self.value.__dict__
199
# log.msg("Erf, %r created with no traceback, %s %s." % (
200
# repr(self), repr(exc_value), repr(exc_type)))
201
# for s in traceback.format_stack():
204
frames = self.frames = []
205
stack = self.stack = []
207
# added 2003-06-23 by Chris Armstrong. Yes, I actually have a
208
# use case where I need this traceback object, and I've made
209
# sure that it'll be cleaned up.
214
elif not isinstance(self.value, Failure):
215
# we don't do frame introspection since it's expensive,
216
# and if we were passed a plain exception with no
217
# traceback, it's not useful anyway
218
f = stackOffset = None
220
while stackOffset and f:
221
# This excludes this Failure.__init__ frame from the
222
# stack, leaving it to start with our caller instead.
226
# Keeps the *full* stack. Formerly in spread.pb.print_excFullStack:
228
# The need for this function arises from the fact that several
229
# PB classes have the peculiar habit of discarding exceptions
230
# with bareword "except:"s. This premature exception
231
# catching means tracebacks generated here don't tend to show
232
# what called upon the PB object.
235
localz = f.f_locals.copy()
236
if f.f_locals is f.f_globals:
239
globalz = f.f_globals.copy()
240
for d in globalz, localz:
241
if d.has_key("__builtins__"):
242
del d["__builtins__"]
245
f.f_code.co_filename,
252
while tb is not None:
254
localz = f.f_locals.copy()
255
if f.f_locals is f.f_globals:
258
globalz = f.f_globals.copy()
259
for d in globalz, localz:
260
if d.has_key("__builtins__"):
261
del d["__builtins__"]
265
f.f_code.co_filename,
271
if inspect.isclass(self.type) and issubclass(self.type, Exception):
272
parentCs = reflect.allYourBase(self.type)
273
self.parents = map(reflect.qual, parentCs)
274
self.parents.append(reflect.qual(self.type))
276
self.parents = [self.type]
278
def trap(self, *errorTypes):
279
"""Trap this failure if its type is in a predetermined list.
281
This allows you to trap a Failure in an error callback. It will be
282
automatically re-raised if it is not a type that you expect.
284
The reason for having this particular API is because it's very useful
285
in Deferred errback chains::
287
def _ebFoo(self, failure):
288
r = failure.trap(Spam, Eggs)
289
print 'The Failure is due to either Spam or Eggs!'
295
If the failure is not a Spam or an Eggs, then the Failure
296
will be 'passed on' to the next errback.
298
@type errorTypes: L{Exception}
300
error = self.check(*errorTypes)
305
def check(self, *errorTypes):
306
"""Check if this failure's type is in a predetermined list.
308
@type errorTypes: list of L{Exception} classes or
309
fully-qualified class names.
310
@returns: the matching L{Exception} type, or None if no match.
312
for error in errorTypes:
314
if inspect.isclass(error) and issubclass(error, Exception):
315
err = reflect.qual(error)
316
if err in self.parents:
321
def raiseException(self):
323
raise the original exception, preserving traceback
324
information if available.
326
raise self.type, self.value, self.tb
329
def throwExceptionIntoGenerator(self, g):
331
Throw the original exception into the given generator,
332
preserving traceback information if available.
334
@return: The next value yielded from the generator.
335
@raise StopIteration: If there are no more values in the generator.
336
@raise anything else: Anything that the generator raises.
338
return g.throw(self.type, self.value, self.tb)
341
def _findFailure(cls):
343
Find the failure that represents the exception currently in context.
345
tb = sys.exc_info()[-1]
351
while lastTb.tb_next:
352
secondLastTb = lastTb
353
lastTb = lastTb.tb_next
355
lastFrame = lastTb.tb_frame
357
# NOTE: f_locals.get('self') is used rather than
358
# f_locals['self'] because psyco frames do not contain
359
# anything in their locals() dicts. psyco makes debugging
360
# difficult anyhow, so losing the Failure objects (and thus
361
# the tracebacks) here when it is used is not that big a deal.
363
# handle raiseException-originated exceptions
364
if lastFrame.f_code is cls.raiseException.func_code:
365
return lastFrame.f_locals.get('self')
367
# handle throwExceptionIntoGenerator-originated exceptions
368
# this is tricky, and differs if the exception was caught
369
# inside the generator, or above it:
371
# it is only really originating from
372
# throwExceptionIntoGenerator if the bottom of the traceback
374
# Pyrex and Cython extensions create traceback frames
375
# with no co_code, but they can't yield so we know it's okay to just return here.
376
if ((not lastFrame.f_code.co_code) or
377
lastFrame.f_code.co_code[lastTb.tb_lasti] != cls._yieldOpcode):
380
# if the exception was caught above the generator.throw
381
# (outside the generator), it will appear in the tb (as the
384
frame = secondLastTb.tb_frame
385
if frame.f_code is cls.throwExceptionIntoGenerator.func_code:
386
return frame.f_locals.get('self')
388
# if the exception was caught below the generator.throw
389
# (inside the generator), it will appear in the frames' linked
390
# list, above the top-level traceback item (which must be the
391
# generator frame itself, thus its caller is
392
# throwExceptionIntoGenerator).
393
frame = tb.tb_frame.f_back
394
if frame and frame.f_code is cls.throwExceptionIntoGenerator.func_code:
395
return frame.f_locals.get('self')
397
_findFailure = classmethod(_findFailure)
400
return "<%s %s>" % (self.__class__, self.type)
403
return "[Failure instance: %s]" % self.getBriefTraceback()
405
def __getstate__(self):
406
"""Avoid pickling objects in the traceback.
410
c = self.__dict__.copy()
415
[(j[0], reflect.safe_repr(j[1])) for j in v[3]],
416
[(j[0], reflect.safe_repr(j[1])) for j in v[4]]
417
] for v in self.frames
420
# added 2003-06-23. See comment above in __init__
423
if self.stack is not None:
424
# XXX: This is a band-aid. I can't figure out where these
425
# (failure.stack is None) instances are coming from.
429
[(j[0], reflect.safe_repr(j[1])) for j in v[3]],
430
[(j[0], reflect.safe_repr(j[1])) for j in v[4]]
431
] for v in self.stack
437
def cleanFailure(self):
438
"""Remove references to other objects, replacing them with strings.
440
self.__dict__ = self.__getstate__()
442
def getTracebackObject(self):
444
Get an object that represents this Failure's stack that can be passed
445
to traceback.extract_tb.
447
If the original traceback object is still present, return that. If this
448
traceback object has been lost but we still have the information,
449
return a fake traceback object (see L{_Traceback}). If there is no
450
traceback information at all, return None.
452
if self.tb is not None:
454
elif len(self.frames) > 0:
455
return _Traceback(self.frames)
459
def getErrorMessage(self):
460
"""Get a string of the exception which caused this Failure."""
461
if isinstance(self.value, Failure):
462
return self.value.getErrorMessage()
463
return reflect.safe_str(self.value)
465
def getBriefTraceback(self):
467
self.printBriefTraceback(file=io)
470
def getTraceback(self, elideFrameworkCode=0, detail='default'):
472
self.printTraceback(file=io, elideFrameworkCode=elideFrameworkCode, detail=detail)
475
def printTraceback(self, file=None, elideFrameworkCode=0, detail='default'):
476
"""Emulate Python's standard error reporting mechanism.
483
if detail == 'verbose':
484
w( '*--- Failure #%d%s---\n' %
486
(self.pickled and ' (pickled) ') or ' '))
487
elif detail == 'brief':
489
hasFrames = 'Traceback'
491
hasFrames = 'Traceback (failure with no frames)'
492
w("%s: %s: %s\n" % (hasFrames, self.type, self.value))
494
w( 'Traceback (most recent call last):\n')
496
# Frames, formatted in appropriate style
498
if not elideFrameworkCode:
499
format_frames(self.stack[-traceupLength:], w, detail)
500
w("%s\n" % (EXCEPTION_CAUGHT_HERE,))
501
format_frames(self.frames, w, detail)
502
elif not detail == 'brief':
503
# Yeah, it's not really a traceback, despite looking like one...
507
if not detail == 'brief':
508
# Unfortunately, self.type will not be a class object if this
509
# Failure was created implicitly from a string exception.
510
# qual() doesn't make any sense on a string, so check for this
511
# case here and just write out the string if that's what we
513
if isinstance(self.type, (str, unicode)):
516
w("%s: %s\n" % (reflect.qual(self.type),
517
reflect.safe_str(self.value)))
519
if isinstance(self.value, Failure):
520
# TODO: indentation for chained failures?
521
file.write(" (chained Failure)\n")
522
self.value.printTraceback(file, elideFrameworkCode, detail)
523
if detail == 'verbose':
524
w('*--- End of Failure #%d ---\n' % self.count)
526
def printBriefTraceback(self, file=None, elideFrameworkCode=0):
527
"""Print a traceback as densely as possible.
529
self.printTraceback(file, elideFrameworkCode, detail='brief')
531
def printDetailedTraceback(self, file=None, elideFrameworkCode=0):
532
"""Print a traceback with detailed locals and globals information.
534
self.printTraceback(file, elideFrameworkCode, detail='verbose')
536
# slyphon: make post-morteming exceptions tweakable
538
DO_POST_MORTEM = True
540
def _debuginit(self, exc_value=None, exc_type=None, exc_tb=None,
541
Failure__init__=Failure.__init__.im_func):
542
if (exc_value, exc_type, exc_tb) == (None, None, None):
544
if not exc[0] == self.__class__ and DO_POST_MORTEM:
545
print "Jumping into debugger for post-mortem of exception '%s':" % exc[1]
547
pdb.post_mortem(exc[2])
548
Failure__init__(self, exc_value, exc_type, exc_tb)
550
def startDebugMode():
551
"""Enable debug hooks for Failures."""
552
Failure.__init__ = _debuginit
555
# Sibling imports - at the bottom and unqualified to avoid unresolvable