Back to index

moin  1.9.0~rc2
tbtools.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     werkzeug.debug.tbtools
00004     ~~~~~~~~~~~~~~~~~~~~~~
00005 
00006     This module provides various traceback related utility functions.
00007 
00008     :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
00009     :license: BSD.
00010 """
00011 import re
00012 import os
00013 import sys
00014 import inspect
00015 import traceback
00016 import codecs
00017 from tokenize import TokenError
00018 from werkzeug.utils import cached_property
00019 from werkzeug.debug.console import Console
00020 from werkzeug.debug.utils import render_template
00021 
00022 _coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
00023 _line_re = re.compile(r'^(.*?)$(?m)')
00024 _funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
00025 UTF8_COOKIE = '\xef\xbb\xbf'
00026 
00027 system_exceptions = (SystemExit, KeyboardInterrupt)
00028 try:
00029     system_exceptions += (GeneratorExit,)
00030 except NameError:
00031     pass
00032 
00033 
00034 def get_current_traceback(ignore_system_exceptions=False,
00035                           show_hidden_frames=False, skip=0):
00036     """Get the current exception info as `Traceback` object.  Per default
00037     calling this method will reraise system exceptions such as generator exit,
00038     system exit or others.  This behavior can be disabled by passing `False`
00039     to the function as first parameter.
00040     """
00041     exc_type, exc_value, tb = sys.exc_info()
00042     if ignore_system_exceptions and exc_type in system_exceptions:
00043         raise
00044     for x in xrange(skip):
00045         if tb.tb_next is None:
00046             break
00047         tb = tb.tb_next
00048     tb = Traceback(exc_type, exc_value, tb)
00049     if not show_hidden_frames:
00050         tb.filter_hidden_frames()
00051     return tb
00052 
00053 
00054 class Line(object):
00055     """Helper for the source renderer."""
00056     __slots__ = ('lineno', 'code', 'in_frame', 'current')
00057 
00058     def __init__(self, lineno, code):
00059         self.lineno = lineno
00060         self.code = code
00061         self.in_frame = False
00062         self.current = False
00063 
00064     def classes(self):
00065         rv = ['line']
00066         if self.in_frame:
00067             rv.append('in-frame')
00068         if self.current:
00069             rv.append('current')
00070         return rv
00071     classes = property(classes)
00072 
00073 
00074 class Traceback(object):
00075     """Wraps a traceback."""
00076 
00077     def __init__(self, exc_type, exc_value, tb):
00078         self.exc_type = exc_type
00079         self.exc_value = exc_value
00080         if not isinstance(exc_type, str):
00081             exception_type = exc_type.__name__
00082             if exc_type.__module__ not in ('__builtin__', 'exceptions'):
00083                 exception_type = exc_type.__module__ + '.' + exception_type
00084         else:
00085             exception_type = exc_type
00086         self.exception_type = exception_type
00087 
00088         # we only add frames to the list that are not hidden.  This follows
00089         # the the magic variables as defined by paste.exceptions.collector
00090         self.frames = []
00091         while tb:
00092             self.frames.append(Frame(exc_type, exc_value, tb))
00093             tb = tb.tb_next
00094 
00095     def filter_hidden_frames(self):
00096         """Remove the frames according to the paste spec."""
00097         new_frames = []
00098         hidden = False
00099         for frame in self.frames:
00100             hide = frame.hide
00101             if hide in ('before', 'before_and_this'):
00102                 new_frames = []
00103                 hidden = False
00104                 if hide == 'before_and_this':
00105                     continue
00106             elif hide in ('reset', 'reset_and_this'):
00107                 hidden = False
00108                 if hide == 'reset_and_this':
00109                     continue
00110             elif hide in ('after', 'after_and_this'):
00111                 hidden = True
00112                 if hide == 'after_and_this':
00113                     continue
00114             elif hide or hidden:
00115                 continue
00116             new_frames.append(frame)
00117 
00118         # if the last frame is missing something went terrible wrong :(
00119         if self.frames[-1] in new_frames:
00120             self.frames[:] = new_frames
00121 
00122     def is_syntax_error(self):
00123         """Is it a syntax error?"""
00124         return isinstance(self.exc_value, SyntaxError)
00125     is_syntax_error = property(is_syntax_error)
00126 
00127     def exception(self):
00128         """String representation of the exception."""
00129         buf = traceback.format_exception_only(self.exc_type, self.exc_value)
00130         return ''.join(buf).strip().decode('utf-8', 'replace')
00131     exception = property(exception)
00132 
00133     def log(self, logfile=None):
00134         """Log the ASCII traceback into a file object."""
00135         if logfile is None:
00136             logfile = sys.stderr
00137         tb = self.plaintext.encode('utf-8', 'replace').rstrip() + '\n'
00138         logfile.write(tb)
00139 
00140     def paste(self):
00141         """Create a paste and return the paste id."""
00142         from xmlrpclib import ServerProxy
00143         srv = ServerProxy('http://paste.pocoo.org/xmlrpc/')
00144         return srv.pastes.newPaste('pytb', self.plaintext)
00145 
00146     def render_summary(self, include_title=True):
00147         """Render the traceback for the interactive console."""
00148         return render_template('traceback_summary.html', traceback=self,
00149                                include_title=include_title)
00150 
00151     def render_full(self, evalex=False):
00152         """Render the Full HTML page with the traceback info."""
00153         return render_template('traceback_full.html', traceback=self,
00154                                evalex=evalex)
00155 
00156     def plaintext(self):
00157         return render_template('traceback_plaintext.html', traceback=self)
00158     plaintext = cached_property(plaintext)
00159 
00160     id = property(lambda x: id(x))
00161 
00162 
00163 class Frame(object):
00164     """A single frame in a traceback."""
00165 
00166     def __init__(self, exc_type, exc_value, tb):
00167         self.lineno = tb.tb_lineno
00168         self.function_name = tb.tb_frame.f_code.co_name
00169         self.locals = tb.tb_frame.f_locals
00170         self.globals = tb.tb_frame.f_globals
00171 
00172         fn = inspect.getsourcefile(tb) or inspect.getfile(tb)
00173         if fn[-4:] in ('.pyo', '.pyc'):
00174             fn = fn[:-1]
00175         # if it's a file on the file system resolve the real filename.
00176         if os.path.isfile(fn):
00177             fn = os.path.realpath(fn)
00178         self.filename = fn
00179         self.module = self.globals.get('__name__')
00180         self.loader = self.globals.get('__loader__')
00181         self.code = tb.tb_frame.f_code
00182 
00183         # support for paste's traceback extensions
00184         self.hide = self.locals.get('__traceback_hide__', False)
00185         info = self.locals.get('__traceback_info__')
00186         if info is not None:
00187             try:
00188                 info = unicode(info)
00189             except UnicodeError:
00190                 info = str(info).decode('utf-8', 'replace')
00191         self.info = info
00192 
00193     def render(self):
00194         """Render a single frame in a traceback."""
00195         return render_template('frame.html', frame=self)
00196 
00197     def render_source(self):
00198         """Render the sourcecode."""
00199         lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)]
00200 
00201         # find function definition and mark lines
00202         if hasattr(self.code, 'co_firstlineno'):
00203             lineno = self.code.co_firstlineno - 1
00204             while lineno > 0:
00205                 if _funcdef_re.match(lines[lineno].code):
00206                     break
00207                 lineno -= 1
00208             try:
00209                 offset = len(inspect.getblock([x.code + '\n' for x
00210                                                in lines[lineno:]]))
00211             except TokenError:
00212                 offset = 0
00213             for line in lines[lineno:lineno + offset]:
00214                 line.in_frame = True
00215 
00216         # mark current line
00217         try:
00218             lines[self.lineno - 1].current = True
00219         except IndexError:
00220             pass
00221 
00222         return render_template('source.html', frame=self, lines=lines)
00223 
00224     def eval(self, code, mode='single'):
00225         """Evaluate code in the context of the frame."""
00226         if isinstance(code, basestring):
00227             if isinstance(code, unicode):
00228                 code = UTF8_COOKIE + code.encode('utf-8')
00229             code = compile(code, '<interactive>', mode)
00230         if mode != 'exec':
00231             return eval(code, self.globals, self.locals)
00232         exec code in self.globals, self.locals
00233 
00234     @cached_property
00235     def sourcelines(self):
00236         """The sourcecode of the file as list of unicode strings."""
00237         # get sourcecode from loader or file
00238         source = None
00239         if self.loader is not None:
00240             try:
00241                 if hasattr(self.loader, 'get_source'):
00242                     source = self.loader.get_source(self.module)
00243                 elif hasattr(self.loader, 'get_source_by_code'):
00244                     source = self.loader.get_source_by_code(self.code)
00245             except:
00246                 # we munch the exception so that we don't cause troubles
00247                 # if the loader is broken.
00248                 pass
00249 
00250         if source is None:
00251             try:
00252                 f = file(self.filename)
00253             except IOError:
00254                 return []
00255             try:
00256                 source = f.read()
00257             finally:
00258                 f.close()
00259 
00260         # already unicode?  return right away
00261         if isinstance(source, unicode):
00262             return source.splitlines()
00263 
00264         # yes. it should be ascii, but we don't want to reject too many
00265         # characters in the debugger if something breaks
00266         charset = 'utf-8'
00267         if source.startswith(UTF8_COOKIE):
00268             source = source[3:]
00269         else:
00270             for idx, match in enumerate(_line_re.finditer(source)):
00271                 match = _line_re.search(match.group())
00272                 if match is not None:
00273                     charset = match.group(1)
00274                     break
00275                 if idx > 1:
00276                     break
00277 
00278         # on broken cookies we fall back to utf-8 too
00279         try:
00280             codecs.lookup(charset)
00281         except LookupError:
00282             charset = 'utf-8'
00283 
00284         return source.decode(charset, 'replace').splitlines()
00285 
00286     @property
00287     def current_line(self):
00288         try:
00289             return self.sourcelines[self.lineno - 1]
00290         except IndexError:
00291             return u''
00292 
00293     @cached_property
00294     def console(self):
00295         return Console(self.globals, self.locals)
00296 
00297     id = property(lambda x: id(x))