Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / python / failure.py
1 # -*- test-case-name: twisted.test.test_failure -*-
2 # See also test suite twisted.test.test_pbfailure
3
4 # Copyright (c) Twisted Matrix Laboratories.
5 # See LICENSE for details.
6
7
8 """
9 Asynchronous-friendly error mechanism.
10
11 See L{Failure}.
12 """
13
14 # System Imports
15 import sys
16 import linecache
17 import inspect
18 import opcode
19 from cStringIO import StringIO
20 from inspect import getmro
21
22 from twisted.python import reflect
23
24 count = 0
25 traceupLength = 4
26
27 class DefaultException(Exception):
28     pass
29
30 def format_frames(frames, write, detail="default"):
31     """Format and write frames.
32
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())
36     @type frames: list
37     @param write: this will be called with formatted strings.
38     @type write: callable
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.
44     @type detail: string
45     """
46     if detail not in ('default', 'brief', 'verbose',
47                       'verbose-vars-not-captured'):
48         raise ValueError(
49             "Detail must be default, brief, verbose, or "
50             "verbose-vars-not-captured. (not %r)" % (detail,))
51     w = write
52     if detail == "brief":
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))
66             w(' [ Locals ]\n')
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)))
70             w(' ( Globals )\n')
71             for name, val in globalVars:
72                 w("  %s : %s\n" %  (name, repr(val)))
73
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> ---"
77
78
79
80 class NoCurrentExceptionError(Exception):
81     """
82     Raised when trying to create a Failure from the current interpreter
83     exception state and there is no current exception state.
84     """
85
86
87 class _Traceback(object):
88     """
89     Fake traceback object which can be passed to functions in the standard
90     library L{traceback} module.
91     """
92
93     def __init__(self, frames):
94         """
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
98         standard tracebacks.
99
100         @param frames: [(methodname, filename, lineno, locals, globals), ...]
101         """
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
107         if len(frames) == 0:
108             self.tb_next = None
109         else:
110             self.tb_next = _Traceback(frames)
111
112
113 class _Frame(object):
114     """
115     A fake frame object, used by L{_Traceback}.
116
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)
120     """
121
122     def __init__(self, name, filename):
123         """
124         @param name: method/function name for this frame.
125         @type name: C{str}
126         @param filename: filename for this frame.
127         @type name: C{str}
128         """
129         self.f_code = _Code(name, filename)
130         self.f_globals = {}
131         self.f_locals = {}
132
133
134 class _Code(object):
135     """
136     A fake code object, used by L{_Traceback} via L{_Frame}.
137     """
138     def __init__(self, name, filename):
139         self.co_name = name
140         self.co_filename = filename
141
142
143 class Failure:
144     """
145     A basic abstraction for an error that has occurred.
146
147     This is necessary because Python's built-in error mechanisms are
148     inconvenient for asynchronous communication.
149
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.
155
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.
160     """
161
162     pickled = 0
163     stack = None
164
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"])
169
170     def __init__(self, exc_value=None, exc_type=None, exc_tb=None,
171                  captureVars=False):
172         """
173         Initialize me with an explanation of the error.
174
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
178         argument.
179
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}.
185
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}).
190
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}.
194         """
195         global count
196         count = count + 1
197         self.count = count
198         self.type = self.value = tb = None
199         self.captureVars = captureVars
200
201         #strings Exceptions/Failures are bad, mmkay?
202         if isinstance(exc_value, (str, unicode)) and exc_type is None:
203             import warnings
204             warnings.warn(
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)
208
209         stackOffset = 0
210
211         if exc_value is None:
212             exc_value = self._findFailure()
213
214         if exc_value is None:
215             self.type, self.value, tb = sys.exc_info()
216             if self.type is None:
217                 raise NoCurrentExceptionError()
218             stackOffset = 1
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
225         else:
226             self.type = exc_type
227             self.value = exc_value
228         if isinstance(self.value, Failure):
229             self.__dict__ = self.value.__dict__
230             return
231         if tb is None:
232             if exc_tb:
233                 tb = exc_tb
234 #             else:
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():
238 #                     log.msg(s)
239
240         frames = self.frames = []
241         stack = self.stack = []
242
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.
246         self.tb = tb
247
248         if tb:
249             f = tb.tb_frame
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
255
256         while stackOffset and f:
257             # This excludes this Failure.__init__ frame from the
258             # stack, leaving it to start with our caller instead.
259             f = f.f_back
260             stackOffset -= 1
261
262         # Keeps the *full* stack.  Formerly in spread.pb.print_excFullStack:
263         #
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.
269
270         while f:
271             if captureVars:
272                 localz = f.f_locals.copy()
273                 if f.f_locals is f.f_globals:
274                     globalz = {}
275                 else:
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()
282             else:
283                 localz = globalz = ()
284             stack.insert(0, (
285                 f.f_code.co_name,
286                 f.f_code.co_filename,
287                 f.f_lineno,
288                 localz,
289                 globalz,
290                 ))
291             f = f.f_back
292
293         while tb is not None:
294             f = tb.tb_frame
295             if captureVars:
296                 localz = f.f_locals.copy()
297                 if f.f_locals is f.f_globals:
298                     globalz = {}
299                 else:
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()
306             else:
307                 localz = globalz = ()
308             frames.append((
309                 f.f_code.co_name,
310                 f.f_code.co_filename,
311                 tb.tb_lineno,
312                 localz,
313                 globalz,
314                 ))
315             tb = tb.tb_next
316         if inspect.isclass(self.type) and issubclass(self.type, Exception):
317             parentCs = getmro(self.type)
318             self.parents = map(reflect.qual, parentCs)
319         else:
320             self.parents = [self.type]
321
322     def trap(self, *errorTypes):
323         """Trap this failure if its type is in a predetermined list.
324
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.
327
328         The reason for having this particular API is because it's very useful
329         in Deferred errback chains::
330
331             def _ebFoo(self, failure):
332                 r = failure.trap(Spam, Eggs)
333                 print 'The Failure is due to either Spam or Eggs!'
334                 if r == Spam:
335                     print 'Spam did it!'
336                 elif r == Eggs:
337                     print 'Eggs did it!'
338
339         If the failure is not a Spam or an Eggs, then the Failure
340         will be 'passed on' to the next errback.
341
342         @type errorTypes: L{Exception}
343         """
344         error = self.check(*errorTypes)
345         if not error:
346             raise self
347         return error
348
349     def check(self, *errorTypes):
350         """Check if this failure's type is in a predetermined list.
351
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.
355         """
356         for error in errorTypes:
357             err = error
358             if inspect.isclass(error) and issubclass(error, Exception):
359                 err = reflect.qual(error)
360             if err in self.parents:
361                 return error
362         return None
363
364
365     def raiseException(self):
366         """
367         raise the original exception, preserving traceback
368         information if available.
369         """
370         raise self.type, self.value, self.tb
371
372
373     def throwExceptionIntoGenerator(self, g):
374         """
375         Throw the original exception into the given generator,
376         preserving traceback information if available.
377
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.
381         """
382         return g.throw(self.type, self.value, self.tb)
383
384
385     def _findFailure(cls):
386         """
387         Find the failure that represents the exception currently in context.
388         """
389         tb = sys.exc_info()[-1]
390         if not tb:
391             return
392
393         secondLastTb = None
394         lastTb = tb
395         while lastTb.tb_next:
396             secondLastTb = lastTb
397             lastTb = lastTb.tb_next
398
399         lastFrame = lastTb.tb_frame
400
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.
406
407         # handle raiseException-originated exceptions
408         if lastFrame.f_code is cls.raiseException.func_code:
409             return lastFrame.f_locals.get('self')
410
411         # handle throwExceptionIntoGenerator-originated exceptions
412         # this is tricky, and differs if the exception was caught
413         # inside the generator, or above it:
414
415         # it is only really originating from
416         # throwExceptionIntoGenerator if the bottom of the traceback
417         # is a yield.
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):
422             return
423
424         # if the exception was caught above the generator.throw
425         # (outside the generator), it will appear in the tb (as the
426         # second last item):
427         if secondLastTb:
428             frame = secondLastTb.tb_frame
429             if frame.f_code is cls.throwExceptionIntoGenerator.func_code:
430                 return frame.f_locals.get('self')
431
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')
440
441     _findFailure = classmethod(_findFailure)
442
443     def __repr__(self):
444         return "<%s %s>" % (self.__class__, self.type)
445
446     def __str__(self):
447         return "[Failure instance: %s]" % self.getBriefTraceback()
448
449     def __getstate__(self):
450         """Avoid pickling objects in the traceback.
451         """
452         if self.pickled:
453             return self.__dict__
454         c = self.__dict__.copy()
455
456         c['frames'] = [
457             [
458                 v[0], v[1], v[2],
459                 _safeReprVars(v[3]),
460                 _safeReprVars(v[4]),
461             ] for v in self.frames
462         ]
463
464         # added 2003-06-23. See comment above in __init__
465         c['tb'] = None
466
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.
470             c['stack'] = [
471                 [
472                     v[0], v[1], v[2],
473                     _safeReprVars(v[3]),
474                     _safeReprVars(v[4]),
475                 ] for v in self.stack
476             ]
477
478         c['pickled'] = 1
479         return c
480
481     def cleanFailure(self):
482         """Remove references to other objects, replacing them with strings.
483         """
484         self.__dict__ = self.__getstate__()
485
486     def getTracebackObject(self):
487         """
488         Get an object that represents this Failure's stack that can be passed
489         to traceback.extract_tb.
490
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.
495         """
496         if self.tb is not None:
497             return self.tb
498         elif len(self.frames) > 0:
499             return _Traceback(self.frames)
500         else:
501             return None
502
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)
508
509     def getBriefTraceback(self):
510         io = StringIO()
511         self.printBriefTraceback(file=io)
512         return io.getvalue()
513
514     def getTraceback(self, elideFrameworkCode=0, detail='default'):
515         io = StringIO()
516         self.printTraceback(file=io, elideFrameworkCode=elideFrameworkCode, detail=detail)
517         return io.getvalue()
518
519
520     def printTraceback(self, file=None, elideFrameworkCode=False, detail='default'):
521         """
522         Emulate Python's standard error reporting mechanism.
523
524         @param file: If specified, a file-like object to which to write the
525             traceback.
526
527         @param elideFrameworkCode: A flag indicating whether to attempt to
528             remove uninteresting frames from within Twisted itself from the
529             output.
530
531         @param detail: A string indicating how much information to include
532             in the traceback.  Must be one of C{'brief'}, C{'default'}, or
533             C{'verbose'}.
534         """
535         if file is None:
536             file = log.logerr
537         w = file.write
538
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
542             # all.
543             formatDetail = 'verbose-vars-not-captured'
544         else:
545             formatDetail = detail
546
547         # Preamble
548         if detail == 'verbose':
549             w( '*--- Failure #%d%s---\n' %
550                (self.count,
551                 (self.pickled and ' (pickled) ') or ' '))
552         elif detail == 'brief':
553             if self.frames:
554                 hasFrames = 'Traceback'
555             else:
556                 hasFrames = 'Traceback (failure with no frames)'
557             w("%s: %s: %s\n" % (
558                     hasFrames,
559                     reflect.safe_str(self.type),
560                     reflect.safe_str(self.value)))
561         else:
562             w( 'Traceback (most recent call last):\n')
563
564         # Frames, formatted in appropriate style
565         if self.frames:
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...
572             w("Failure: ")
573
574         # postamble, if any
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
580             # have.
581             if isinstance(self.type, (str, unicode)):
582                 w(self.type + "\n")
583             else:
584                 w("%s: %s\n" % (reflect.qual(self.type),
585                                 reflect.safe_str(self.value)))
586         # chaining
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)
593
594
595     def printBriefTraceback(self, file=None, elideFrameworkCode=0):
596         """Print a traceback as densely as possible.
597         """
598         self.printTraceback(file, elideFrameworkCode, detail='brief')
599
600     def printDetailedTraceback(self, file=None, elideFrameworkCode=0):
601         """Print a traceback with detailed locals and globals information.
602         """
603         self.printTraceback(file, elideFrameworkCode, detail='verbose')
604
605
606 def _safeReprVars(varsDictItems):
607     """
608     Convert a list of (name, object) pairs into (name, repr) pairs.
609
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.
612
613     @param varsDictItems: a sequence of (name, value) pairs as returned by e.g.
614         C{locals().items()}.
615     @returns: a sequence of (name, repr) pairs.
616     """
617     return [(name, reflect.safe_repr(obj)) for (name, obj) in varsDictItems]
618
619
620 # slyphon: make post-morteming exceptions tweakable
621
622 DO_POST_MORTEM = True
623
624 def _debuginit(self, exc_value=None, exc_type=None, exc_tb=None,
625                captureVars=False,
626                Failure__init__=Failure.__init__.im_func):
627     """
628     Initialize failure object, possibly spawning pdb.
629     """
630     if (exc_value, exc_type, exc_tb) == (None, None, None):
631         exc = sys.exc_info()
632         if not exc[0] == self.__class__ and DO_POST_MORTEM:
633             try:
634                 strrepr = str(exc[1])
635             except:
636                 strrepr = "broken str"
637             print "Jumping into debugger for post-mortem of exception '%s':" % (strrepr,)
638             import pdb
639             pdb.post_mortem(exc[2])
640     Failure__init__(self, exc_value, exc_type, exc_tb, captureVars)
641
642
643 def startDebugMode():
644     """Enable debug hooks for Failures."""
645     Failure.__init__ = _debuginit
646
647
648 # Sibling imports - at the bottom and unqualified to avoid unresolvable
649 # circularity
650 import log