From 3cd180e93ba9f8c9d5f66234de1df50d1b2dc108 Mon Sep 17 00:00:00 2001 From: Hunter Date: Mon, 19 May 2025 11:13:13 -0500 Subject: [PATCH 01/10] added enhancement auto completing import with sys builtins --- Lib/_pyrepl/_module_completer.py | 5 +++-- Lib/test/test_pyrepl/test_pyrepl.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/_pyrepl/_module_completer.py b/Lib/_pyrepl/_module_completer.py index 347f05607c75c5..43a564846726eb 100644 --- a/Lib/_pyrepl/_module_completer.py +++ b/Lib/_pyrepl/_module_completer.py @@ -81,8 +81,9 @@ def find_modules(self, path: str, prefix: str) -> list[str]: def _find_modules(self, path: str, prefix: str) -> list[str]: if not path: # Top-level import (e.g. `import foo`` or `from foo`)` - return [name for _, name, _ in self.global_cache - if name.startswith(prefix)] + builtins = [name for name in sys.builtin_module_names if name.startswith(prefix)] + modules = [name for _, name, _ in self.global_cache if name.startswith(prefix)] + return sorted(builtins + modules) if path.startswith('.'): # Convert relative path to absolute path diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index a1cfbe4c3cadb7..7364b8364ca3cf 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1004,6 +1004,7 @@ def test_get_path_and_prefix(self): ('foo.bar', ('foo', 'bar')), ('foo.bar.', ('foo.bar', '')), ('foo.bar.baz', ('foo.bar', 'baz')), + ('sys', ('', 'sys')), ) completer = ModuleCompleter() for name, expected in cases: From 035d8dca1e92e1337a88ebc1589cb3279d15580d Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 13:31:56 -0400 Subject: [PATCH 02/10] revert test changes --- Lib/test/test_pyrepl/test_pyrepl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 7364b8364ca3cf..a1cfbe4c3cadb7 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1004,7 +1004,6 @@ def test_get_path_and_prefix(self): ('foo.bar', ('foo', 'bar')), ('foo.bar.', ('foo.bar', '')), ('foo.bar.baz', ('foo.bar', 'baz')), - ('sys', ('', 'sys')), ) completer = ModuleCompleter() for name, expected in cases: From 66d7ae60c4dd2ff07fb9e545301aa4e3848955d6 Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 13:54:42 -0400 Subject: [PATCH 03/10] test: add test for builtin import --- Lib/test/test_pyrepl/test_pyrepl.py | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index a1cfbe4c3cadb7..5cac21a4eeaea2 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -959,6 +959,59 @@ def test_import_completions(self): output = reader.readline() self.assertEqual(output, expected) + def test_builtin_completion(self): + import importlib + # Make iter_modules() search only the standard library. + # This makes the test more reliable in case there are + # other user packages/scripts on PYTHONPATH which can + # intefere with the completions. + lib_path = os.path.dirname(importlib.__path__[0]) + sys.path = [lib_path] + + cases = ( + # Basic import completion + ("import tim\t\n", "import time"), + ("import sys\t\t\n", "import sys"), # should pass? + ("import mat\t\n", "import math"), + ("import bui\t\n", "import builtins"), + + # Multiple imports + ("import foo, ti\t\n", "import foo, time"), + ("import foo, sys\t\n", "import foo, sys"), + + # Import with alias + ("import time as t\t\n", "import time as t"), + ("import math as m, sys\t\n", "import math as m, sys"), + + # From-import for functions or attributes + ("from math import si\t\n", "from math import sin"), + ("from math import co\t\n", "from math import cos"), + ("from sys import pat\t\n", "from sys import path"), + ("from builtins import str\t\n", "from builtins import str"), + + # From-import for modules + ("from bui\t\n", "from builtins"), + ("from math impo\t\n", "from math import"), + ("from sys impo\t\n", "from sys import"), + + # Nested completion with tab navigation + ("import builti\t\t\n", "import builtins"), + ("from builti\t\t\n", "from builtins"), + + # tab twice + ("import ma\t\t\n", "import math"), + ("from ma\t\t\n", "from math"), + + # not matching anything + ("import ll\t\t\n", "import ll") + ) + for code, expected in cases: + with self.subTest(code=code): + events = code_to_events(code) + reader = self.prepare_reader(events, namespace={}) + output = reader.readline() + self.assertEqual(output, expected) + def test_relative_import_completions(self): cases = ( ("from .readl\t\n", "from .readline"), From 1d395cb8619eed74e2ecd4a91ad271bf66ea95a7 Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 14:24:26 -0400 Subject: [PATCH 04/10] del unneed tests --- Lib/test/test_pyrepl/test_pyrepl.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 5cac21a4eeaea2..bd8e62b300e9d9 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -959,7 +959,7 @@ def test_import_completions(self): output = reader.readline() self.assertEqual(output, expected) - def test_builtin_completion(self): + def test_builtin_completion_top_level(self): import importlib # Make iter_modules() search only the standard library. # This makes the test more reliable in case there are @@ -980,28 +980,17 @@ def test_builtin_completion(self): ("import foo, sys\t\n", "import foo, sys"), # Import with alias - ("import time as t\t\n", "import time as t"), + ("import tim\t as t\n", "import time as t"), ("import math as m, sys\t\n", "import math as m, sys"), - # From-import for functions or attributes - ("from math import si\t\n", "from math import sin"), - ("from math import co\t\n", "from math import cos"), - ("from sys import pat\t\n", "from sys import path"), + # From-import with top level + ("from mat\t", "from math"), + ("from ma\t\tt\t\n", "from math"), ("from builtins import str\t\n", "from builtins import str"), # From-import for modules ("from bui\t\n", "from builtins"), - ("from math impo\t\n", "from math import"), - ("from sys impo\t\n", "from sys import"), - # Nested completion with tab navigation - ("import builti\t\t\n", "import builtins"), - ("from builti\t\t\n", "from builtins"), - - # tab twice - ("import ma\t\t\n", "import math"), - ("from ma\t\t\n", "from math"), - # not matching anything ("import ll\t\t\n", "import ll") ) From e134e0139dd118c98d55fc3687fad12c9c8683f6 Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 14:26:24 -0400 Subject: [PATCH 05/10] del more unneed tests --- Lib/test/test_pyrepl/test_pyrepl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index bd8e62b300e9d9..4e0535c525b07d 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -986,7 +986,6 @@ def test_builtin_completion_top_level(self): # From-import with top level ("from mat\t", "from math"), ("from ma\t\tt\t\n", "from math"), - ("from builtins import str\t\n", "from builtins import str"), # From-import for modules ("from bui\t\n", "from builtins"), From 83a84571ed020f8eafb243722111362420e438fa Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 14:31:26 -0400 Subject: [PATCH 06/10] fixbug: add \n on tests --- Lib/test/test_pyrepl/test_pyrepl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 4e0535c525b07d..b02617a252ef85 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -984,7 +984,7 @@ def test_builtin_completion_top_level(self): ("import math as m, sys\t\n", "import math as m, sys"), # From-import with top level - ("from mat\t", "from math"), + ("from mat\t\n", "from math"), ("from ma\t\tt\t\n", "from math"), # From-import for modules From 0fbeb765a95a12e7d54a36c06c68a778039ef6af Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 14:41:06 -0400 Subject: [PATCH 07/10] clean up unneed test --- Lib/test/test_pyrepl/test_pyrepl.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index b02617a252ef85..4562863a357c97 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -969,29 +969,15 @@ def test_builtin_completion_top_level(self): sys.path = [lib_path] cases = ( - # Basic import completion ("import tim\t\n", "import time"), - ("import sys\t\t\n", "import sys"), # should pass? + ("import sys\t\t\n", "import sys"), ("import mat\t\n", "import math"), ("import bui\t\n", "import builtins"), - - # Multiple imports - ("import foo, ti\t\n", "import foo, time"), - ("import foo, sys\t\n", "import foo, sys"), - - # Import with alias - ("import tim\t as t\n", "import time as t"), - ("import math as m, sys\t\n", "import math as m, sys"), - - # From-import with top level ("from mat\t\n", "from math"), ("from ma\t\tt\t\n", "from math"), - - # From-import for modules ("from bui\t\n", "from builtins"), - - # not matching anything - ("import ll\t\t\n", "import ll") + ("import foo, ti\t\n", "import foo, time"), + ("import foo, sys\t\n", "import foo, sys"), ) for code, expected in cases: with self.subTest(code=code): From 3b45160ad09af6e5afb47d2d103dcedef4f9f133 Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 15:51:42 -0400 Subject: [PATCH 08/10] clean up test case --- Lib/test/test_pyrepl/test_pyrepl.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 4562863a357c97..59f5d1f893f9fd 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -969,15 +969,8 @@ def test_builtin_completion_top_level(self): sys.path = [lib_path] cases = ( - ("import tim\t\n", "import time"), - ("import sys\t\t\n", "import sys"), - ("import mat\t\n", "import math"), ("import bui\t\n", "import builtins"), - ("from mat\t\n", "from math"), - ("from ma\t\tt\t\n", "from math"), ("from bui\t\n", "from builtins"), - ("import foo, ti\t\n", "import foo, time"), - ("import foo, sys\t\n", "import foo, sys"), ) for code, expected in cases: with self.subTest(code=code): From 019537b26e5ec7bb44e798c7c8e0675afaf4e82b Mon Sep 17 00:00:00 2001 From: Hunter Date: Mon, 19 May 2025 15:07:27 -0500 Subject: [PATCH 09/10] updated NEWS.d --- .../next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst diff --git a/Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst b/Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst new file mode 100644 index 00000000000000..a65df886919145 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst @@ -0,0 +1,2 @@ +Updated tab completion on REPL to include builtin modules. Contributed by +Tom Wang, Hunter Young From b8402ebad2c80bf3c699880beaee01887db0f035 Mon Sep 17 00:00:00 2001 From: tommix626 Date: Mon, 19 May 2025 16:41:14 -0400 Subject: [PATCH 10/10] change var name for type check --- Lib/_pyrepl/_module_completer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/_pyrepl/_module_completer.py b/Lib/_pyrepl/_module_completer.py index 43a564846726eb..0606797226d1e0 100644 --- a/Lib/_pyrepl/_module_completer.py +++ b/Lib/_pyrepl/_module_completer.py @@ -81,9 +81,9 @@ def find_modules(self, path: str, prefix: str) -> list[str]: def _find_modules(self, path: str, prefix: str) -> list[str]: if not path: # Top-level import (e.g. `import foo`` or `from foo`)` - builtins = [name for name in sys.builtin_module_names if name.startswith(prefix)] - modules = [name for _, name, _ in self.global_cache if name.startswith(prefix)] - return sorted(builtins + modules) + builtin_modules = [name for name in sys.builtin_module_names if name.startswith(prefix)] + third_party_modules = [name for _, name, _ in self.global_cache if name.startswith(prefix)] + return sorted(builtin_modules + third_party_modules) if path.startswith('.'): # Convert relative path to absolute path