Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a3a3cf6

Browse filesBrowse 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
Copy full SHA for a3a3cf6

File tree

Expand file treeCollapse file tree

4 files changed

+57
-5
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+57
-5
lines changed

‎Lib/_pyrepl/_module_completer.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/_module_completer.py
+12-3Lines 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

Copy file name to clipboardExpand all lines: Lib/test/test_pyrepl/test_pyrepl.py
+43-2Lines 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

Copy file name to clipboardExpand all lines: Misc/ACKS
+1Lines 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
+1Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.