From f1eba0c28208246dac76895ebaf5949f7d957ef3 Mon Sep 17 00:00:00 2001 From: yihong0618 Date: Sat, 6 Sep 2025 08:29:36 +0800 Subject: [PATCH 01/12] fix: make it the same behavior in repl help mode Signed-off-by: yihong0618 --- Lib/pydoc.py | 2 +- .../next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst diff --git a/Lib/pydoc.py b/Lib/pydoc.py index d508fb70ea429e..6a9eade64e7873 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -2059,7 +2059,7 @@ def interact(self): while True: try: request = self.getline('help> ') - if not request: break + if not request: continue except (KeyboardInterrupt, EOFError): break request = request.strip() diff --git a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst new file mode 100644 index 00000000000000..faa4b3bb1b90a2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst @@ -0,0 +1 @@ +make the same exit behavior in repl help mode. From 9e16e68e77106033b0858a6370326a230605799f Mon Sep 17 00:00:00 2001 From: yihong0618 Date: Sat, 6 Sep 2025 08:46:12 +0800 Subject: [PATCH 02/12] fix: address commnets add tests and better news Signed-off-by: yihong0618 --- Lib/test/test_pydoc/test_pydoc.py | 42 +++++++++++++++++++ ...-09-06-08-29-08.gh-issue-138568.iZlalC.rst | 5 ++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 3b50ead00bdd31..82f4a140c3cb62 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2165,6 +2165,48 @@ def test_keywords(self): self.assertEqual(sorted(pydoc.Helper.keywords), sorted(keyword.kwlist)) + def test_interact_empty_line_continues(self): + with captured_stdout() as output: + helper = pydoc.Helper(output=output) + input_sequence = ['', 'quit'] + call_count = [0] + def mock_getline(prompt): + output.write(prompt) + result = input_sequence[call_count[0]] + call_count[0] += 1 + return result + + with unittest.mock.patch.object(helper, 'getline', side_effect=mock_getline): + helper.interact() + + output_text = output.getvalue() + prompt_count = output_text.count('help> ') + self.assertEqual(prompt_count, 2) + + def test_interact_quit_commands_exit(self): + quit_commands = ['quit', 'q', 'exit'] + + for quit_cmd in quit_commands: + with self.subTest(quit_command=quit_cmd): + with captured_stdout() as output: + helper = pydoc.Helper(output=output) + + call_count = [0] + + def mock_getline(prompt): + output.write(prompt) + call_count[0] += 1 + return quit_cmd + + with unittest.mock.patch.object(helper, 'getline', side_effect=mock_getline): + helper.interact() + + # Should only be called once before exiting + self.assertEqual(call_count[0], 1) + output_text = output.getvalue() + prompt_count = output_text.count('help> ') + self.assertEqual(prompt_count, 1) + class PydocWithMetaClasses(unittest.TestCase): def tearDown(self): diff --git a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst index faa4b3bb1b90a2..713fc607a7df94 100644 --- a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst +++ b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst @@ -1 +1,4 @@ -make the same exit behavior in repl help mode. +In :mod:`pydoc` interactive help mode to handle empty input correctly. +Empty lines (pressing Enter without input) now continue the help session +instead of exiting, matching the documented behavior that only "q", "quit", +or "exit" commands should terminate the help session. From b50ffbb954898f2c723040871ab074942e84ec1c Mon Sep 17 00:00:00 2001 From: yihong Date: Sat, 6 Sep 2025 09:19:32 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E6=9B=B4=E6=96=B0=202025-09-06-08-29-08.?= =?UTF-8?q?gh-issue-138568.iZlalC.rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst index 713fc607a7df94..35f885bbb8a80a 100644 --- a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst +++ b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst @@ -1,4 +1,3 @@ -In :mod:`pydoc` interactive help mode to handle empty input correctly. -Empty lines (pressing Enter without input) now continue the help session -instead of exiting, matching the documented behavior that only "q", "quit", -or "exit" commands should terminate the help session. +In :mod:`pydoc` interactive help mode to handle empty input correct as doc. +Empty lines (pressing Enter without input) will continue in help session +instead of exiting. \ No newline at end of file From 575462a9a89e3fba0662590ea404e7998343d2ac Mon Sep 17 00:00:00 2001 From: yihong0618 Date: Sat, 6 Sep 2025 11:47:04 +0800 Subject: [PATCH 04/12] fix: lint and words Signed-off-by: yihong0618 --- Lib/test/test_pydoc/test_pydoc.py | 2 +- .../Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 82f4a140c3cb62..f3a987a49db652 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2166,6 +2166,7 @@ def test_keywords(self): sorted(keyword.kwlist)) def test_interact_empty_line_continues(self): + # gh-138568: test pressing Enter without input should continue in help session with captured_stdout() as output: helper = pydoc.Helper(output=output) input_sequence = ['', 'quit'] @@ -2201,7 +2202,6 @@ def mock_getline(prompt): with unittest.mock.patch.object(helper, 'getline', side_effect=mock_getline): helper.interact() - # Should only be called once before exiting self.assertEqual(call_count[0], 1) output_text = output.getvalue() prompt_count = output_text.count('help> ') diff --git a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst index 35f885bbb8a80a..cf29d0d768b352 100644 --- a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst +++ b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst @@ -1,3 +1,2 @@ In :mod:`pydoc` interactive help mode to handle empty input correct as doc. -Empty lines (pressing Enter without input) will continue in help session -instead of exiting. \ No newline at end of file +press Enter without input should continue in help session instead of exiting. From 4201a09bf0ec474c46442a46e5f1b3df160a6d62 Mon Sep 17 00:00:00 2001 From: yihong Date: Sun, 7 Sep 2025 07:21:54 +0800 Subject: [PATCH 05/12] Update Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst Co-authored-by: adam j hartz --- .../Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst index cf29d0d768b352..8a916310259c78 100644 --- a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst +++ b/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst @@ -1,2 +1,2 @@ -In :mod:`pydoc` interactive help mode to handle empty input correct as doc. -press Enter without input should continue in help session instead of exiting. +Adjusted the built-in :func:`help` function so that empty inputs are ignored in +interactive mode. From 7462dbbcd8bcd5b247c48ecae2c907afa77e6767 Mon Sep 17 00:00:00 2001 From: yihong0618 Date: Sun, 7 Sep 2025 09:11:02 +0800 Subject: [PATCH 06/12] fix: address comments Signed-off-by: yihong0618 Co-authored-by: adam j hartz --- Lib/pydoc.py | 2 +- Lib/test/test_pydoc/test_pydoc.py | 64 ++++++++++++++----------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 6a9eade64e7873..50d0ccb8fb9fc3 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -2059,7 +2059,7 @@ def interact(self): while True: try: request = self.getline('help> ') - if not request: continue + if not request.strip(): continue except (KeyboardInterrupt, EOFError): break request = request.strip() diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index f3a987a49db652..ddfd0560df39d8 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2161,51 +2161,45 @@ def test_url_requests(self): class TestHelper(unittest.TestCase): + def mock_interactive_session(self, inputs): + """ + Given a list of inputs, run an interactive help session. Returns a string + of what would be shown on screen. + """ + input_iter = iter(inputs) + + def mock_getline(prompt): + output.write(prompt) + next_input = next(input_iter) + output.write(next_input + os.linesep) + return next_input + + with captured_stdout() as output: + helper = pydoc.Helper(output=output) + with unittest.mock.patch.object(helper, "getline", mock_getline): + helper.interact() + + return output.getvalue().replace(os.linesep, "\n") + def test_keywords(self): self.assertEqual(sorted(pydoc.Helper.keywords), sorted(keyword.kwlist)) def test_interact_empty_line_continues(self): # gh-138568: test pressing Enter without input should continue in help session - with captured_stdout() as output: - helper = pydoc.Helper(output=output) - input_sequence = ['', 'quit'] - call_count = [0] - def mock_getline(prompt): - output.write(prompt) - result = input_sequence[call_count[0]] - call_count[0] += 1 - return result - - with unittest.mock.patch.object(helper, 'getline', side_effect=mock_getline): - helper.interact() - - output_text = output.getvalue() - prompt_count = output_text.count('help> ') - self.assertEqual(prompt_count, 2) + self.assertEqual( + self.mock_interactive_session(["", " ", "quit"]), + "\nhelp> \nhelp> \nhelp> quit\n", + ) def test_interact_quit_commands_exit(self): - quit_commands = ['quit', 'q', 'exit'] - + quit_commands = ["quit", "q", "exit"] for quit_cmd in quit_commands: with self.subTest(quit_command=quit_cmd): - with captured_stdout() as output: - helper = pydoc.Helper(output=output) - - call_count = [0] - - def mock_getline(prompt): - output.write(prompt) - call_count[0] += 1 - return quit_cmd - - with unittest.mock.patch.object(helper, 'getline', side_effect=mock_getline): - helper.interact() - - self.assertEqual(call_count[0], 1) - output_text = output.getvalue() - prompt_count = output_text.count('help> ') - self.assertEqual(prompt_count, 1) + self.assertEqual( + self.mock_interactive_session([quit_cmd]), + f"\nhelp> {quit_cmd}\n", + ) class PydocWithMetaClasses(unittest.TestCase): From 6fa113636a1b422da14331e09f26edf08985fa10 Mon Sep 17 00:00:00 2001 From: yihong Date: Sun, 7 Sep 2025 13:50:44 +0800 Subject: [PATCH 07/12] Update Lib/pydoc.py Co-authored-by: adam j hartz --- Lib/pydoc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 50d0ccb8fb9fc3..8229759016d9b5 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -2059,10 +2059,11 @@ def interact(self): while True: try: request = self.getline('help> ') - if not request.strip(): continue except (KeyboardInterrupt, EOFError): break request = request.strip() + if not request: + continue # Make sure significant trailing quoting marks of literals don't # get deleted while cleaning input From 3b4915a41746cfde9f7e653225bb0fc5d2dd0506 Mon Sep 17 00:00:00 2001 From: yihong0618 Date: Wed, 10 Sep 2025 09:57:43 +0800 Subject: [PATCH 08/12] fix: move the news to core and buildins dir Signed-off-by: yihong0618 --- .../2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Library => Core_and_Builtins}/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst (100%) diff --git a/Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst rename to Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-08-29-08.gh-issue-138568.iZlalC.rst From 2babb7db1d9fde85815057609a12d3daffb2dcd7 Mon Sep 17 00:00:00 2001 From: yihong Date: Wed, 10 Sep 2025 10:26:08 +0800 Subject: [PATCH 09/12] fix: apply suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Lib/pydoc.py | 4 ++-- Lib/test/test_pydoc/test_pydoc.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 8229759016d9b5..21d556bedbd87f 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -2061,9 +2061,9 @@ def interact(self): request = self.getline('help> ') except (KeyboardInterrupt, EOFError): break + if not request or request.isspace(): + continue # back to the prompt request = request.strip() - if not request: - continue # Make sure significant trailing quoting marks of literals don't # get deleted while cleaning input diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index ddfd0560df39d8..d39c5a4d4adec4 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2179,7 +2179,7 @@ def mock_getline(prompt): with unittest.mock.patch.object(helper, "getline", mock_getline): helper.interact() - return output.getvalue().replace(os.linesep, "\n") + return output.getvalue().split(os.linesep) def test_keywords(self): self.assertEqual(sorted(pydoc.Helper.keywords), From 4cae1c26480e274ccdc8b66e8c59e42f87aadfbe Mon Sep 17 00:00:00 2001 From: yihong0618 Date: Wed, 10 Sep 2025 10:45:42 +0800 Subject: [PATCH 10/12] fix: the tests Signed-off-by: yihong0618 --- Lib/test/test_pydoc/test_pydoc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index d39c5a4d4adec4..60e82c1b964702 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2189,7 +2189,7 @@ def test_interact_empty_line_continues(self): # gh-138568: test pressing Enter without input should continue in help session self.assertEqual( self.mock_interactive_session(["", " ", "quit"]), - "\nhelp> \nhelp> \nhelp> quit\n", + ["", "help> ", "help> ", "help> quit", ""], ) def test_interact_quit_commands_exit(self): @@ -2198,7 +2198,7 @@ def test_interact_quit_commands_exit(self): with self.subTest(quit_command=quit_cmd): self.assertEqual( self.mock_interactive_session([quit_cmd]), - f"\nhelp> {quit_cmd}\n", + ["", f"help> {quit_cmd}", ""], ) From 2c4944c9d7418018ce62d4cd99e13aa4902257f6 Mon Sep 17 00:00:00 2001 From: yihong0618 Date: Wed, 10 Sep 2025 11:35:31 +0800 Subject: [PATCH 11/12] fix: test wrong on windows Signed-off-by: yihong0618 --- Lib/test/test_pydoc/test_pydoc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 60e82c1b964702..66a1fe9ebf6162 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2179,7 +2179,8 @@ def mock_getline(prompt): with unittest.mock.patch.object(helper, "getline", mock_getline): helper.interact() - return output.getvalue().split(os.linesep) + # handle different line endings across platforms consistently + return output.getvalue().splitlines(keepends=False) + [''] def test_keywords(self): self.assertEqual(sorted(pydoc.Helper.keywords), From 64d9f3661153775146671180801d140f6b50f815 Mon Sep 17 00:00:00 2001 From: yihong Date: Wed, 10 Sep 2025 20:16:00 +0800 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: adam j hartz --- Lib/test/test_pydoc/test_pydoc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 66a1fe9ebf6162..2e933a1f9a4bd4 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -2180,7 +2180,7 @@ def mock_getline(prompt): helper.interact() # handle different line endings across platforms consistently - return output.getvalue().splitlines(keepends=False) + [''] + return output.getvalue().strip().splitlines(keepends=False) def test_keywords(self): self.assertEqual(sorted(pydoc.Helper.keywords), @@ -2190,7 +2190,7 @@ def test_interact_empty_line_continues(self): # gh-138568: test pressing Enter without input should continue in help session self.assertEqual( self.mock_interactive_session(["", " ", "quit"]), - ["", "help> ", "help> ", "help> quit", ""], + ["help> ", "help> ", "help> quit"], ) def test_interact_quit_commands_exit(self): @@ -2199,7 +2199,7 @@ def test_interact_quit_commands_exit(self): with self.subTest(quit_command=quit_cmd): self.assertEqual( self.mock_interactive_session([quit_cmd]), - ["", f"help> {quit_cmd}", ""], + [f"help> {quit_cmd}"], )