Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/pylons/pylons/error.py
0001"""Custom error middleware subclasses, used for error theme
0002
0003These error middleware sub-classes are used mainly to provide skinning
0004for the Paste middleware. In the future this entire module will likely
0005be little more than a template, as Paste will get the skinning functionality.
0006
0007The only additional thing besides skinning supplied, is the Template traceback
0008information.
0009"""
0010import cgi
0011import logging
0012import sys
0013
0014from paste.evalexception.middleware import *
0015from paste.exceptions.formatter import *
0016
0017import pylons
0018from pylons.middleware import media_path
0019from pylons.util import get_prefix
0020
0021__all__ = []
0022
0023log = logging.getLogger(__name__)
0024
0025
0026class Supplement(errormiddleware.Supplement):
0027    """This is a supplement used to display standard WSGI information in
0028    the traceback.
0029    """
0030    def extraData(self):
0031        data = {}
0032        cgi_vars = data[('extra', 'CGI Variables')] = {}
0033        wsgi_vars = data[('extra', 'WSGI Variables')] = {}
0034        # XXX: Legacy: hiding paste.config
0035        hide_vars = ['paste.config', 'wsgi.errors', 'wsgi.input',
0036                     'wsgi.multithread', 'wsgi.multiprocess',
0037                     'wsgi.run_once', 'wsgi.version',
0038                     'wsgi.url_scheme']
0039        for name, value in self.environ.items():
0040            if name.upper() == name:
0041                if value:
0042                    cgi_vars[name] = value
0043            elif name not in hide_vars:
0044                wsgi_vars[name] = value
0045        if self.environ['wsgi.version'] != (1, 0):
0046            wsgi_vars['wsgi.version'] = self.environ['wsgi.version']
0047        proc_desc = tuple([int(bool(self.environ[key]))
0048                           for key in ('wsgi.multiprocess',
0049                                       'wsgi.multithread',
0050                                       'wsgi.run_once')])
0051        wsgi_vars['wsgi process'] = self.process_combos[proc_desc]
0052        wsgi_vars['application'] = self.middleware.application
0053        data[('extra', 'Configuration')] = pylons.config.current_conf()
0054
0055        # Add any extra sections here
0056
0057        return data
0058
0059
0060class HTMLFormatter(formatter.HTMLFormatter):
0061    def format_collected_data(self, exc_data):
0062        general_data = {}
0063        if self.show_extra_data:
0064            for name, value_list in exc_data.extra_data.items():
0065                if isinstance(name, tuple):
0066                    importance, title = name
0067                else:
0068                    importance, title = 'normal', name
0069                for value in value_list:
0070                    general_data[(importance, name)] = self.format_extra_data(
0071                        importance, title, value)
0072        lines = []
0073        frames = self.filter_frames(exc_data.frames)
0074        for frame in frames:
0075            sup = frame.supplement
0076            if sup:
0077                if sup.object:
0078                    general_data[('important', 'object')] = self.format_sup_object(
0079                        sup.object)
0080                if sup.source_url:
0081                    general_data[('important', 'source_url')] = self.format_sup_url(
0082                        sup.source_url)
0083                if sup.line:
0084                    lines.append(self.format_sup_line_pos(sup.line, sup.column))
0085                if sup.expression:
0086                    lines.append(self.format_sup_expression(sup.expression))
0087                if sup.warnings:
0088                    for warning in sup.warnings:
0089                        lines.append(self.format_sup_warning(warning))
0090                if sup.info:
0091                    lines.extend(self.format_sup_info(sup.info))
0092            if frame.supplement_exception:
0093                lines.append('Exception in supplement:')
0094                lines.append(self.quote_long(frame.supplement_exception))
0095            if frame.traceback_info:
0096                lines.append(self.format_traceback_info(frame.traceback_info))
0097            filename = frame.filename
0098            if filename and self.trim_source_paths:
0099                for path, repl in self.trim_source_paths:
0100                    if filename.startswith(path):
0101                        filename = repl + filename[len(path):]
0102                        break
0103            lines.append(self.format_source_line(filename or '?', frame))
0104            source = frame.get_source_line()
0105            long_source = frame.get_source_line(2)
0106            if source:
0107                lines.append(self.format_long_source(
0108                    source, long_source))
0109        exc_info = self.format_exception_info(
0110            exc_data.exception_type,
0111            exc_data.exception_value)
0112        data_by_importance = {'important': [], 'normal': [],
0113                              'supplemental': [], 'extra': []}
0114        for (importance, name), value in general_data.items():
0115            data_by_importance[importance].append(
0116                (name, value))
0117        for value in data_by_importance.itervalues():
0118            value.sort()
0119        return self.format_combine(data_by_importance, lines, exc_info)
0120
0121    def format_extra_data(self, importance, title, value):
0122        if isinstance(value, str):
0123            s = self.pretty_string_repr(value)
0124            if '\n' in s:
0125                return '%s:<br><pre>%s</pre>' % (title, self.quote(s))
0126            else:
0127                return '%s: <tt>%s</tt>' % (title, self.quote(s))
0128        elif isinstance(value, dict):
0129            return self.zebra_table(title, value)
0130        elif (isinstance(value, (list, tuple))
0131              and self.long_item_list(value)):
0132            return '%s: <tt>[<br>\n&nbsp; &nbsp; %s]</tt>' % (
0133                title, ',<br>&nbsp; &nbsp; '.join(map(self.quote, map(repr, value))))
0134        else:
0135            return '%s: <tt>%s</tt>' % (title, self.quote(repr(value)))
0136
0137    def zebra_table(self, title, rows, table_class="variables"):
0138        if isinstance(rows, dict):
0139            rows = rows.items()
0140            rows.sort()
0141        table = ['<table class="%s">' % table_class,
0142                 '<tr class="header"><th colspan="2">%s</th></tr>'
0143                 % self.quote(title)]
0144        odd = False
0145        for name, value in rows:
0146            try:
0147                value = repr(value)
0148            except Exception, e:
0149                value = 'Cannot print: %s' % e
0150            odd = not odd
0151            table.append(
0152                '<tr class="%s"><td>%s</td>'
0153                % (odd and 'odd' or 'even', self.quote(name)))
0154            table.append(
0155                '<td><tt>%s</tt></td></tr>'
0156                % make_wrappable(self.quote(value)))
0157        table.append('</table>')
0158        return '\n'.join(table)
0159
0160    def format_combine(self, data_by_importance, lines, exc_info):
0161
0162        lines[:0] = [value for n, value in data_by_importance['important']]
0163        lines.append(exc_info)
0164        for name in 'normal', 'supplemental':
0165            lines.extend([value for n, value in data_by_importance[name]])
0166
0167        extra_data = []
0168        if data_by_importance['extra']:
0169            #extra_data.append(
0170            #    '<script type="text/javascript">\nshow_button(\'extra_data\', \'extra data\');\n</script>\n' +
0171            #    '<div id="extra_data" class="hidden-data">\n')
0172            extra_data.extend([value for n, value in data_by_importance['extra']])
0173            #extra_data.append('</div>')
0174        extra_data_text = self.format_combine_lines(extra_data)
0175        text = self.format_combine_lines(lines)
0176        if self.include_reusable:
0177            return str(error_css + hide_display_js + text), extra_data
0178        else:
0179            # Usually because another error is already on this page,
0180            # and so the js & CSS are unneeded
0181            return text, extra_data
0182
0183
0184class InvalidTemplate(Exception):
0185    pass
0186
0187
0188class PylonsEvalException(EvalException):
0189
0190    def __init__(self, application, global_conf=None, xmlhttp_key=None,
0191                 error_template=error_template, **errorparams):
0192        self.application = application
0193        self.error_template=error_template
0194        self.debug_infos = {}
0195        if xmlhttp_key is None:
0196            if global_conf is None:
0197                xmlhttp_key = '_'
0198            else:
0199                xmlhttp_key = global_conf.get('xmlhttp_key', '_')
0200        self.xmlhttp_key = xmlhttp_key
0201        self.errorparams = errorparams
0202        self.errorparams['debug_mode'] = self.errorparams['debug']
0203        del self.errorparams['debug']
0204
0205        for s in ['head','traceback_data','extra_data','template_data']:
0206            if "%("+s+")s" not in self.error_template:
0207                raise InvalidTemplate("Could not find %s in template"%("%("+s+")s"))
0208        try:
0209            error_template%{'head': '',
0210                'traceback_data': '',
0211                'extra_data':'',
0212                'template_data':'',
0213                'set_tab':'',
0214                'prefix':''}
0215        except:
0216            raise Exception('Invalid template. Please ensure all % signs are properly '
0217                            'quoted as %% and no extra substitution strings are present.')
0218
0219    def media(self, environ, start_response):
0220        """Handle Pylons specific media content @ /_debug/media/pylons"""
0221        if environ['PATH_INFO'].startswith('/pylons'):
0222            request.path_info_pop(environ)
0223            app = urlparser.StaticURLParser(media_path)
0224            return app(environ, start_response)
0225        else:
0226            return super(self.__class__, self).media(environ, start_response)
0227    media.exposed = True
0228
0229    def respond(self, environ, start_response):
0230        if environ.get('paste.throw_errors'):
0231            return self.application(environ, start_response)
0232        base_path = request.construct_url(environ, with_path_info=False,
0233                                          with_query_string=False)
0234        environ['paste.throw_errors'] = True
0235        started = []
0236        def detect_start_response(status, headers, exc_info=None):
0237            try:
0238                return start_response(status, headers, exc_info)
0239            except:
0240                raise
0241            else:
0242                started.append(True)
0243        try:
0244            __traceback_supplement__ = Supplement, self, environ
0245            app_iter = self.application(environ, detect_start_response)
0246            try:
0247                return_iter = list(app_iter)
0248                return return_iter
0249            finally:
0250                if hasattr(app_iter, 'close'):
0251                    app_iter.close()
0252        except:
0253            exc_info = sys.exc_info()
0254            for expected in environ.get('paste.expected_exceptions', []):
0255                if isinstance(exc_info[1], expected):
0256                    raise
0257
0258            # Tell the Registry to save its StackedObjectProxies current state
0259            # for later restoration
0260            registry.restorer.save_registry_state(environ)
0261
0262            count = get_debug_count(environ)
0263            view_uri = self.make_view_url(environ, base_path, count)
0264            if not started:
0265                headers = [('content-type', 'text/html')]
0266                headers.append(('X-Debug-URL', view_uri))
0267                start_response('500 Internal Server Error',
0268                               headers,
0269                               exc_info)
0270            environ['wsgi.errors'].write('Debug at: %s\n' % view_uri)
0271
0272            exc_data = collector.collect_exception(*exc_info)
0273            #debug_info = PylonsDebugInfo(count, exc_info, exc_data, base_path,
0274            debug_info = PylonsDebugInfo(count, exc_info, exc_data,
0275                                         get_prefix(environ, warn=False),
0276                                         environ, view_uri, error_template)
0277            assert count not in self.debug_infos
0278            self.debug_infos[count] = debug_info
0279
0280            if self.xmlhttp_key:
0281                get_vars = wsgilib.parse_querystring(environ)
0282                if dict(get_vars).get(self.xmlhttp_key):
0283                    exc_data = collector.collect_exception(*exc_info)
0284                    html = formatter.format_html(
0285                        exc_data, include_hidden_frames=False,
0286                        include_reusable=False, show_extra_data=False)
0287                    return [html]
0288
0289            # @@: it would be nice to deal with bad content types here
0290            return debug_info.content()
0291
0292
0293def myghty_html_data(exc_value):
0294    if hasattr(exc_value, 'htmlformat'):
0295        return exc_value.htmlformat()[333:-14]
0296    if hasattr(exc_value, 'mtrace'):
0297        return exc_value.mtrace.htmlformat()[333:-14]
0298
0299
0300template_error_formatters = [myghty_html_data]
0301
0302
0303try:
0304    import mako.exceptions
0305except ImportError:
0306    pass
0307else:
0308    def mako_html_data(exc_value):
0309        if isinstance(exc_value, (mako.exceptions.CompileException, mako.exceptions.SyntaxException)):
0310            return mako.exceptions.html_error_template().render(full=False,
0311                                                                css=False)
0312
0313    template_error_formatters.insert(0,mako_html_data)
0314
0315
0316class PylonsDebugInfo(DebugInfo):
0317    def __init__(self, counter, exc_info, exc_data, base_path,
0318                 environ, view_uri, error_template):
0319        DebugInfo.__init__(self, counter, exc_info, exc_data, base_path,
0320                           environ, view_uri)
0321        self.error_template = error_template
0322
0323    def content(self):
0324        html, extra_data = format_eval_html(self.exc_data, self.base_path, self.counter)
0325        head_html = (formatter.error_css + formatter.hide_display_js)
0326        head_html += self.eval_javascript(self.counter)
0327        repost_button = make_repost_button(self.environ)
0328        template_data = '<p>No Template information available.</p>'
0329        tab = 'traceback_data'
0330
0331        for formatter_ in template_error_formatters:
0332            result = formatter_(self.exc_value)
0333            if result:
0334                tab = 'template_data'
0335                template_data = result
0336                break
0337
0338        head_html = (error_head_template % {'prefix':self.base_path}) + head_html
0339
0340        traceback_data = error_traceback_template % {
0341            'prefix':self.base_path,
0342            'body':html,
0343            'repost_button': repost_button or '',
0344        }
0345
0346        extra_data = """<h1 class="first"><a name="content"></a>Extra Data</h1>""" +               '\n'.join(extra_data)
0348        page = self.error_template % {
0349            'head': head_html,
0350            'traceback_data': traceback_data,
0351            'extra_data':extra_data,
0352            'template_data':template_data.replace('<h2>',
0353                                              '<h1 class="first">').replace('</h2>',
0354                                                                            '</h1>'),
0355            'set_tab':tab,
0356            'prefix':self.base_path,
0357            }
0358        return [page]
0359
0360    def eval_javascript(self, counter):
0361        base_path = self.base_path + '/_debug'
0362        return (
0363            '<script type="text/javascript" src="%s/mochikit/MochiKit.js">'
0364            '</script>\n'
0365            '<script type="text/javascript" src="%s/media/debug.js">'
0366            '</script>\n'
0367            '<script type="text/javascript">\n'
0368            'debug_base = %r;\n'
0369            'debug_count = %r;\n'
0370            '</script>\n'
0371            % (base_path, base_path, base_path, counter))
0372
0373
0374class EvalHTMLFormatter(HTMLFormatter):
0375
0376    def __init__(self, base_path, counter, **kw):
0377        super(EvalHTMLFormatter, self).__init__(**kw)
0378        self.base_path = base_path
0379        self.counter = counter
0380
0381    def format_source_line(self, filename, frame):
0382        line = formatter.HTMLFormatter.format_source_line(
0383            self, filename, frame)
0384        return (line +
0385                '  <a href="#" class="switch_source" '
0386                'tbid="%s" onClick="return showFrame(this)">&nbsp; &nbsp; '
0387                '<img src="%s/_debug/media/pylons/img/plus.jpg" border=0 width=9 '
0388                'height=9> &nbsp; &nbsp;</a>'
0389                % (frame.tbid, self.base_path))
0390
0391
0392def format_eval_html(exc_data, base_path, counter):
0393    short_formatter = EvalHTMLFormatter(
0394        base_path=base_path,
0395        counter=counter,
0396        include_reusable=False)
0397    short_er, extra_data = short_formatter.format_collected_data(exc_data)
0398    short_text_er = formatter.format_text(exc_data, show_extra_data=False)
0399    long_formatter = EvalHTMLFormatter(
0400        base_path=base_path,
0401        counter=counter,
0402        show_hidden_frames=True,
0403        show_extra_data=False,
0404        include_reusable=False)
0405    long_er, extra_data_none = long_formatter