8000 gh-134215: PyREPL: Do not show underscored modules by default during … · python/cpython@a3a3cf6 · GitHub
[go: up one dir, main page]

Skip to content

Commit a3a3cf6

Browse files
kevtegStanFromIrelandtomasr8ambv
authored
gh-134215: PyREPL: Do not show underscored modules by default during autocompletion (gh-134267)
Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Co-authored-by: Tomas R. <tomas.roun8@gmail.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent c91ad5d commit a3a3cf6

File tree

4 files changed

+57
-5
lines changed

4 files changed

+57
-5
lines changed

Lib/_pyrepl/_module_completer.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ def find_modules(self, path: str, prefix: str) -> list[str]:
8181
def _find_modules(self, path: str, prefix: str) -> list[str]:
8282
if not path:
8383
# Top-level import (e.g. `import foo<tab>`` or `from foo<tab>`)`
84-
builtin_modules = [name for name in sys.builtin_module_names if name.startswith(prefix)]
85-
third_party_modules = [name for _, name, _ in self.global_cache if name.startswith(prefix)]
84+
builtin_modules = [name for name in sys.builtin_module_names
85+
if self.is_suggestion_match(name, prefix)]
86+
third_party_modules = [module.name for module in self.global_cache
87+
if self.is_suggestion_match(module.name, prefix)]
8688
return sorted(builtin_modules + third_party_modules)
8789

8890
if path.startswith('.'):
@@ -98,7 +100,14 @@ def _find_modules(self, path: str, prefix: str) -> list[str]:
98100
if mod_info.ispkg and mod_info.name == segment]
99101
modules = self.iter_submodules(modules)
100102
return [module.name for module in modules
101-
if module.name.startswith(prefix)]
103+
if self.is_suggestion_match(module.name, prefix)]
104+
105+
def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
106+
if prefix:
107+
return module_name.startswith(prefix)
108+
# For consistency with attribute completion, which
109+
# does not suggest private attributes unless requested.
110+
return not module_name.startswith("_")
102111

103112
def iter_submodules(self, parent_modules: list[pkgutil.ModuleInfo]) -> Iterator[pkgutil.ModuleInfo]:
104113
"""Iterate over all submodules of the given parent modules."""

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import subprocess
99
import sys
1010
import tempfile
11+
from pkgutil import ModuleInfo
1112
from unittest import TestCase, skipUnless, skipIf
1213
from unittest.mock import patch
1314
from test.support import force_not_colorized, make_clean_env, Py_DEBUG
@@ -959,6 +960,46 @@ def test_import_completions(self):
959960
output = reader.readline()
960961
self.assertEqual(output, expected)
961962

963+
@patch("pkgutil.iter_modules", lambda: [ModuleInfo(None, "public", True),
964+
ModuleInfo(None, "_private", True)])
965+
@patch("sys.builtin_module_names", ())
966+
def test_private_completions(self):
967+
cases = (
968+
# Return public methods by default
969+
("import \t\n", "import public"),
970+
("from \t\n", "from public"),
971+
# Return private methods if explicitly specified
972+
("import _\t\n", "import _private"),
973+
("from _\t\n", "from _private"),
974+
)
975+
for code, expected in cases:
976+
with self.subTest(code=code):
977+
events = code_to_events(code)
978+
reader = self.prepare_reader(events, namespace={})
979+
output = reader.readline()
980+
self.assertEqual(output, expected)
981+
982+
@patch(
983+
"_pyrepl._module_completer.ModuleCompleter.iter_submodules",
984+
lambda *_: [
985+
ModuleInfo(None, "public", True),
986+
ModuleInfo(None, "_private", True),
987+
],
988+
)
989+
def test_sub_module_private_completions(self):
990+
cases = (
991+
# Return public methods by default
992+
("from foo import \t\n", "from foo import public"),
993+
# Return private methods if explicitly specified
994+
("from foo import _\t\n", "from foo import _private"),
995+
)
996+
for code, expected in cases:
997+
with self.subTest(code=code):
998+
events = code_to_events(code)
999+
reader = self.prepare_reader(events, namespace={})
1000+
output = reader.readline()
1001+
self.assertEqual(output, expected)
1002+
9621003
def test_builtin_completion_top_level(self):
9631004
import importlib
9641005
# Make iter_modules() search only the standard library.
@@ -991,8 +1032,8 @@ def test_relative_import_completions(self):
9911032
output = reader.readline()
9921033
self.assertEqual(output, expected)
9931034

994-
@patch("pkgutil.iter_modules", lambda: [(None, 'valid_name', None),
995-
(None, 'invalid-name', None)])
1035+
@patch("pkgutil.iter_modules", lambda: [ModuleInfo(None, "valid_name", True),
1036+
ModuleInfo(None, "invalid-name", True)])
9961037
def test_invalid_identifiers(self):
9971038
# Make sure modules which are not valid identifiers
9981039
# are not suggested as those cannot be imported via 'import'.

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ Chris Herborth
763763
Ivan Herman
764764
Jürgen Hermann
765765
Joshua Jay Herman
766+
Kevin Hernandez
766767
Gary Herron
767768
Ernie Hershey
768769
Thomas Herve
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:term:`REPL` import autocomplete only suggests private modules when explicitly specified.

0 commit comments

Comments
 (0)
0