https://github.com/python/cpython/commit/88f67df22cbd50e884df061be2a106ccd658bf39
commit: 88f67df22cbd50e884df061be2a106ccd658bf39
branch: 3.12
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-06-08T09:46:25Z
summary:

[3.12] bpo-37755: Use configured output in pydoc instead of pager (GH-15105) 
(GH-120262)

If the Helper() class was initialized with an output, the topics, keywords
and symbols help still use the pager instead of the output.
Change the behavior so  the output is used if available while keeping the
previous behavior if no output was configured.
(cherry picked from commit 2080425154d235b4b7dcc9a8a2f58e71769125ca)

Co-authored-by: Enrico Tröger <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-06-02-13-35-11.gh-issue-81936.ETeW9x.rst
M Lib/pydoc.py
M Lib/test/test_pydoc/test_pydoc.py

diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 9a8812392af493..e3745e5453b5c3 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -2148,7 +2148,7 @@ def help(self, request, is_cli=False):
             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)
@@ -2241,7 +2241,11 @@ def showtopic(self, topic, more_xrefs=''):
             text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n'
             wrapped_text = textwrap.wrap(text, 72)
             doc += '\n%s\n' % '\n'.join(wrapped_text)
-        pager(doc)
+
+        if self._output is None:
+            pager(doc)
+        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 a35257c8ffc784..6f9677ca9b9c1b 100644
--- a/Lib/test/test_pydoc/test_pydoc.py
+++ b/Lib/test/test_pydoc/test_pydoc.py
@@ -15,6 +15,7 @@
 import types
 import typing
 import unittest
+import unittest.mock
 import urllib.parse
 import xml.etree
 import xml.etree.ElementTree
@@ -656,16 +657,13 @@ def test_fail_help_output_redirect(self):
 
     @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 = """
@@ -675,21 +673,112 @@ def test_help_output_redirect(self):
         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 00000000000000..d53cc73e728d54
--- /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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to