https://github.com/python/cpython/commit/a3a3cf6d157948ed64ae837d6310b933a39a2493
commit: a3a3cf6d157948ed64ae837d6310b933a39a2493
branch: main
author: Kevin Hernández <kevin.hernan...@unet.edu.ve>
committer: ambv <luk...@langa.pl>
date: 2025-05-20T22:26:48+02:00
summary:

gh-134215: PyREPL: Do not show underscored modules by default during 
autocompletion (gh-134267)

Co-authored-by: Stan Ulbrych <89152624+stanfromirel...@users.noreply.github.com>
Co-authored-by: Tomas R. <tomas.ro...@gmail.com>
Co-authored-by: Łukasz Langa <luk...@langa.pl>

files:
A Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst
M Lib/_pyrepl/_module_completer.py
M Lib/test/test_pyrepl/test_pyrepl.py
M Misc/ACKS

diff --git a/Lib/_pyrepl/_module_completer.py b/Lib/_pyrepl/_module_completer.py
index 0606797226d1e0..9aafb55090e2ce 100644
--- a/Lib/_pyrepl/_module_completer.py
+++ b/Lib/_pyrepl/_module_completer.py
@@ -81,8 +81,10 @@ 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<tab>`` or `from foo<tab>`)`
-            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)]
+            builtin_modules = [name for name in sys.builtin_module_names
+                               if self.is_suggestion_match(name, prefix)]
+            third_party_modules = [module.name for module in self.global_cache
+                                   if self.is_suggestion_match(module.name, 
prefix)]
             return sorted(builtin_modules + third_party_modules)
 
         if path.startswith('.'):
@@ -98,7 +100,14 @@ def _find_modules(self, path: str, prefix: str) -> 
list[str]:
                        if mod_info.ispkg and mod_info.name == segment]
             modules = self.iter_submodules(modules)
         return [module.name for module in modules
-                if module.name.startswith(prefix)]
+                if self.is_suggestion_match(module.name, prefix)]
+
+    def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
+        if prefix:
+            return module_name.startswith(prefix)
+        # For consistency with attribute completion, which
+        # does not suggest private attributes unless requested.
+        return not module_name.startswith("_")
 
     def iter_submodules(self, parent_modules: list[pkgutil.ModuleInfo]) -> 
Iterator[pkgutil.ModuleInfo]:
         """Iterate over all submodules of the given parent modules."""
diff --git a/Lib/test/test_pyrepl/test_pyrepl.py 
b/Lib/test/test_pyrepl/test_pyrepl.py
index 59f5d1f893f9fd..29762232d43b89 100644
--- a/Lib/test/test_pyrepl/test_pyrepl.py
+++ b/Lib/test/test_pyrepl/test_pyrepl.py
@@ -8,6 +8,7 @@
 import subprocess
 import sys
 import tempfile
+from pkgutil import ModuleInfo
 from unittest import TestCase, skipUnless, skipIf
 from unittest.mock import patch
 from test.support import force_not_colorized, make_clean_env, Py_DEBUG
@@ -959,6 +960,46 @@ def test_import_completions(self):
                 output = reader.readline()
                 self.assertEqual(output, expected)
 
+    @patch("pkgutil.iter_modules", lambda: [ModuleInfo(None, "public", True),
+                                            ModuleInfo(None, "_private", 
True)])
+    @patch("sys.builtin_module_names", ())
+    def test_private_completions(self):
+        cases = (
+            # Return public methods by default
+            ("import \t\n", "import public"),
+            ("from \t\n", "from public"),
+            # Return private methods if explicitly specified
+            ("import _\t\n", "import _private"),
+            ("from _\t\n", "from _private"),
+        )
+        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)
+
+    @patch(
+        "_pyrepl._module_completer.ModuleCompleter.iter_submodules",
+        lambda *_: [
+            ModuleInfo(None, "public", True),
+            ModuleInfo(None, "_private", True),
+        ],
+    )
+    def test_sub_module_private_completions(self):
+        cases = (
+            # Return public methods by default
+            ("from foo import \t\n", "from foo import public"),
+            # Return private methods if explicitly specified
+            ("from foo import _\t\n", "from foo import _private"),
+        )
+        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_builtin_completion_top_level(self):
         import importlib
         # Make iter_modules() search only the standard library.
@@ -991,8 +1032,8 @@ def test_relative_import_completions(self):
                 output = reader.readline()
                 self.assertEqual(output, expected)
 
-    @patch("pkgutil.iter_modules", lambda: [(None, 'valid_name', None),
-                                            (None, 'invalid-name', None)])
+    @patch("pkgutil.iter_modules", lambda: [ModuleInfo(None, "valid_name", 
True),
+                                            ModuleInfo(None, "invalid-name", 
True)])
     def test_invalid_identifiers(self):
         # Make sure modules which are not valid identifiers
         # are not suggested as those cannot be imported via 'import'.
diff --git a/Misc/ACKS b/Misc/ACKS
index 1b500870dec472..571142e7e49763 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -763,6 +763,7 @@ Chris Herborth
 Ivan Herman
 Jürgen Hermann
 Joshua Jay Herman
+Kevin Hernandez
 Gary Herron
 Ernie Hershey
 Thomas Herve
diff --git 
a/Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst 
b/Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst
new file mode 100644
index 00000000000000..546ed2a56b6695
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst
@@ -0,0 +1 @@
+:term:`REPL` import autocomplete only suggests private modules when explicitly 
specified.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to