Skip to content

Commit ab9fd92

Browse files
committed
add cgitb.py & test_cgitb.py from cpython 3.8
Signed-off-by: snowapril <sinjihng@gmail.com>
1 parent fcf5500 commit ab9fd92

File tree

2 files changed

+389
-0
lines changed

2 files changed

+389
-0
lines changed

Lib/cgitb.py

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

Lib/test/test_cgitb.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from test.support import temp_dir
2+
from test.support.script_helper import assert_python_failure
3+
import unittest
4+
import sys
5+
import cgitb
6+
7+
class TestCgitb(unittest.TestCase):
8+
9+
def test_fonts(self):
10+
text = "Hello Robbie!"
11+
self.assertEqual(cgitb.small(text), "<small>{}</small>".format(text))
12+
self.assertEqual(cgitb.strong(text), "<strong>{}</strong>".format(text))
13+
self.assertEqual(cgitb.grey(text),
14+
'<font color="#909090">{}</font>'.format(text))
15+
16+
def test_blanks(self):
17+
self.assertEqual(cgitb.small(""), "")
18+
self.assertEqual(cgitb.strong(""), "")
19+
self.assertEqual(cgitb.grey(""), "")
20+
21+
def test_html(self):
22+
try:
23+
raise ValueError("Hello World")
24+
except ValueError as err:
25+
# If the html was templated we could do a bit more here.
26+
# At least check that we get details on what we just raised.
27+
html = cgitb.html(sys.exc_info())
28+
self.assertIn("ValueError", html)
29+
self.assertIn(str(err), html)
30+
31+
def test_text(self):
32+
try:
33+
raise ValueError("Hello World")
34+
except ValueError as err:
35+
text = cgitb.text(sys.exc_info())
36+
self.assertIn("ValueError", text)
37+
self.assertIn("Hello World", text)
38+
39+
def test_syshook_no_logdir_default_format(self):
40+
with temp_dir() as tracedir:
41+
rc, out, err = assert_python_failure(
42+
'-c',
43+
('import cgitb; cgitb.enable(logdir=%s); '
44+
'raise ValueError("Hello World")') % repr(tracedir))
45+
out = out.decode(sys.getfilesystemencoding())
46+
self.assertIn("ValueError", out)
47+
self.assertIn("Hello World", out)
48+
self.assertIn("<strong>&lt;module&gt;</strong>", out)
49+
# By default we emit HTML markup.
50+
self.assertIn('<p>', out)
51+
self.assertIn('</p>', out)
52+
53+
def test_syshook_no_logdir_text_format(self):
54+
# Issue 12890: we were emitting the <p> tag in text mode.
55+
with temp_dir() as tracedir:
56+
rc, out, err = assert_python_failure(
57+
'-c',
58+
('import cgitb; cgitb.enable(format="text", logdir=%s); '
59+
'raise ValueError("Hello World")') % repr(tracedir))
60+
out = out.decode(sys.getfilesystemencoding())
61+
self.assertIn("ValueError", out)
62+
self.assertIn("Hello World", out)
63+
self.assertNotIn('<p>', out)
64+
self.assertNotIn('</p>', out)
65+
66+
67+
if __name__ == "__main__":
68+
unittest.main()

0 commit comments

Comments
 (0)