1 # -*- test-case-name: twisted.test.test_failure -*-
2 # See also test suite twisted.test.test_pbfailure
4 # Copyright (c) Twisted Matrix Laboratories.
5 # See LICENSE for details.
9 Asynchronous-friendly error mechanism.
19 from cStringIO import StringIO
20 from inspect import getmro
22 from twisted.python import reflect
27 class DefaultException(Exception):
30 def format_frames(frames, write, detail="default"):
31 """Format and write frames.
33 @param frames: is a list of frames as used by Failure.frames, with
34 each frame being a list of
35 (funcName, fileName, lineNumber, locals.items(), globals.items())
37 @param write: this will be called with formatted strings.
39 @param detail: Four detail levels are available:
40 default, brief, verbose, and verbose-vars-not-captured.
41 C{Failure.printDetailedTraceback} uses the latter when the caller asks
42 for verbose, but no vars were captured, so that an explicit warning
43 about the missing data is shown.
46 if detail not in ('default', 'brief', 'verbose',
47 'verbose-vars-not-captured'):
49 "Detail must be default, brief, verbose, or "
50 "verbose-vars-not-captured. (not %r)" % (detail,))
53 for method, filename, lineno, localVars, globalVars in frames:
54 w('%s:%s:%s\n' % (filename, lineno, method))
55 elif detail == "default":
56 for method, filename, lineno, localVars, globalVars in frames:
57 w( ' File "%s", line %s, in %s\n' % (filename, lineno, method))
58 w( ' %s\n' % linecache.getline(filename, lineno).strip())
59 elif detail == "verbose-vars-not-captured":
60 for method, filename, lineno, localVars, globalVars in frames:
61 w("%s:%d: %s(...)\n" % (filename, lineno, method))
62 w(' [Capture of Locals and Globals disabled (use captureVars=True)]\n')
63 elif detail == "verbose":
64 for method, filename, lineno, localVars, globalVars in frames:
65 w("%s:%d: %s(...)\n" % (filename, lineno, method))
67 # Note: the repr(val) was (self.pickled and val) or repr(val)))
68 for name, val in localVars:
69 w(" %s : %s\n" % (name, repr(val)))
71 for name, val in globalVars:
72 w(" %s : %s\n" % (name, repr(val)))
74 # slyphon: i have a need to check for this value in trial
75 # so I made it a module-level constant
76 EXCEPTION_CAUGHT_HERE = "--- <exception caught here> ---"
80 class NoCurrentExceptionError(Exception):
82 Raised when trying to create a Failure from the current interpreter
83 exception state and there is no current exception state.
87 class _Traceback(object):
89 Fake traceback object which can be passed to functions in the standard
90 library L{traceback} module.
93 def __init__(self, frames):
95 Construct a fake traceback object using a list of frames. Note that
96 although frames generally include locals and globals, this information
97 is not kept by this object, since locals and globals are not used in
100 @param frames: [(methodname, filename, lineno, locals, globals), ...]
102 assert len(frames) > 0, "Must pass some frames"
103 head, frames = frames[0], frames[1:]
104 name, filename, lineno, localz, globalz = head
105 self.tb_frame = _Frame(name, filename)
106 self.tb_lineno = lineno
110 self.tb_next = _Traceback(frames)
113 class _Frame(object):
115 A fake frame object, used by L{_Traceback}.
117 @ivar f_code: fake L{code<types.CodeType>} object
118 @ivar f_globals: fake f_globals dictionary (usually empty)
119 @ivar f_locals: fake f_locals dictionary (usually empty)
122 def __init__(self, name, filename):
124 @param name: method/function name for this frame.
126 @param filename: filename for this frame.
129 self.f_code = _Code(name, filename)
136 A fake code object, used by L{_Traceback} via L{_Frame}.
138 def __init__(self, name, filename):
140 self.co_filename = filename
145 A basic abstraction for an error that has occurred.
147 This is necessary because Python's built-in error mechanisms are
148 inconvenient for asynchronous communication.
150 The C{stack} and C{frame} attributes contain frames. Each frame is a tuple
151 of (funcName, fileName, lineNumber, localsItems, globalsItems), where
152 localsItems and globalsItems are the contents of
153 C{locals().items()}/C{globals().items()} for that frame, or an empty tuple
154 if those details were not captured.
156 @ivar value: The exception instance responsible for this failure.
157 @ivar type: The exception's class.
158 @ivar stack: list of frames, innermost last, excluding C{Failure.__init__}.
159 @ivar frames: list of frames, innermost first.
165 # The opcode of "yield" in Python bytecode. We need this in _findFailure in
166 # order to identify whether an exception was thrown by a
167 # throwExceptionIntoGenerator.
168 _yieldOpcode = chr(opcode.opmap["YIELD_VALUE"])
170 def __init__(self, exc_value=None, exc_type=None, exc_tb=None,
173 Initialize me with an explanation of the error.
175 By default, this will use the current C{exception}
176 (L{sys.exc_info}()). However, if you want to specify a
177 particular kind of failure, you can pass an exception as an
180 If no C{exc_value} is passed, then an "original" C{Failure} will
181 be searched for. If the current exception handler that this
182 C{Failure} is being constructed in is handling an exception
183 raised by L{raiseException}, then this C{Failure} will act like
184 the original C{Failure}.
186 For C{exc_tb} only L{traceback} instances or C{None} are allowed.
187 If C{None} is supplied for C{exc_value}, the value of C{exc_tb} is
188 ignored, otherwise if C{exc_tb} is C{None}, it will be found from
189 execution context (ie, L{sys.exc_info}).
191 @param captureVars: if set, capture locals and globals of stack
192 frames. This is pretty slow, and makes no difference unless you
193 are going to use L{printDetailedTraceback}.
198 self.type = self.value = tb = None
199 self.captureVars = captureVars
201 #strings Exceptions/Failures are bad, mmkay?
202 if isinstance(exc_value, (str, unicode)) and exc_type is None:
205 "Don't pass strings (like %r) to failure.Failure (replacing with a DefaultException)." %
206 exc_value, DeprecationWarning, stacklevel=2)
207 exc_value = DefaultException(exc_value)
211 if exc_value is None:
212 exc_value = self._findFailure()
214 if exc_value is None:
215 self.type, self.value, tb = sys.exc_info()
216 if self.type is None:
217 raise NoCurrentExceptionError()
219 elif exc_type is None:
220 if isinstance(exc_value, Exception):
221 self.type = exc_value.__class__
222 else: #allow arbitrary objects.
223 self.type = type(exc_value)
224 self.value = exc_value
227 self.value = exc_value
228 if isinstance(self.value, Failure):
229 self.__dict__ = self.value.__dict__
235 # log.msg("Erf, %r created with no traceback, %s %s." % (
236 # repr(self), repr(exc_value), repr(exc_type)))
237 # for s in traceback.format_stack():
240 frames = self.frames = []
241 stack = self.stack = []
243 # added 2003-06-23 by Chris Armstrong. Yes, I actually have a
244 # use case where I need this traceback object, and I've made
245 # sure that it'll be cleaned up.
250 elif not isinstance(self.value, Failure):
251 # we don't do frame introspection since it's expensive,
252 # and if we were passed a plain exception with no
253 # traceback, it's not useful anyway
254 f = stackOffset = None
256 while stackOffset and f:
257 # This excludes this Failure.__init__ frame from the
258 # stack, leaving it to start with our caller instead.
262 # Keeps the *full* stack. Formerly in spread.pb.print_excFullStack:
264 # The need for this function arises from the fact that several
265 # PB classes have the peculiar habit of discarding exceptions
266 # with bareword "except:"s. This premature exception
267 # catching means tracebacks generated here don't tend to show
268 # what called upon the PB object.
272 localz = f.f_locals.copy()
273 if f.f_locals is f.f_globals:
276 globalz = f.f_globals.copy()
277 for d in globalz, localz:
278 if "__builtins__" in d:
279 del d["__builtins__"]
280 localz = localz.items()
281 globalz = globalz.items()
283 localz = globalz = ()
286 f.f_code.co_filename,
293 while tb is not None:
296 localz = f.f_locals.copy()
297 if f.f_locals is f.f_globals:
300 globalz = f.f_globals.copy()
301 for d in globalz, localz:
302 if "__builtins__" in d:
303 del d["__builtins__"]
304 localz = localz.items()
305 globalz = globalz.items()
307 localz = globalz = ()
310 f.f_code.co_filename,
316 if inspect.isclass(self.type) and issubclass(self.type, Exception):
317 parentCs = getmro(self.type)
318 self.parents = map(reflect.qual, parentCs)
320 self.parents = [self.type]
322 def trap(self, *errorTypes):
323 """Trap this failure if its type is in a predetermined list.
325 This allows you to trap a Failure in an error callback. It will be
326 automatically re-raised if it is not a type that you expect.
328 The reason for having this particular API is because it's very useful
329 in Deferred errback chains::
331 def _ebFoo(self, failure):
332 r = failure.trap(Spam, Eggs)
333 print 'The Failure is due to either Spam or Eggs!'
339 If the failure is not a Spam or an Eggs, then the Failure
340 will be 'passed on' to the next errback.
342 @type errorTypes: L{Exception}
344 error = self.check(*errorTypes)
349 def check(self, *errorTypes):
350 """Check if this failure's type is in a predetermined list.
352 @type errorTypes: list of L{Exception} classes or
353 fully-qualified class names.
354 @returns: the matching L{Exception} type, or None if no match.
356 for error in errorTypes:
358 if inspect.isclass(error) and issubclass(error, Exception):
359 err = reflect.qual(error)
360 if err in self.parents:
365 def raiseException(self):
367 raise the original exception, preserving traceback
368 information if available.
370 raise self.type, self.value, self.tb
373 def throwExceptionIntoGenerator(self, g):
375 Throw the original exception into the given generator,
376 preserving traceback information if available.
378 @return: The next value yielded from the generator.
379 @raise StopIteration: If there are no more values in the generator.
380 @raise anything else: Anything that the generator raises.
382 return g.throw(self.type, self.value, self.tb)
385 def _findFailure(cls):
387 Find the failure that represents the exception currently in context.
389 tb = sys.exc_info()[-1]
395 while lastTb.tb_next:
396 secondLastTb = lastTb
397 lastTb = lastTb.tb_next
399 lastFrame = lastTb.tb_frame
401 # NOTE: f_locals.get('self') is used rather than
402 # f_locals['self'] because psyco frames do not contain
403 # anything in their locals() dicts. psyco makes debugging
404 # difficult anyhow, so losing the Failure objects (and thus
405 # the tracebacks) here when it is used is not that big a deal.
407 # handle raiseException-originated exceptions
408 if lastFrame.f_code is cls.raiseException.func_code:
409 return lastFrame.f_locals.get('self')
411 # handle throwExceptionIntoGenerator-originated exceptions
412 # this is tricky, and differs if the exception was caught
413 # inside the generator, or above it:
415 # it is only really originating from
416 # throwExceptionIntoGenerator if the bottom of the traceback
418 # Pyrex and Cython extensions create traceback frames
419 # with no co_code, but they can't yield so we know it's okay to just return here.
420 if ((not lastFrame.f_code.co_code) or
421 lastFrame.f_code.co_code[lastTb.tb_lasti] != cls._yieldOpcode):
424 # if the exception was caught above the generator.throw
425 # (outside the generator), it will appear in the tb (as the
428 frame = secondLastTb.tb_frame
429 if frame.f_code is cls.throwExceptionIntoGenerator.func_code:
430 return frame.f_locals.get('self')
432 # if the exception was caught below the generator.throw
433 # (inside the generator), it will appear in the frames' linked
434 # list, above the top-level traceback item (which must be the
435 # generator frame itself, thus its caller is
436 # throwExceptionIntoGenerator).
437 frame = tb.tb_frame.f_back
438 if frame and frame.f_code is cls.throwExceptionIntoGenerator.func_code:
439 return frame.f_locals.get('self')
441 _findFailure = classmethod(_findFailure)
444 return "<%s %s>" % (self.__class__, self.type)
447 return "[Failure instance: %s]" % self.getBriefTraceback()
449 def __getstate__(self):
450 """Avoid pickling objects in the traceback.
454 c = self.__dict__.copy()
461 ] for v in self.frames
464 # added 2003-06-23. See comment above in __init__
467 if self.stack is not None:
468 # XXX: This is a band-aid. I can't figure out where these
469 # (failure.stack is None) instances are coming from.
475 ] for v in self.stack
481 def cleanFailure(self):
482 """Remove references to other objects, replacing them with strings.
484 self.__dict__ = self.__getstate__()
486 def getTracebackObject(self):
488 Get an object that represents this Failure's stack that can be passed
489 to traceback.extract_tb.
491 If the original traceback object is still present, return that. If this
492 traceback object has been lost but we still have the information,
493 return a fake traceback object (see L{_Traceback}). If there is no
494 traceback information at all, return None.
496 if self.tb is not None:
498 elif len(self.frames) > 0:
499 return _Traceback(self.frames)
503 def getErrorMessage(self):
504 """Get a string of the exception which caused this Failure."""
505 if isinstance(self.value, Failure):
506 return self.value.getErrorMessage()
507 return reflect.safe_str(self.value)
509 def getBriefTraceback(self):
511 self.printBriefTraceback(file=io)
514 def getTraceback(self, elideFrameworkCode=0, detail='default'):
516 self.printTraceback(file=io, elideFrameworkCode=elideFrameworkCode, detail=detail)
520 def printTraceback(self, file=None, elideFrameworkCode=False, detail='default'):
522 Emulate Python's standard error reporting mechanism.
524 @param file: If specified, a file-like object to which to write the
527 @param elideFrameworkCode: A flag indicating whether to attempt to
528 remove uninteresting frames from within Twisted itself from the
531 @param detail: A string indicating how much information to include
532 in the traceback. Must be one of C{'brief'}, C{'default'}, or
539 if detail == 'verbose' and not self.captureVars:
540 # We don't have any locals or globals, so rather than show them as
541 # empty make the output explicitly say that we don't have them at
543 formatDetail = 'verbose-vars-not-captured'
545 formatDetail = detail
548 if detail == 'verbose':
549 w( '*--- Failure #%d%s---\n' %
551 (self.pickled and ' (pickled) ') or ' '))
552 elif detail == 'brief':
554 hasFrames = 'Traceback'
556 hasFrames = 'Traceback (failure with no frames)'
559 reflect.safe_str(self.type),
560 reflect.safe_str(self.value)))
562 w( 'Traceback (most recent call last):\n')
564 # Frames, formatted in appropriate style
566 if not elideFrameworkCode:
567 format_frames(self.stack[-traceupLength:], w, formatDetail)
568 w("%s\n" % (EXCEPTION_CAUGHT_HERE,))
569 format_frames(self.frames, w, formatDetail)
570 elif not detail == 'brief':
571 # Yeah, it's not really a traceback, despite looking like one...
575 if not detail == 'brief':
576 # Unfortunately, self.type will not be a class object if this
577 # Failure was created implicitly from a string exception.
578 # qual() doesn't make any sense on a string, so check for this
579 # case here and just write out the string if that's what we
581 if isinstance(self.type, (str, unicode)):
584 w("%s: %s\n" % (reflect.qual(self.type),
585 reflect.safe_str(self.value)))
587 if isinstance(self.value, Failure):
588 # TODO: indentation for chained failures?
589 file.write(" (chained Failure)\n")
590 self.value.printTraceback(file, elideFrameworkCode, detail)
591 if detail == 'verbose':
592 w('*--- End of Failure #%d ---\n' % self.count)
595 def printBriefTraceback(self, file=None, elideFrameworkCode=0):
596 """Print a traceback as densely as possible.
598 self.printTraceback(file, elideFrameworkCode, detail='brief')
600 def printDetailedTraceback(self, file=None, elideFrameworkCode=0):
601 """Print a traceback with detailed locals and globals information.
603 self.printTraceback(file, elideFrameworkCode, detail='verbose')
606 def _safeReprVars(varsDictItems):
608 Convert a list of (name, object) pairs into (name, repr) pairs.
610 L{twisted.python.reflect.safe_repr} is used to generate the repr, so no
611 exceptions will be raised by faulty C{__repr__} methods.
613 @param varsDictItems: a sequence of (name, value) pairs as returned by e.g.
615 @returns: a sequence of (name, repr) pairs.
617 return [(name, reflect.safe_repr(obj)) for (name, obj) in varsDictItems]
620 # slyphon: make post-morteming exceptions tweakable
622 DO_POST_MORTEM = True
624 def _debuginit(self, exc_value=None, exc_type=None, exc_tb=None,
626 Failure__init__=Failure.__init__.im_func):
628 Initialize failure object, possibly spawning pdb.
630 if (exc_value, exc_type, exc_tb) == (None, None, None):
632 if not exc[0] == self.__class__ and DO_POST_MORTEM:
634 strrepr = str(exc[1])
636 strrepr = "broken str"
637 print "Jumping into debugger for post-mortem of exception '%s':" % (strrepr,)
639 pdb.post_mortem(exc[2])
640 Failure__init__(self, exc_value, exc_type, exc_tb, captureVars)
643 def startDebugMode():
644 """Enable debug hooks for Failures."""
645 Failure.__init__ = _debuginit
648 # Sibling imports - at the bottom and unqualified to avoid unresolvable