[Zope-Checkins] CVS: Zope3/lib/python/Zope/Exceptions - ExceptionFormatter.py:1.1.2.1 ITracebackSupplement.py:1.1.2.1

Shane Hathaway shane@cvs.zope.org
Thu, 14 Mar 2002 16:41:45 -0500


Update of /cvs-repository/Zope3/lib/python/Zope/Exceptions
In directory cvs.zope.org:/tmp/cvs-serv12053

Added Files:
      Tag: Zope-3x-branch
	ExceptionFormatter.py ITracebackSupplement.py 
Log Message:
Added the custom exception formatter, with tests.  Differences from the
traceback module:

- It prints module names instead of source file names, which results in
  shorter lines and avoids disclosing filesystem paths.

- It can print application-specific traceback information.

- It can output in HTML format, which will make it possible to include
  direct links from the traceback to the templates and scripts where the
  exception occurred, without wrapping exceptions.  (Wrapping exceptions
  tends to obscure information.)


=== Added File Zope3/lib/python/Zope/Exceptions/ExceptionFormatter.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""An exception formatter that shows traceback supplements and traceback info,
optionally in HTML.

$Id: ExceptionFormatter.py,v 1.1.2.1 2002/03/14 21:41:45 shane Exp $
"""

import sys
import cgi


DEBUG_EXCEPTION_FORMATTER = 1


class TextExceptionFormatter:

    line_sep = '\n'
    show_revisions = 0

    def __init__(self, limit=None):
        self.limit = limit

    def escape(self, s):
        return s

    def getPrefix(self):
        return 'Traceback (innermost last):'

    def getLimit(self):
        limit = self.limit
        if limit is None:
            limit = getattr(sys, 'tracebacklimit', None)
        return limit

    def getRevision(self, globals):
        if not self.show_revisions:
            return None
        revision = globals.get('__revision__', None)
        if revision is None:
            # Incorrect but commonly used spelling
            revision = globals.get('__version__', None)

        if revision is not None:
            try:
                revision = str(revision).strip()
            except:
                revision = '???'
        return revision

    def getObjectPath(self, o):
        """Returns an informal path to an object.
        """
        try:
            from Zope.ContextWrapper import wrapper
        except ImportError:
            # Not available.
            return None

        res = []
        while o is not None:
            d = wrapper.getdict(o)
            if d:
                name = d.get('name', None)
                if name:
                    res.append(name)
            o = wrapper.getcontext(o)

        res.reverse()
        return res

    def formatSupplementLine(self, line):
        return '   - %s' % line

    def formatObjectInfo(self, mo):
        """Returns a line about the object.
        
        Someday this should be replaced with code that
        produces a URL for editing the object.
        """
        result = []
        class_ = getattr(mo, '__class__', None)
        ob_type = getattr(class_, '__name__', 'Object')
        s = repr(mo)
        path = self.getObjectPath(mo)
        if path:
            s = s + ' at ' + '/'.join(path)
        result.append(self.formatSupplementLine(s))
        return result

    def formatSupplement(self, supplement, tb):
        result = []
        fmtLine = self.formatSupplementLine

        mo = getattr(supplement, 'manageable_object', None)
        if mo is not None:
            result.extend(self.formatObjectInfo(mo))

        line = getattr(supplement, 'line', 0)
        if line == -1:
            line = tb.tb_lineno
        col = getattr(supplement, 'column', -1)
        if line:
            if col is not None and col >= 0:
                result.append(fmtLine('Line %s, Column %s' % (
                    line, col)))
            else:
                result.append(fmtLine('Line %s' % line))
        elif col is not None and col >= 0:
            result.append(fmtLine('Column %s' % col))

        expr = getattr(supplement, 'expression', None)
        if expr:
            result.append(fmtLine('Expression: %s' % expr))

        warnings = getattr(supplement, 'warnings', None)
        if warnings:
            for warning in warnings:
                result.append(fmtLine('Warning: %s' % warning))

        getInfo = getattr(supplement, 'getInfo', None)
        if getInfo is not None:
            extra = getInfo()
            if extra:
                result.append(extra)
        return result

    def formatTracebackInfo(self, tbi):
        return self.formatSupplementLine('__traceback_info__: %s' % tbi)

    def formatLine(self, tb):
        f = tb.tb_frame
        lineno = tb.tb_lineno
        co = f.f_code
        filename = co.co_filename
        name = co.co_name
        locals = f.f_locals
        globals = f.f_globals
        modname = globals.get('__name__', filename)

        s = '  Module %s, line %d' % (modname, lineno)

        revision = self.getRevision(globals)
        if revision:
            s = s + ', rev. %s' % revision

        s = s + ', in %s' % name

        result = []
        result.append(self.escape(s))

        try:
            # Output a traceback supplement, if any.
            if locals.has_key('__traceback_supplement__'):
                # Use the supplement defined in the function.
                tbs = locals['__traceback_supplement__']
            elif globals.has_key('__traceback_supplement__'):
                # Use the supplement defined in the module.
                # This is used by Scripts (Python).
                tbs = globals['__traceback_supplement__']
            else:
                tbs = None
            if tbs is not None:
                factory = tbs[0]
                args = tbs[1:]
                supp = factory(*args)
                result.extend(self.formatSupplement(supp, tb))
        except:
            if DEBUG_EXCEPTION_FORMATTER:
                import traceback
                traceback.print_exc()
            # else just swallow the exception.

        try:
            tbi = locals.get('__traceback_info__', None)
            if tbi is not None:
                result.append(self.formatTracebackInfo(tbi))
        except:
            pass

        return self.line_sep.join(result)

    def formatExceptionOnly(self, etype, value):
        import traceback
        return self.line_sep.join(
            traceback.format_exception_only(etype, value))

    def formatLastLine(self, exc_line):
        return self.escape(exc_line)

    def formatException(self, etype, value, tb):
        # The next line provides a way to detect recursion.
        __exception_formatter__ = 1
        result = [self.getPrefix()]
        limit = self.getLimit()
        n = 0
        while tb is not None and (limit is None or n < limit):
            if tb.tb_frame.f_locals.get('__exception_formatter__'):
                # Stop recursion.
                result.append('(Recursive formatException() stopped)')
                break
            line = self.formatLine(tb)
            result.append(line)
            tb = tb.tb_next
            n = n + 1
        exc_line = self.formatExceptionOnly(etype, value)
        result.append(self.formatLastLine(exc_line))
        return result



class HTMLExceptionFormatter (TextExceptionFormatter):

    line_sep = '<br />\r\n'

    def escape(self, s):
        return cgi.escape(s)

    def getPrefix(self):
        return '<p>Traceback (innermost last):\r\n<ul>'

    def formatSupplementLine(self, line):
        return '<b>%s</b>' % self.escape(str(line))

    def formatTracebackInfo(self, tbi):
        s = self.escape(str(tbi))
        s = s.replace('\n', self.line_sep)
        return '__traceback_info__: %s' % s

    def formatLine(self, tb):
        line = TextExceptionFormatter.formatLine(self, tb)
        return '<li>%s</li>' % line

    def formatLastLine(self, exc_line):
        return '</ul>%s</p>' % exc_line



limit = 200

if hasattr(sys, 'tracebacklimit'):
    limit = min(limit, sys.tracebacklimit)

text_formatter = TextExceptionFormatter(limit)
html_formatter = HTMLExceptionFormatter(limit)


def format_exception(t, v, tb, as_html=0):
    if as_html:
        fmt = html_formatter
    else:
        fmt = text_formatter
    return fmt.formatException(t, v, tb)



=== Added File Zope3/lib/python/Zope/Exceptions/ITracebackSupplement.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""ITracebackSupplement interface definition.

$Id: ITracebackSupplement.py,v 1.1.2.1 2002/03/14 21:41:45 shane Exp $
"""


from Interface import Interface
from Interface.Attribute import Attribute

class ITracebackSupplement(Interface):
    """Provides valuable information to supplement an exception traceback.

    The interface is geared toward providing meaningful feedback when
    exceptions occur in user code written in mini-languages like
    Zope page templates and restricted Python scripts.
    """

    manageable_object = Attribute(
        'manageable_object',
        """Optional.  Set to the script where the exception occurred.

        Normally this generates a URL in the traceback that the user
        can visit to manage the object.  Set to None if unknown or
        not available.
        """
        )

    line = Attribute(
        'line',
        """Optional.  Set to the line number (>=1) where the exception
        occurred.

        Set to 0 or None if the line number is unknown.
        """
        )

    column = Attribute(
        'column',
        """Optional.  Set to the column offset (>=0) where the exception
        occurred.

        Set to None if the column number is unknown.
        """
        )

    expression = Attribute(
        'expression',
        """Optional.  Set to the expression that was being evaluated.

        Set to None if not available or not applicable.
        """
        )

    warnings = Attribute(
        'warnings',
        """Optional.  Set to a sequence of warning messages.

        Set to None if not available, not applicable, or if the exception
        itself provides enough information.
        """
        )


    def getInfo(as_html=0):
        """Optional.  Returns a string containing any other useful info.

        If as_html is set, the implementation must HTML-quote the result
        (normally using cgi.escape()).  Returns None to provide no
        extra info.
        """