Skip to content

Commit 88f67df

Browse files
[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 2080425) Co-authored-by: Enrico Tröger <[email protected]>
1 parent 479655a commit 88f67df

File tree

3 files changed

+116
-20
lines changed

3 files changed

+116
-20
lines changed

Lib/pydoc.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,7 +2148,7 @@ def help(self, request, is_cli=False):
21482148
elif request in self.symbols: self.showsymbol(request)
21492149
elif request in ['True', 'False', 'None']:
21502150
# special case these keywords since they are objects too
2151-
doc(eval(request), 'Help on %s:', is_cli=is_cli)
2151+
doc(eval(request), 'Help on %s:', output=self._output, is_cli=is_cli)
21522152
elif request in self.keywords: self.showtopic(request)
21532153
elif request in self.topics: self.showtopic(request)
21542154
elif request: doc(request, 'Help on %s:', output=self._output, is_cli=is_cli)
@@ -2241,7 +2241,11 @@ def showtopic(self, topic, more_xrefs=''):
22412241
text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n'
22422242
wrapped_text = textwrap.wrap(text, 72)
22432243
doc += '\n%s\n' % '\n'.join(wrapped_text)
2244-
pager(doc)
2244+
2245+
if self._output is None:
2246+
pager(doc)
2247+
else:
2248+
self.output.write(doc)
22452249

22462250
def _gettopic(self, topic, more_xrefs=''):
22472251
"""Return unbuffered tuple of (topic, xrefs).

Lib/test/test_pydoc/test_pydoc.py

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import types
1616
import typing
1717
import unittest
18+
import unittest.mock
1819
import urllib.parse
1920
import xml.etree
2021
import xml.etree.ElementTree
@@ -656,16 +657,13 @@ def test_fail_help_output_redirect(self):
656657

657658
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
658659
'trace function introduces __locals__ unexpectedly')
660+
@unittest.mock.patch('pydoc.pager')
659661
@requires_docstrings
660-
def test_help_output_redirect(self):
662+
def test_help_output_redirect(self, pager_mock):
661663
# issue 940286, if output is set in Helper, then all output from
662664
# Helper.help should be redirected
663-
getpager_old = pydoc.getpager
664-
getpager_new = lambda: (lambda x: x)
665665
self.maxDiff = None
666666

667-
buf = StringIO()
668-
helper = pydoc.Helper(output=buf)
669667
unused, doc_loc = get_pydoc_text(pydoc_mod)
670668
module = "test.test_pydoc.pydoc_mod"
671669
help_header = """
@@ -675,21 +673,112 @@ def test_help_output_redirect(self):
675673
help_header = textwrap.dedent(help_header)
676674
expected_help_pattern = help_header + expected_text_pattern
677675

678-
pydoc.getpager = getpager_new
679-
try:
676+
with captured_output('stdout') as output, \
677+
captured_output('stderr') as err, \
678+
StringIO() as buf:
679+
helper = pydoc.Helper(output=buf)
680+
helper.help(module)
681+
result = buf.getvalue().strip()
682+
expected_text = expected_help_pattern % (
683+
(doc_loc,) +
684+
expected_text_data_docstrings +
685+
(inspect.getabsfile(pydoc_mod),))
686+
self.assertEqual('', output.getvalue())
687+
self.assertEqual('', err.getvalue())
688+
self.assertEqual(expected_text, result)
689+
690+
pager_mock.assert_not_called()
691+
692+
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
693+
'trace function introduces __locals__ unexpectedly')
694+
@requires_docstrings
695+
@unittest.mock.patch('pydoc.pager')
696+
def test_help_output_redirect_various_requests(self, pager_mock):
697+
# issue 940286, if output is set in Helper, then all output from
698+
# Helper.help should be redirected
699+
700+
def run_pydoc_for_request(request, expected_text_part):
701+
"""Helper function to run pydoc with its output redirected"""
680702
with captured_output('stdout') as output, \
681-
captured_output('stderr') as err:
682-
helper.help(module)
703+
captured_output('stderr') as err, \
704+
StringIO() as buf:
705+
helper = pydoc.Helper(output=buf)
706+
helper.help(request)
683707
result = buf.getvalue().strip()
684-
expected_text = expected_help_pattern % (
685-
(doc_loc,) +
686-
expected_text_data_docstrings +
687-
(inspect.getabsfile(pydoc_mod),))
688-
self.assertEqual('', output.getvalue())
689-
self.assertEqual('', err.getvalue())
690-
self.assertEqual(expected_text, result)
691-
finally:
692-
pydoc.getpager = getpager_old
708+
self.assertEqual('', output.getvalue(), msg=f'failed on request "{request}"')
709+
self.assertEqual('', err.getvalue(), msg=f'failed on request "{request}"')
710+
self.assertIn(expected_text_part, result, msg=f'failed on request "{request}"')
711+
pager_mock.assert_not_called()
712+
713+
self.maxDiff = None
714+
715+
# test for "keywords"
716+
run_pydoc_for_request('keywords', 'Here is a list of the Python keywords.')
717+
# test for "symbols"
718+
run_pydoc_for_request('symbols', 'Here is a list of the punctuation symbols')
719+
# test for "topics"
720+
run_pydoc_for_request('topics', 'Here is a list of available topics.')
721+
# test for "modules" skipped, see test_modules()
722+
# test for symbol "%"
723+
run_pydoc_for_request('%', 'The power operator')
724+
# test for special True, False, None keywords
725+
run_pydoc_for_request('True', 'class bool(int)')
726+
run_pydoc_for_request('False', 'class bool(int)')
727+
run_pydoc_for_request('None', 'class NoneType(object)')
728+
# test for keyword "assert"
729+
run_pydoc_for_request('assert', 'The "assert" statement')
730+
# test for topic "TYPES"
731+
run_pydoc_for_request('TYPES', 'The standard type hierarchy')
732+
# test for "pydoc.Helper.help"
733+
run_pydoc_for_request('pydoc.Helper.help', 'Help on function help in pydoc.Helper:')
734+
# test for pydoc.Helper.help
735+
run_pydoc_for_request(pydoc.Helper.help, 'Help on function help in module pydoc:')
736+
# test for pydoc.Helper() instance skipped because it is always meant to be interactive
737+
738+
def test_showtopic(self):
739+
with captured_stdout() as showtopic_io:
740+
helper = pydoc.Helper()
741+
helper.showtopic('with')
742+
helptext = showtopic_io.getvalue()
743+
self.assertIn('The "with" statement', helptext)
744+
745+
def test_fail_showtopic(self):
746+
with captured_stdout() as showtopic_io:
747+
helper = pydoc.Helper()
748+
helper.showtopic('abd')
749+
expected = "no documentation found for 'abd'"
750+
self.assertEqual(expected, showtopic_io.getvalue().strip())
751+
752+
@unittest.mock.patch('pydoc.pager')
753+
def test_fail_showtopic_output_redirect(self, pager_mock):
754+
with StringIO() as buf:
755+
helper = pydoc.Helper(output=buf)
756+
helper.showtopic("abd")
757+
expected = "no documentation found for 'abd'"
758+
self.assertEqual(expected, buf.getvalue().strip())
759+
760+
pager_mock.assert_not_called()
761+
762+
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
763+
'trace function introduces __locals__ unexpectedly')
764+
@requires_docstrings
765+
@unittest.mock.patch('pydoc.pager')
766+
def test_showtopic_output_redirect(self, pager_mock):
767+
# issue 940286, if output is set in Helper, then all output from
768+
# Helper.showtopic should be redirected
769+
self.maxDiff = None
770+
771+
with captured_output('stdout') as output, \
772+
captured_output('stderr') as err, \
773+
StringIO() as buf:
774+
helper = pydoc.Helper(output=buf)
775+
helper.showtopic('with')
776+
result = buf.getvalue().strip()
777+
self.assertEqual('', output.getvalue())
778+
self.assertEqual('', err.getvalue())
779+
self.assertIn('The "with" statement', result)
780+
781+
pager_mock.assert_not_called()
693782

694783
def test_lambda_with_return_annotation(self):
695784
func = lambda a, b, c: 1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`!help` and :meth:`!showtopic` methods now respect a
2+
configured *output* argument to :class:`!pydoc.Helper` and not use the
3+
pager in such cases. Patch by Enrico Tröger.

0 commit comments

Comments
 (0)