summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xLib/pydoc.py8
-rw-r--r--Lib/test/test_pydoc/test_pydoc.py125
-rw-r--r--Misc/NEWS.d/next/Library/2024-06-02-13-35-11.gh-issue-81936.ETeW9x.rst3
3 files changed, 116 insertions, 20 deletions
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 922e7fb..cd890a7 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -2035,7 +2035,7 @@ has the same effect as typing a particular string at the help> prompt.
elif request in self.symbols: self.showsymbol(request)
elif request in ['True', 'False', 'None']:
# special case these keywords since they are objects too
- doc(eval(request), 'Help on %s:', is_cli=is_cli)
+ doc(eval(request), 'Help on %s:', output=self._output, is_cli=is_cli)
elif request in self.keywords: self.showtopic(request)
elif request in self.topics: self.showtopic(request)
elif request: doc(request, 'Help on %s:', output=self._output, is_cli=is_cli)
@@ -2128,7 +2128,11 @@ module "pydoc_data.topics" could not be found.
text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n'
wrapped_text = textwrap.wrap(text, 72)
doc += '\n%s\n' % '\n'.join(wrapped_text)
- pager(doc, f'Help on {topic!s}')
+
+ if self._output is None:
+ pager(doc, f'Help on {topic!s}')
+ else:
+ self.output.write(doc)
def _gettopic(self, topic, more_xrefs=''):
"""Return unbuffered tuple of (topic, xrefs).
diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py
index 436fdb3..57e5b8e 100644
--- a/Lib/test/test_pydoc/test_pydoc.py
+++ b/Lib/test/test_pydoc/test_pydoc.py
@@ -17,6 +17,7 @@ import time
import types
import typing
import unittest
+import unittest.mock
import urllib.parse
import xml.etree
import xml.etree.ElementTree
@@ -658,16 +659,13 @@ class PydocDocTest(unittest.TestCase):
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'trace function introduces __locals__ unexpectedly')
+ @unittest.mock.patch('pydoc.pager')
@requires_docstrings
- def test_help_output_redirect(self):
+ def test_help_output_redirect(self, pager_mock):
# issue 940286, if output is set in Helper, then all output from
# Helper.help should be redirected
- getpager_old = pydoc.getpager
- getpager_new = lambda: (lambda x: x)
self.maxDiff = None
- buf = StringIO()
- helper = pydoc.Helper(output=buf)
unused, doc_loc = get_pydoc_text(pydoc_mod)
module = "test.test_pydoc.pydoc_mod"
help_header = """
@@ -677,21 +675,112 @@ class PydocDocTest(unittest.TestCase):
help_header = textwrap.dedent(help_header)
expected_help_pattern = help_header + expected_text_pattern
- pydoc.getpager = getpager_new
- try:
+ with captured_output('stdout') as output, \
+ captured_output('stderr') as err, \
+ StringIO() as buf:
+ helper = pydoc.Helper(output=buf)
+ helper.help(module)
+ result = buf.getvalue().strip()
+ expected_text = expected_help_pattern % (
+ (doc_loc,) +
+ expected_text_data_docstrings +
+ (inspect.getabsfile(pydoc_mod),))
+ self.assertEqual('', output.getvalue())
+ self.assertEqual('', err.getvalue())
+ self.assertEqual(expected_text, result)
+
+ pager_mock.assert_not_called()
+
+ @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
+ 'trace function introduces __locals__ unexpectedly')
+ @requires_docstrings
+ @unittest.mock.patch('pydoc.pager')
+ def test_help_output_redirect_various_requests(self, pager_mock):
+ # issue 940286, if output is set in Helper, then all output from
+ # Helper.help should be redirected
+
+ def run_pydoc_for_request(request, expected_text_part):
+ """Helper function to run pydoc with its output redirected"""
with captured_output('stdout') as output, \
- captured_output('stderr') as err:
- helper.help(module)
+ captured_output('stderr') as err, \
+ StringIO() as buf:
+ helper = pydoc.Helper(output=buf)
+ helper.help(request)
result = buf.getvalue().strip()
- expected_text = expected_help_pattern % (
- (doc_loc,) +
- expected_text_data_docstrings +
- (inspect.getabsfile(pydoc_mod),))
- self.assertEqual('', output.getvalue())
- self.assertEqual('', err.getvalue())
- self.assertEqual(expected_text, result)
- finally:
- pydoc.getpager = getpager_old
+ self.assertEqual('', output.getvalue(), msg=f'failed on request "{request}"')
+ self.assertEqual('', err.getvalue(), msg=f'failed on request "{request}"')
+ self.assertIn(expected_text_part, result, msg=f'failed on request "{request}"')
+ pager_mock.assert_not_called()
+
+ self.maxDiff = None
+
+ # test for "keywords"
+ run_pydoc_for_request('keywords', 'Here is a list of the Python keywords.')
+ # test for "symbols"
+ run_pydoc_for_request('symbols', 'Here is a list of the punctuation symbols')
+ # test for "topics"
+ run_pydoc_for_request('topics', 'Here is a list of available topics.')
+ # test for "modules" skipped, see test_modules()
+ # test for symbol "%"
+ run_pydoc_for_request('%', 'The power operator')
+ # test for special True, False, None keywords
+ run_pydoc_for_request('True', 'class bool(int)')
+ run_pydoc_for_request('False', 'class bool(int)')
+ run_pydoc_for_request('None', 'class NoneType(object)')
+ # test for keyword "assert"
+ run_pydoc_for_request('assert', 'The "assert" statement')
+ # test for topic "TYPES"
+ run_pydoc_for_request('TYPES', 'The standard type hierarchy')
+ # test for "pydoc.Helper.help"
+ run_pydoc_for_request('pydoc.Helper.help', 'Help on function help in pydoc.Helper:')
+ # test for pydoc.Helper.help
+ run_pydoc_for_request(pydoc.Helper.help, 'Help on function help in module pydoc:')
+ # test for pydoc.Helper() instance skipped because it is always meant to be interactive
+
+ def test_showtopic(self):
+ with captured_stdout() as showtopic_io:
+ helper = pydoc.Helper()
+ helper.showtopic('with')
+ helptext = showtopic_io.getvalue()
+ self.assertIn('The "with" statement', helptext)
+
+ def test_fail_showtopic(self):
+ with captured_stdout() as showtopic_io:
+ helper = pydoc.Helper()
+ helper.showtopic('abd')
+ expected = "no documentation found for 'abd'"
+ self.assertEqual(expected, showtopic_io.getvalue().strip())
+
+ @unittest.mock.patch('pydoc.pager')
+ def test_fail_showtopic_output_redirect(self, pager_mock):
+ with StringIO() as buf:
+ helper = pydoc.Helper(output=buf)
+ helper.showtopic("abd")
+ expected = "no documentation found for 'abd'"
+ self.assertEqual(expected, buf.getvalue().strip())
+
+ pager_mock.assert_not_called()
+
+ @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
+ 'trace function introduces __locals__ unexpectedly')
+ @requires_docstrings
+ @unittest.mock.patch('pydoc.pager')
+ def test_showtopic_output_redirect(self, pager_mock):
+ # issue 940286, if output is set in Helper, then all output from
+ # Helper.showtopic should be redirected
+ self.maxDiff = None
+
+ with captured_output('stdout') as output, \
+ captured_output('stderr') as err, \
+ StringIO() as buf:
+ helper = pydoc.Helper(output=buf)
+ helper.showtopic('with')
+ result = buf.getvalue().strip()
+ self.assertEqual('', output.getvalue())
+ self.assertEqual('', err.getvalue())
+ self.assertIn('The "with" statement', result)
+
+ pager_mock.assert_not_called()
def test_lambda_with_return_annotation(self):
func = lambda a, b, c: 1
diff --git a/Misc/NEWS.d/next/Library/2024-06-02-13-35-11.gh-issue-81936.ETeW9x.rst b/Misc/NEWS.d/next/Library/2024-06-02-13-35-11.gh-issue-81936.ETeW9x.rst
new file mode 100644
index 0000000..d53cc73
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-02-13-35-11.gh-issue-81936.ETeW9x.rst
@@ -0,0 +1,3 @@
+:meth:`!help` and :meth:`!showtopic` methods now respect a
+configured *output* argument to :class:`!pydoc.Helper` and not use the
+pager in such cases. Patch by Enrico Tröger.