mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
#10961: fix exception handling in new pydoc server code.
Patch by Ron Adam, reviewed by Eric Araujo.
This commit is contained in:
parent
ce227e3518
commit
d2f3857c40
3 changed files with 108 additions and 99 deletions
133
Lib/pydoc.py
133
Lib/pydoc.py
|
@ -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
|
||||||
|
@ -2387,7 +2386,7 @@ def _start_server(urlhandler, port):
|
||||||
else:
|
else:
|
||||||
content_type = 'text/html'
|
content_type = 'text/html'
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-Type', '%s;charset=UTF-8' % content_type)
|
self.send_header('Content-Type', '%s; charset=UTF-8' % content_type)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(self.urlhandler(
|
self.wfile.write(self.urlhandler(
|
||||||
self.path, content_type).encode('utf-8'))
|
self.path, content_type).encode('utf-8'))
|
||||||
|
@ -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">
|
||||||
|
</form>
|
||||||
</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'> </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):
|
||||||
|
|
|
@ -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__":
|
||||||
|
|
|
@ -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
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue