]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-120541: Improve the "less" prompt in pydoc (GH-120543)
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 15 Jun 2024 17:56:40 +0000 (20:56 +0300)
committerGitHub <noreply@github.com>
Sat, 15 Jun 2024 17:56:40 +0000 (20:56 +0300)
When help() is called with non-string argument, use __qualname__ or
__name__ if available, otherwise use "{typename} object".

Lib/pydoc.py
Lib/test/test_pydoc/test_pydoc.py
Misc/NEWS.d/next/Library/2024-06-15-12-04-46.gh-issue-120541.d3cc5y.rst [new file with mode: 0644]

index be5cd9a80db710d62603fc045b7ae10a172e850d..768c3dcb11ec59bf04fbc23c78da7f5e8d34c1fa 100644 (file)
@@ -1755,7 +1755,14 @@ 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:
+                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:
index a17c16cc73cf0eaf4ae94f306144a214fabd54d7..b520cfd0b50e382f7d1eb57df8cc63142b88ba86 100644 (file)
@@ -31,7 +31,7 @@ from test.support import os_helper
 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 @@ class PydocDocTest(unittest.TestCase):
         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 @@ class PydocDocTest(unittest.TestCase):
 
         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()
@@ -742,6 +740,45 @@ class PydocDocTest(unittest.TestCase):
         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):
+        def run_pydoc_pager(request, what, expected_first_line):
+            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())
+                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()
@@ -775,9 +812,8 @@ class PydocDocTest(unittest.TestCase):
         # 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()
@@ -790,7 +826,7 @@ class PydocDocTest(unittest.TestCase):
     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)
@@ -798,7 +834,7 @@ class PydocDocTest(unittest.TestCase):
     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)
@@ -806,7 +842,7 @@ class PydocDocTest(unittest.TestCase):
     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)
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 (file)
index 0000000..bf8830c
--- /dev/null
@@ -0,0 +1,2 @@
+Improve the prompt in the "less" pager when :func:`help` is called with
+non-string argument.