From 9c2a8b4071b4d39cd54258b43881019fddadabae Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 15 Jun 2024 11:59:35 +0300 Subject: [PATCH 1/5] gh-120541: Improve the "less" prompt in pydoc When help() is called with non-string argument, use __qualname__ or __name__ if available, otherwise use "{typename} object". --- Lib/pydoc.py | 8 ++++ Lib/test/test_pydoc/test_pydoc.py | 43 +++++++++++++++++++ ...-06-15-12-04-46.gh-issue-120541.d3cc5y.rst | 2 + 3 files changed, 53 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst diff --git a/Lib/pydoc.py b/Lib/pydoc.py index be5cd9a80db710..26371662fa7cdc 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -1756,6 +1756,14 @@ def doc(thing, title='Python Library Documentation: %s', forceload=0, if output is None: try: what = thing if isinstance(thing, str) else type(thing).__name__ + if isinstance(thing, str): + what = thing + else: + what = getattr(thing, '__qualname__', None) + if not isinstance(what, str): + what = getattr(thing, '__name__', None) + if not isinstance(what, str): + what = type(thing).__name__ + ' object' pager(render_doc(thing, title, forceload), f'Help on {what!s}') except ImportError as exc: if is_cli: diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index a17c16cc73cf0e..6343ca6de8b0eb 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -742,6 +742,49 @@ def run_pydoc_for_request(request, expected_text_part): 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 + @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), + 'trace function introduces __locals__ unexpectedly') + @requires_docstrings + def test_help_output_pager(self): + self.maxDiff = None + + def run_pydoc_pager(request, what, expected_first_line): + with captured_output('stdout') as output, \ + captured_output('stderr') as err, \ + unittest.mock.patch('pydoc.pager') as pager_mock, \ + self.subTest(repr(request)): + helper = pydoc.Helper() + helper.help(request) + self.assertEqual('', err.getvalue()) + self.assertEqual('\n', output.getvalue()) + pager_mock.assert_called_once() + result = clean_text(pager_mock.call_args.args[0]) + self.assertEqual(result.splitlines()[0], expected_first_line) + self.assertEqual(pager_mock.call_args.args[1], f'Help on {what}') + + run_pydoc_pager('%', 'EXPRESSIONS', 'Operator precedence') + run_pydoc_pager('True', 'bool object', 'Help on bool object:') + run_pydoc_pager(True, 'bool object', 'Help on bool object:') + run_pydoc_pager('assert', 'assert', 'The "assert" statement') + run_pydoc_pager('TYPES', 'TYPES', 'The standard type hierarchy') + run_pydoc_pager('pydoc.Helper.help', 'pydoc.Helper.help', + 'Help on function help in pydoc.Helper:') + run_pydoc_pager(pydoc.Helper.help, 'Helper.help', + 'Help on function help in module pydoc:') + run_pydoc_pager('str', 'str', 'Help on class str in module builtins:') + run_pydoc_pager(str, 'str', 'Help on class str in module builtins:') + run_pydoc_pager('str.upper', 'str.upper', 'Help on method_descriptor in str:') + run_pydoc_pager(str.upper, 'str.upper', 'Help on method_descriptor:') + run_pydoc_pager(str.__add__, 'str.__add__', 'Help on wrapper_descriptor:') + run_pydoc_pager(int.numerator, 'int.numerator', + 'Help on getset descriptor builtins.int.numerator:') + run_pydoc_pager(list[int], 'list', + 'Help on GenericAlias in module builtins:') + run_pydoc_pager('sys', 'sys', + 'Help on built-in module sys:') + run_pydoc_pager(sys, 'sys', + 'Help on built-in module sys:') + def test_showtopic(self): with captured_stdout() as showtopic_io: helper = pydoc.Helper() diff --git a/Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst b/Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst new file mode 100644 index 00000000000000..9242d3e2954227 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst @@ -0,0 +1,2 @@ +iImprove the prompt in the "less" pager when :func:`help` is called with +non-string argument. From 668b3addf86bba4a08480daaaf5946290aa57396 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 15 Jun 2024 18:24:45 +0300 Subject: [PATCH 2/5] Use captured_stdout() and captured_stderr() instead of captured_output(). --- Lib/test/test_pydoc/test_pydoc.py | 33 +++++++++++++------------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 6343ca6de8b0eb..a8e249a0e6f6f9 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -31,7 +31,7 @@ from test.support.script_helper import (assert_python_ok, assert_python_failure, spawn_python) from test.support import threading_helper -from test.support import (reap_children, captured_output, captured_stdout, +from test.support import (reap_children, captured_stdout, captured_stderr, is_emscripten, is_wasi, requires_docstrings, MISSING_C_DOCSTRINGS) from test.support.os_helper import (TESTFN, rmtree, unlink) @@ -680,9 +680,8 @@ def test_help_output_redirect(self, pager_mock): help_header = textwrap.dedent(help_header) expected_help_pattern = help_header + expected_text_pattern - with captured_output('stdout') as output, \ - captured_output('stderr') as err, \ - StringIO() as buf: + with captured_stdout() as output, captured_stderr() as err: + buf = StringIO() helper = pydoc.Helper(output=buf) helper.help(module) result = buf.getvalue().strip() @@ -706,9 +705,8 @@ def test_help_output_redirect_various_requests(self, pager_mock): 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, \ - StringIO() as buf: + with captured_stdout() as output, captured_stderr() as err: + buf = StringIO() helper = pydoc.Helper(output=buf) helper.help(request) result = buf.getvalue().strip() @@ -746,13 +744,11 @@ def run_pydoc_for_request(request, expected_text_part): 'trace function introduces __locals__ unexpectedly') @requires_docstrings def test_help_output_pager(self): - self.maxDiff = None - def run_pydoc_pager(request, what, expected_first_line): - with captured_output('stdout') as output, \ - captured_output('stderr') as err, \ - unittest.mock.patch('pydoc.pager') as pager_mock, \ - self.subTest(repr(request)): + with (captured_stdout() as output, + captured_stderr() as err, + unittest.mock.patch('pydoc.pager') as pager_mock, + self.subTest(repr(request))): helper = pydoc.Helper() helper.help(request) self.assertEqual('', err.getvalue()) @@ -818,9 +814,8 @@ def test_showtopic_output_redirect(self, pager_mock): # Helper.showtopic should be redirected self.maxDiff = None - with captured_output('stdout') as output, \ - captured_output('stderr') as err, \ - StringIO() as buf: + with captured_stdout() as output, captured_stderr() as err: + buf = StringIO() helper = pydoc.Helper(output=buf) helper.showtopic('with') result = buf.getvalue().strip() @@ -833,7 +828,7 @@ def test_showtopic_output_redirect(self, pager_mock): def test_lambda_with_return_annotation(self): func = lambda a, b, c: 1 func.__annotations__ = {"return": int} - with captured_output('stdout') as help_io: + with captured_stdout() as help_io: pydoc.help(func) helptext = help_io.getvalue() self.assertIn("lambda (a, b, c) -> int", helptext) @@ -841,7 +836,7 @@ def test_lambda_with_return_annotation(self): def test_lambda_without_return_annotation(self): func = lambda a, b, c: 1 func.__annotations__ = {"a": int, "b": int, "c": int} - with captured_output('stdout') as help_io: + with captured_stdout() as help_io: pydoc.help(func) helptext = help_io.getvalue() self.assertIn("lambda (a: int, b: int, c: int)", helptext) @@ -849,7 +844,7 @@ def test_lambda_without_return_annotation(self): def test_lambda_with_return_and_params_annotation(self): func = lambda a, b, c: 1 func.__annotations__ = {"a": int, "b": int, "c": int, "return": int} - with captured_output('stdout') as help_io: + with captured_stdout() as help_io: pydoc.help(func) helptext = help_io.getvalue() self.assertIn("lambda (a: int, b: int, c: int) -> int", helptext) From 1ecc07192fce5b532b36f113d4228c4b40d494d6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 15 Jun 2024 18:26:37 +0300 Subject: [PATCH 3/5] Remove the old code. --- Lib/pydoc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 26371662fa7cdc..768c3dcb11ec59 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -1755,7 +1755,6 @@ def doc(thing, title='Python Library Documentation: %s', forceload=0, """Display text documentation, given an object or a path to an object.""" if output is None: try: - what = thing if isinstance(thing, str) else type(thing).__name__ if isinstance(thing, str): what = thing else: From 14ce4d569b8d27c74274bf6f1f6092fed9513862 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 15 Jun 2024 18:32:12 +0300 Subject: [PATCH 4/5] Reformat. --- Lib/test/test_pydoc/test_pydoc.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index a8e249a0e6f6f9..b520cfd0b50e38 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -776,10 +776,8 @@ def run_pydoc_pager(request, what, expected_first_line): 'Help on getset descriptor builtins.int.numerator:') run_pydoc_pager(list[int], 'list', 'Help on GenericAlias in module builtins:') - run_pydoc_pager('sys', 'sys', - 'Help on built-in module sys:') - run_pydoc_pager(sys, 'sys', - 'Help on built-in module sys:') + run_pydoc_pager('sys', 'sys', 'Help on built-in module sys:') + run_pydoc_pager(sys, 'sys', 'Help on built-in module sys:') def test_showtopic(self): with captured_stdout() as showtopic_io: From 3267079e0e1b0e8e51ba7150b095e18c6204d984 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 15 Jun 2024 16:39:28 +0100 Subject: [PATCH 5/5] Update Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst --- .../next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst b/Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst index 9242d3e2954227..bf8830c6c50386 100644 --- a/Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst +++ b/Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst @@ -1,2 +1,2 @@ -iImprove the prompt in the "less" pager when :func:`help` is called with +Improve the prompt in the "less" pager when :func:`help` is called with non-string argument.