#10961: fix exception handling in new pydoc server code.

Patch by Ron Adam, reviewed by Eric Araujo.
This commit is contained in:
Georg Brandl 2011-01-30 08:37:19 +00:00
parent ce227e3518
commit d2f3857c40
3 changed files with 108 additions and 99 deletions

View file

@ -70,7 +70,7 @@ import time
import warnings import warnings
from collections import deque from collections import deque
from reprlib import Repr from reprlib import Repr
from traceback import extract_tb from traceback import extract_tb, format_exception_only
# --------------------------------------------------------- common routines # --------------------------------------------------------- common routines
@ -1882,7 +1882,9 @@ module "pydoc_data.topics" could not be found.
def _gettopic(self, topic, more_xrefs=''): def _gettopic(self, topic, more_xrefs=''):
"""Return unbuffered tuple of (topic, xrefs). """Return unbuffered tuple of (topic, xrefs).
If an error occurs, topic is the error message, and xrefs is ''. If an error occurs here, the exception is caught and displayed by
the url handler.
This function duplicates the showtopic method but returns its This function duplicates the showtopic method but returns its
result directly so it can be formatted for display in an html page. result directly so it can be formatted for display in an html page.
""" """
@ -1895,14 +1897,11 @@ module "pydoc_data.topics" could not be found.
''' , '') ''' , '')
target = self.topics.get(topic, self.keywords.get(topic)) target = self.topics.get(topic, self.keywords.get(topic))
if not target: if not target:
return 'no documentation found for %r' % topic, '' raise ValueError('could not find topic')
if isinstance(target, str): if isinstance(target, str):
return self._gettopic(target, more_xrefs) return self._gettopic(target, more_xrefs)
label, xrefs = target label, xrefs = target
try:
doc = pydoc_data.topics.topics[label] doc = pydoc_data.topics.topics[label]
except KeyError:
return 'no documentation found for %r' % topic, ''
if more_xrefs: if more_xrefs:
xrefs = (xrefs or '') + ' ' + more_xrefs xrefs = (xrefs or '') + ' ' + more_xrefs
return doc, xrefs return doc, xrefs
@ -2479,10 +2478,10 @@ def _url_handler(url, content_type="text/html"):
css_path) css_path)
return '''\ return '''\
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: %s</title> <html><head><title>Pydoc: %s</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
%s</head><body bgcolor="#f0f0f8">%s %s</head><body bgcolor="#f0f0f8">%s<div style="clear:both;padding-top:.5em;">%s</div>
</body></html>''' % (title, css_link, contents) </body></html>''' % (title, css_link, html_navbar(), contents)
def filelink(self, url, path): def filelink(self, url, path):
return '<a href="getfile?key=%s">%s</a>' % (url, path) return '<a href="getfile?key=%s">%s</a>' % (url, path)
@ -2491,12 +2490,12 @@ def _url_handler(url, content_type="text/html"):
html = _HTMLDoc() html = _HTMLDoc()
def html_navbar(): def html_navbar():
version = "%s [%s, %s]" % (platform.python_version(), version = html.escape("%s [%s, %s]" % (platform.python_version(),
platform.python_build()[0], platform.python_build()[0],
platform.python_compiler()) platform.python_compiler()))
return """ return """
<div style='float:left'> <div style='float:left'>
Python %s<br>%s<br><br> Python %s<br>%s
</div> </div>
<div style='float:right'> <div style='float:right'>
<div style='text-align:center'> <div style='text-align:center'>
@ -2505,19 +2504,17 @@ def _url_handler(url, content_type="text/html"):
: <a href="keywords.html">Keywords</a> : <a href="keywords.html">Keywords</a>
</div> </div>
<div> <div>
<form action="get" style='float:left'> <form action="get" style='display:inline;'>
<input type=text name=key size=15> <input type=text name=key size=15>
<input type=submit value="Get"> <input type=submit value="Get">
&nbsp;&nbsp;&nbsp; </form>&nbsp;
</form> <form action="search" style='display:inline;'>
<form action="search" style='float:right'>
<input type=text name=key size=15> <input type=text name=key size=15>
<input type=submit value="Search"> <input type=submit value="Search">
</form> </form>
</div> </div>
</div> </div>
<div clear='all'>&nbsp;</div> """ % (version, html.escape(platform.platform(terse=True)))
""" % (version, platform.platform(terse=True))
def html_index(): def html_index():
"""Module Index page.""" """Module Index page."""
@ -2589,7 +2586,7 @@ def _url_handler(url, content_type="text/html"):
"""Index of topic texts available.""" """Index of topic texts available."""
def bltinlink(name): def bltinlink(name):
return '<a href="%s.html">%s</a>' % (name, name) return '<a href="topic?key=%s">%s</a>' % (name, name)
heading = html.heading( heading = html.heading(
'<big><big><strong>INDEX</strong></big></big>', '<big><big><strong>INDEX</strong></big></big>',
@ -2609,7 +2606,7 @@ def _url_handler(url, content_type="text/html"):
names = sorted(Helper.keywords.keys()) names = sorted(Helper.keywords.keys())
def bltinlink(name): def bltinlink(name):
return '<a href="%s.html">%s</a>' % (name, name) return '<a href="topic?key=%s">%s</a>' % (name, name)
contents = html.multicolumn(names, bltinlink) contents = html.multicolumn(names, bltinlink)
contents = heading + html.bigsection( contents = heading + html.bigsection(
@ -2628,12 +2625,13 @@ def _url_handler(url, content_type="text/html"):
heading = html.heading( heading = html.heading(
'<big><big><strong>%s</strong></big></big>' % title, '<big><big><strong>%s</strong></big></big>' % title,
'#ffffff', '#7799ee') '#ffffff', '#7799ee')
contents = '<pre>%s</pre>' % contents contents = '<pre>%s</pre>' % html.markup(contents)
contents = html.bigsection(topic , '#ffffff','#ee77aa', contents) contents = html.bigsection(topic , '#ffffff','#ee77aa', contents)
if xrefs:
xrefs = sorted(xrefs.split()) xrefs = sorted(xrefs.split())
def bltinlink(name): def bltinlink(name):
return '<a href="%s.html">%s</a>' % (name, name) return '<a href="topic?key=%s">%s</a>' % (name, name)
xrefs = html.multicolumn(xrefs, bltinlink) xrefs = html.multicolumn(xrefs, bltinlink)
xrefs = html.section('Related help topics: ', xrefs = html.section('Related help topics: ',
@ -2641,66 +2639,77 @@ def _url_handler(url, content_type="text/html"):
return ('%s %s' % (title, topic), return ('%s %s' % (title, topic),
''.join((heading, contents, xrefs))) ''.join((heading, contents, xrefs)))
def html_error(url): def html_getobj(url):
obj = locate(url, forceload=1)
if obj is None and url != 'None':
raise ValueError('could not find object')
title = describe(obj)
content = html.document(obj, url)
return title, content
def html_error(url, exc):
heading = html.heading( heading = html.heading(
'<big><big><strong>Error</strong></big></big>', '<big><big><strong>Error</strong></big></big>',
'#ffffff', '#ee0000') '#ffffff', '#7799ee')
return heading + url contents = '<br>'.join(html.escape(line) for line in
format_exception_only(type(exc), exc))
contents = heading + html.bigsection(url, '#ffffff', '#bb0000',
contents)
return "Error - %s" % url, contents
def get_html_page(url): def get_html_page(url):
"""Generate an HTML page for url.""" """Generate an HTML page for url."""
complete_url = url
if url.endswith('.html'): if url.endswith('.html'):
url = url[:-5] url = url[:-5]
if url.startswith('/'): try:
url = url[1:] if url in ("", "index"):
if url.startswith("get?key="): title, content = html_index()
url = url[8:]
title = url
contents = ''
if url in ("", ".", "index"):
title, contents = html_index()
elif url == "topics": elif url == "topics":
title, contents = html_topics() title, content = html_topics()
elif url == "keywords": elif url == "keywords":
title, contents = html_keywords() title, content = html_keywords()
elif url.startswith("search?key="): elif '=' in url:
title, contents = html_search(url[11:]) op, _, url = url.partition('=')
elif url.startswith("getfile?key="): if op == "search?key":
url = url[12:] title, content = html_search(url)
elif op == "getfile?key":
title, content = html_getfile(url)
elif op == "topic?key":
# try topics first, then objects.
try: try:
title, contents = html_getfile(url) title, content = html_topicpage(url)
except IOError: except ValueError:
contents = html_error('could not read file %r' % url) title, content = html_getobj(url)
title = 'Read Error' elif op == "get?key":
# try objects first, then topics.
if url in ("", "index"):
title, content = html_index()
else: else:
obj = None
try: try:
obj = locate(url, forceload=1) title, content = html_getobj(url)
except ErrorDuringImport as value: except ValueError:
contents = html.escape(str(value)) title, content = html_topicpage(url)
if obj:
title = describe(obj)
contents = html.document(obj, url)
elif url in Helper.keywords or url in Helper.topics:
title, contents = html_topicpage(url)
else: else:
contents = html_error( raise ValueError('bad pydoc url')
'no Python documentation found for %r' % url) else:
title = 'Error' title, content = html_getobj(url)
return html.page(title, html_navbar() + contents) except Exception as exc:
# Catch any errors and display them in an error page.
title, content = html_error(complete_url, exc)
return html.page(title, content)
if url.startswith('/'): if url.startswith('/'):
url = url[1:] url = url[1:]
if content_type == 'text/css': if content_type == 'text/css':
path_here = os.path.dirname(os.path.realpath(__file__)) path_here = os.path.dirname(os.path.realpath(__file__))
try: css_path = os.path.join(path_here, url)
with open(os.path.join(path_here, url)) as fp: with open(css_path) as fp:
return ''.join(fp.readlines()) return ''.join(fp.readlines())
except IOError:
return 'Error: can not open css file %r' % url
elif content_type == 'text/html': elif content_type == 'text/html':
return get_html_page(url) return get_html_page(url)
return 'Error: unknown content type %r' % content_type # Errors outside the url handler are caught by the server.
raise TypeError('unknown content type %r for url %s' % (content_type, url))
def browse(port=0, *, open_browser=True): def browse(port=0, *, open_browser=True):

View file

@ -244,7 +244,7 @@ def get_html_title(text):
return title return title
class PyDocDocTest(unittest.TestCase): class PydocDocTest(unittest.TestCase):
@unittest.skipIf(sys.flags.optimize >= 2, @unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above") "Docstrings are omitted with -O2 and above")
@ -392,7 +392,7 @@ class TestDescriptions(unittest.TestCase):
self.assertIn(expected, pydoc.render_doc(c)) self.assertIn(expected, pydoc.render_doc(c))
class PyDocServerTest(unittest.TestCase): class PydocServerTest(unittest.TestCase):
"""Tests for pydoc._start_server""" """Tests for pydoc._start_server"""
def test_server(self): def test_server(self):
@ -415,34 +415,31 @@ class PyDocServerTest(unittest.TestCase):
self.assertEqual(serverthread.error, None) self.assertEqual(serverthread.error, None)
class PyDocUrlHandlerTest(unittest.TestCase): class PydocUrlHandlerTest(unittest.TestCase):
"""Tests for pydoc._url_handler""" """Tests for pydoc._url_handler"""
def test_content_type_err(self): def test_content_type_err(self):
err = 'Error: unknown content type '
f = pydoc._url_handler f = pydoc._url_handler
result = f("", "") self.assertRaises(TypeError, f, 'A', '')
self.assertEqual(result, err + "''") self.assertRaises(TypeError, f, 'B', 'foobar')
result = f("", "foobar")
self.assertEqual(result, err + "'foobar'")
def test_url_requests(self): def test_url_requests(self):
# Test for the correct title in the html pages returned. # Test for the correct title in the html pages returned.
# This tests the different parts of the URL handler without # This tests the different parts of the URL handler without
# getting too picky about the exact html. # getting too picky about the exact html.
requests = [ requests = [
("", "Python: Index of Modules"), ("", "Pydoc: Index of Modules"),
("get?key=", "Python: Index of Modules"), ("get?key=", "Pydoc: Index of Modules"),
("index", "Python: Index of Modules"), ("index", "Pydoc: Index of Modules"),
("topics", "Python: Topics"), ("topics", "Pydoc: Topics"),
("keywords", "Python: Keywords"), ("keywords", "Pydoc: Keywords"),
("pydoc", "Python: module pydoc"), ("pydoc", "Pydoc: module pydoc"),
("get?key=pydoc", "Python: module pydoc"), ("get?key=pydoc", "Pydoc: module pydoc"),
("search?key=pydoc", "Python: Search Results"), ("search?key=pydoc", "Pydoc: Search Results"),
("def", "Python: KEYWORD def"), ("topic?key=def", "Pydoc: KEYWORD def"),
("STRINGS", "Python: TOPIC STRINGS"), ("topic?key=STRINGS", "Pydoc: TOPIC STRINGS"),
("foobar", "Python: Error"), ("foobar", "Pydoc: Error - foobar"),
("getfile?key=foobar", "Python: Read Error"), ("getfile?key=foobar", "Pydoc: Error - getfile?key=foobar"),
] ]
for url, title in requests: for url, title in requests:
@ -451,7 +448,7 @@ class PyDocUrlHandlerTest(unittest.TestCase):
self.assertEqual(result, title) self.assertEqual(result, title)
path = string.__file__ path = string.__file__
title = "Python: getfile " + path title = "Pydoc: getfile " + path
url = "getfile?key=" + path url = "getfile?key=" + path
text = pydoc._url_handler(url, "text/html") text = pydoc._url_handler(url, "text/html")
result = get_html_title(text) result = get_html_title(text)
@ -459,10 +456,10 @@ class PyDocUrlHandlerTest(unittest.TestCase):
def test_main(): def test_main():
test.support.run_unittest(PyDocDocTest, test.support.run_unittest(PydocDocTest,
TestDescriptions, TestDescriptions,
PyDocServerTest, PydocServerTest,
PyDocUrlHandlerTest, PydocUrlHandlerTest,
) )
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -88,6 +88,9 @@ Library
- Issue #9509: argparse now properly handles IOErrors raised by - Issue #9509: argparse now properly handles IOErrors raised by
argparse.FileType. argparse.FileType.
- Issue #10961: The new pydoc server now better handles exceptions raised
during request handling.
Build Build
----- -----