~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/cgitb.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""More comprehensive traceback formatting for Python scripts.
 
2
 
 
3
To enable this module, do:
 
4
 
 
5
    import cgitb; cgitb.enable()
 
6
 
 
7
at the top of your script.  The optional arguments to enable() are:
 
8
 
 
9
    display     - if true, tracebacks are displayed in the web browser
 
10
    logdir      - if set, tracebacks are written to files in this directory
 
11
    context     - number of lines of source code to show for each stack frame
 
12
    format      - 'text' or 'html' controls the output format
 
13
 
 
14
By default, tracebacks are displayed but not saved, the context is 5 lines
 
15
and the output format is 'html' (for backwards compatibility with the
 
16
original use of this module)
 
17
 
 
18
Alternatively, if you have caught an exception and want cgitb to display it
 
19
for you, call cgitb.handler().  The optional argument to handler() is a
 
20
3-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
 
21
The default handler displays output as HTML.
 
22
"""
 
23
 
 
24
__author__ = 'Ka-Ping Yee'
 
25
 
 
26
__version__ = '$Revision: 55818 $'
 
27
 
 
28
import sys
 
29
 
 
30
def reset():
 
31
    """Return a string that resets the CGI and browser to a known state."""
 
32
    return '''<!--: spam
 
33
Content-Type: text/html
 
34
 
 
35
<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> -->
 
36
<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> -->
 
37
</font> </font> </font> </script> </object> </blockquote> </pre>
 
38
</table> </table> </table> </table> </table> </font> </font> </font>'''
 
39
 
 
40
__UNDEF__ = []                          # a special sentinel object
 
41
def small(text):
 
42
    if text:
 
43
        return '<small>' + text + '</small>'
 
44
    else:
 
45
        return ''
 
46
 
 
47
def strong(text):
 
48
    if text:
 
49
        return '<strong>' + text + '</strong>'
 
50
    else:
 
51
        return ''
 
52
 
 
53
def grey(text):
 
54
    if text:
 
55
        return '<font color="#909090">' + text + '</font>'
 
56
    else:
 
57
        return ''
 
58
 
 
59
def lookup(name, frame, locals):
 
60
    """Find the value for a given name in the given environment."""
 
61
    if name in locals:
 
62
        return 'local', locals[name]
 
63
    if name in frame.f_globals:
 
64
        return 'global', frame.f_globals[name]
 
65
    if '__builtins__' in frame.f_globals:
 
66
        builtins = frame.f_globals['__builtins__']
 
67
        if type(builtins) is type({}):
 
68
            if name in builtins:
 
69
                return 'builtin', builtins[name]
 
70
        else:
 
71
            if hasattr(builtins, name):
 
72
                return 'builtin', getattr(builtins, name)
 
73
    return None, __UNDEF__
 
74
 
 
75
def scanvars(reader, frame, locals):
 
76
    """Scan one logical line of Python and look up values of variables used."""
 
77
    import tokenize, keyword
 
78
    vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__
 
79
    for ttype, token, start, end, line in tokenize.generate_tokens(reader):
 
80
        if ttype == tokenize.NEWLINE: break
 
81
        if ttype == tokenize.NAME and token not in keyword.kwlist:
 
82
            if lasttoken == '.':
 
83
                if parent is not __UNDEF__:
 
84
                    value = getattr(parent, token, __UNDEF__)
 
85
                    vars.append((prefix + token, prefix, value))
 
86
            else:
 
87
                where, value = lookup(token, frame, locals)
 
88
                vars.append((token, where, value))
 
89
        elif token == '.':
 
90
            prefix += lasttoken + '.'
 
91
            parent = value
 
92
        else:
 
93
            parent, prefix = None, ''
 
94
        lasttoken = token
 
95
    return vars
 
96
 
 
97
def html(einfo, context=5):
 
98
    """Return a nice HTML document describing a given traceback."""
 
99
    import os, time, traceback, linecache, inspect, pydoc
 
100
 
 
101
    etype, evalue, etb = einfo
 
102
    if isinstance(etype, type):
 
103
        etype = etype.__name__
 
104
    pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
 
105
    date = time.ctime(time.time())
 
106
    head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading(
 
107
        '<big><big>%s</big></big>' %
 
108
        strong(pydoc.html.escape(str(etype))),
 
109
        '#ffffff', '#6622aa', pyver + '<br>' + date) + '''
 
110
<p>A problem occurred in a Python script.  Here is the sequence of
 
111
function calls leading up to the error, in the order they occurred.</p>'''
 
112
 
 
113
    indent = '<tt>' + small('&nbsp;' * 5) + '&nbsp;</tt>'
 
114
    frames = []
 
115
    records = inspect.getinnerframes(etb, context)
 
116
    for frame, file, lnum, func, lines, index in records:
 
117
        if file:
 
118
            file = os.path.abspath(file)
 
119
            link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file))
 
120
        else:
 
121
            file = link = '?'
 
122
        args, varargs, varkw, locals = inspect.getargvalues(frame)
 
123
        call = ''
 
124
        if func != '?':
 
125
            call = 'in ' + strong(func) + \
 
126
                inspect.formatargvalues(args, varargs, varkw, locals,
 
127
                    formatvalue=lambda value: '=' + pydoc.html.repr(value))
 
128
 
 
129
        highlight = {}
 
130
        def reader(lnum=[lnum]):
 
131
            highlight[lnum[0]] = 1
 
132
            try: return linecache.getline(file, lnum[0])
 
133
            finally: lnum[0] += 1
 
134
        vars = scanvars(reader, frame, locals)
 
135
 
 
136
        rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' %
 
137
                ('<big>&nbsp;</big>', link, call)]
 
138
        if index is not None:
 
139
            i = lnum - index
 
140
            for line in lines:
 
141
                num = small('&nbsp;' * (5-len(str(i))) + str(i)) + '&nbsp;'
 
142
                line = '<tt>%s%s</tt>' % (num, pydoc.html.preformat(line))
 
143
                if i in highlight:
 
144
                    rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line)
 
145
                else:
 
146
                    rows.append('<tr><td>%s</td></tr>' % grey(line))
 
147
                i += 1
 
148
 
 
149
        done, dump = {}, []
 
150
        for name, where, value in vars:
 
151
            if name in done: continue
 
152
            done[name] = 1
 
153
            if value is not __UNDEF__:
 
154
                if where in ('global', 'builtin'):
 
155
                    name = ('<em>%s</em> ' % where) + strong(name)
 
156
                elif where == 'local':
 
157
                    name = strong(name)
 
158
                else:
 
159
                    name = where + strong(name.split('.')[-1])
 
160
                dump.append('%s&nbsp;= %s' % (name, pydoc.html.repr(value)))
 
161
            else:
 
162
                dump.append(name + ' <em>undefined</em>')
 
163
 
 
164
        rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump))))
 
165
        frames.append('''
 
166
<table width="100%%" cellspacing=0 cellpadding=0 border=0>
 
167
%s</table>''' % '\n'.join(rows))
 
168
 
 
169
    exception = ['<p>%s: %s' % (strong(pydoc.html.escape(str(etype))),
 
170
                                pydoc.html.escape(str(evalue)))]
 
171
    for name in dir(evalue):
 
172
        if name[:1] == '_': continue
 
173
        value = pydoc.html.repr(getattr(evalue, name))
 
174
        exception.append('\n<br>%s%s&nbsp;=\n%s' % (indent, name, value))
 
175
 
 
176
    import traceback
 
177
    return head + ''.join(frames) + ''.join(exception) + '''
 
178
 
 
179
 
 
180
<!-- The above is a description of an error in a Python program, formatted
 
181
     for a Web browser because the 'cgitb' module was enabled.  In case you
 
182
     are not reading this in a Web browser, here is the original traceback:
 
183
 
 
184
%s
 
185
-->
 
186
''' % pydoc.html.escape(
 
187
          ''.join(traceback.format_exception(etype, evalue, etb)))
 
188
 
 
189
def text(einfo, context=5):
 
190
    """Return a plain text document describing a given traceback."""
 
191
    import os, time, traceback, linecache, inspect, pydoc
 
192
 
 
193
    etype, evalue, etb = einfo
 
194
    if isinstance(etype, type):
 
195
        etype = etype.__name__
 
196
    pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
 
197
    date = time.ctime(time.time())
 
198
    head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + '''
 
199
A problem occurred in a Python script.  Here is the sequence of
 
200
function calls leading up to the error, in the order they occurred.
 
201
'''
 
202
 
 
203
    frames = []
 
204
    records = inspect.getinnerframes(etb, context)
 
205
    for frame, file, lnum, func, lines, index in records:
 
206
        file = file and os.path.abspath(file) or '?'
 
207
        args, varargs, varkw, locals = inspect.getargvalues(frame)
 
208
        call = ''
 
209
        if func != '?':
 
210
            call = 'in ' + func + \
 
211
                inspect.formatargvalues(args, varargs, varkw, locals,
 
212
                    formatvalue=lambda value: '=' + pydoc.text.repr(value))
 
213
 
 
214
        highlight = {}
 
215
        def reader(lnum=[lnum]):
 
216
            highlight[lnum[0]] = 1
 
217
            try: return linecache.getline(file, lnum[0])
 
218
            finally: lnum[0] += 1
 
219
        vars = scanvars(reader, frame, locals)
 
220
 
 
221
        rows = [' %s %s' % (file, call)]
 
222
        if index is not None:
 
223
            i = lnum - index
 
224
            for line in lines:
 
225
                num = '%5d ' % i
 
226
                rows.append(num+line.rstrip())
 
227
                i += 1
 
228
 
 
229
        done, dump = {}, []
 
230
        for name, where, value in vars:
 
231
            if name in done: continue
 
232
            done[name] = 1
 
233
            if value is not __UNDEF__:
 
234
                if where == 'global': name = 'global ' + name
 
235
                elif where != 'local': name = where + name.split('.')[-1]
 
236
                dump.append('%s = %s' % (name, pydoc.text.repr(value)))
 
237
            else:
 
238
                dump.append(name + ' undefined')
 
239
 
 
240
        rows.append('\n'.join(dump))
 
241
        frames.append('\n%s\n' % '\n'.join(rows))
 
242
 
 
243
    exception = ['%s: %s' % (str(etype), str(evalue))]
 
244
    for name in dir(evalue):
 
245
        value = pydoc.text.repr(getattr(evalue, name))
 
246
        exception.append('\n%s%s = %s' % (" "*4, name, value))
 
247
 
 
248
    import traceback
 
249
    return head + ''.join(frames) + ''.join(exception) + '''
 
250
 
 
251
The above is a description of an error in a Python program.  Here is
 
252
the original traceback:
 
253
 
 
254
%s
 
255
''' % ''.join(traceback.format_exception(etype, evalue, etb))
 
256
 
 
257
class Hook:
 
258
    """A hook to replace sys.excepthook that shows tracebacks in HTML."""
 
259
 
 
260
    def __init__(self, display=1, logdir=None, context=5, file=None,
 
261
                 format="html"):
 
262
        self.display = display          # send tracebacks to browser if true
 
263
        self.logdir = logdir            # log tracebacks to files if not None
 
264
        self.context = context          # number of source code lines per frame
 
265
        self.file = file or sys.stdout  # place to send the output
 
266
        self.format = format
 
267
 
 
268
    def __call__(self, etype, evalue, etb):
 
269
        self.handle((etype, evalue, etb))
 
270
 
 
271
    def handle(self, info=None):
 
272
        info = info or sys.exc_info()
 
273
        if self.format == "html":
 
274
            self.file.write(reset())
 
275
 
 
276
        formatter = (self.format=="html") and html or text
 
277
        plain = False
 
278
        try:
 
279
            doc = formatter(info, self.context)
 
280
        except:                         # just in case something goes wrong
 
281
            import traceback
 
282
            doc = ''.join(traceback.format_exception(*info))
 
283
            plain = True
 
284
 
 
285
        if self.display:
 
286
            if plain:
 
287
                doc = doc.replace('&', '&amp;').replace('<', '&lt;')
 
288
                self.file.write('<pre>' + doc + '</pre>\n')
 
289
            else:
 
290
                self.file.write(doc + '\n')
 
291
        else:
 
292
            self.file.write('<p>A problem occurred in a Python script.\n')
 
293
 
 
294
        if self.logdir is not None:
 
295
            import os, tempfile
 
296
            suffix = ['.txt', '.html'][self.format=="html"]
 
297
            (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
 
298
            try:
 
299
                file = os.fdopen(fd, 'w')
 
300
                file.write(doc)
 
301
                file.close()
 
302
                msg = '<p> %s contains the description of this error.' % path
 
303
            except:
 
304
                msg = '<p> Tried to save traceback to %s, but failed.' % path
 
305
            self.file.write(msg + '\n')
 
306
        try:
 
307
            self.file.flush()
 
308
        except: pass
 
309
 
 
310
handler = Hook().handle
 
311
def enable(display=1, logdir=None, context=5, format="html"):
 
312
    """Install an exception handler that formats tracebacks as HTML.
 
313
 
 
314
    The optional argument 'display' can be set to 0 to suppress sending the
 
315
    traceback to the browser, and 'logdir' can be set to a directory to cause
 
316
    tracebacks to be written to files there."""
 
317
    sys.excepthook = Hook(display=display, logdir=logdir,
 
318
                          context=context, format=format)