~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# Copyright 2011 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Replacement for some standard library traceback module functions.

These honor traceback supplements as defined in zope.exceptions.
"""

__metaclass__ = type
__all__ = [
    'extract_stack',
    'extract_tb',
    'format_list',
    'print_list',
    'print_stack',
    ]

import linecache
import sys
import traceback


DEBUG_EXCEPTION_FORMATTER = False
EXPLOSIVE_ERRORS = (SystemExit, MemoryError, KeyboardInterrupt)


def _try_except(callable, *args, **kwargs):
    try:
        return callable(*args, **kwargs)
    except EXPLOSIVE_ERRORS:
        raise
    except:
        if DEBUG_EXCEPTION_FORMATTER:
            traceback.print_exc()
    # return None


def _get_frame(f):
    "if the frame is None, make one."
    if f is None:
        try:
            raise ZeroDivisionError
        except ZeroDivisionError:
            f = sys.exc_info()[2].tb_frame.f_back.f_back
    return f


def _fmt(string):
    "Return the string as deemed suitable for the extra information."
    return '   - %s' % string


def print_list(extracted_list, file=None):
    """Print the list of tuples as returned by extract_tb() or
    extract_stack() as a formatted stack trace to the given file."""
    if file is None:
        file = sys.stderr
    for line in format_list(extracted_list):
        file.write(line)


def format_list(extracted_list):
    """Format a list of traceback entry tuples for printing.

    Given a list of tuples as returned by extract_tb() or
    extract_stack(), return a list of strings ready for printing.
    Each string in the resulting list corresponds to the item with the
    same index in the argument list.  Each string ends in a newline;
    the strings may contain internal newlines as well, for those items
    whose source text line or supplement or info are not None.
    """
    list = []
    for filename, lineno, name, line, modname, supp, info in extracted_list:
        item = []
        item.append(
               '  File "%s", line %d, in %s' % (filename, lineno, name))
        if line:
            item.append('    %s' % line.strip())
        # The "supp" and "info" bits are adapted from zope.exceptions.
        try:
            if supp:
                if supp['source_url']:
                    item.append(_fmt(supp['source_url']))
                if supp['line']:
                    if supp['column']:
                        item.append(
                            _fmt('Line %(line)s, Column %(column)s' % supp))
                    else:
                        item.append(_fmt('Line %(line)s' % supp))
                elif supp['column']:
                    item.append(_fmt('Column %(column)s' % supp))
                if supp['expression']:
                    item.append(_fmt('Expression: %(expression)s' % supp))
                if supp['warnings']:
                    for warning in supp['warnings']:
                        item.append(_fmt('Warning: %s' % warning))
                if supp['extra']:
                    item.append(supp['extra'])  # We do not include a prefix.
            if info:
                item.append(_fmt(info))
        except EXPLOSIVE_ERRORS:
            raise
        except:
            # The values above may not stringify properly, or who knows what
            # else.  Be defensive.
            if DEBUG_EXCEPTION_FORMATTER:
                traceback.print_exc()
            # else just swallow the exception.
        item.append('')  # This gives us a trailing newline.
        list.append('\n'.join(item))
    return list


def print_stack(f=None, limit=None, file=None):
    """Print a stack trace from its invocation point.

    The optional 'f' argument can be used to specify an alternate
    stack frame at which to start. The optional 'limit' and 'file'
    arguments have the same meaning as for print_exception().
    """
    print_list(extract_stack(_get_frame(f), limit), file)


def _get_limit(limit):
    "Return the limit or the globally-set limit, if any."
    if limit is None:
        # stdlib uses hasattr here.
        if hasattr(sys, 'tracebacklimit'):
            limit = sys.tracebacklimit
    return limit


def _get_frame_data(f, lineno):
    "Given a frame and a lineno, return data for each item of extract_*."
    # Adapted from zope.exceptions.
    try_except = _try_except  # This is a micro-optimization.
    modname = f.f_globals.get('__name__')
    # Output a traceback supplement, if any.
    supplement_dict = info = None
    if '__traceback_supplement__' in f.f_locals:
        # Use the supplement defined in the function.
        tbs = f.f_locals['__traceback_supplement__']
    elif '__traceback_supplement__' in f.f_globals:
        # Use the supplement defined in the module.
        tbs = f.f_globals['__traceback_supplement__']
    else:
        tbs = None
    if tbs is not None:
        factory = tbs[0]
        args = tbs[1:]
        supplement = try_except(factory, *args)
        if supplement is not None:
            # It might be nice if supplements could be dicts, for simplicity.
            # Historically, though, they are objects.
            # We will turn the supplement into a dict of strings, so that we
            # have "getInfo" pre-processed and so that we are not holding on
            # to anything from the frame.
            extra = None
            getInfo = getattr(supplement, 'getInfo', None)
            if getInfo is not None:
                extra = try_except(getInfo)
                extra = try_except(str, extra) if extra is not None else None
            warnings = []
            # The outer try-except is for the iteration.
            try:
                for warning in getattr(supplement, 'warnings', ()):
                    if warning is not None:
                        warning = try_except(str, warning)
                    if warning is not None:
                        warnings.append(warning)
            except EXPLOSIVE_ERRORS:
                raise
            except:
                if DEBUG_EXCEPTION_FORMATTER:
                    traceback.print_exc()
            supplement_dict = dict(warnings=warnings, extra=extra)
            for key in ('source_url', 'line', 'column', 'expression'):
                value = getattr(supplement, key, None)
                if value is not None:
                    value = try_except(str, value)
                supplement_dict[key] = value
    info = f.f_locals.get('__traceback_info__', None)
    info = try_except(str, info) if info is not None else None
    # End part adapted from zope.exceptions.
    co = f.f_code
    filename = co.co_filename
    name = co.co_name
    linecache.checkcache(filename)
    line = linecache.getline(filename, lineno, f.f_globals)
    if line:
        line = line.strip()
    else:
        line = None
    return (filename, lineno, name, line, modname, supplement_dict, info)


def extract_stack(f=None, limit=None):
    """Extract the raw traceback from the current stack frame.

    The return value has the same format as for extract_tb().  The optional
    'f' and 'limit' arguments have the same meaning as for print_stack().
    Each item in the list is a septuple (filename, line number, function name,
    text, module name, optional supplement dict, optional info string), and
    the entries are in order from oldest to newest stack frame.
    """
    f = _get_frame(f)
    limit = _get_limit(limit)
    list = []
    n = 0
    get_frame_data = _get_frame_data  # This is a micro-optimization.
    while f is not None and (limit is None or n < limit):
        list.append(get_frame_data(f, f.f_lineno))
        f = f.f_back
        n = n + 1
    list.reverse()
    return list


def extract_tb(tb, limit=None):
    """Return list of up to limit pre-processed entries from traceback.

    This is useful for alternate formatting of stack traces.  If 'limit' is
    omitted or None, all entries are extracted.  A pre-processed stack trace
    entry is a sextuple (filename, line number, function name, text, module
    name, optional supplement dict, optional info string) representing the
    information that is printed for a stack trace.  The text is a string with
    leading and trailing whitespace stripped; if the source is not available
    it is None. The supplement dict has keys 'source_url', 'line', 'column',
    'expression', 'warnings' (an iterable), and 'extra', any of which may be
    None.
    """
    # zope.exceptions handles tracebacks.  This function is implemented just
    # to show how this module's patterns might be extended to tracebacks.
    limit = _get_limit(limit)
    list = []
    n = 0
    get_frame_data = _get_frame_data  # This is a micro-optimization.
    while tb is not None and (limit is None or n < limit):
        list.append(get_frame_data(tb.tb_frame, tb.tb_lineno))
        tb = tb.tb_next
        n = n + 1
    return list