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('.'):
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."""
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
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.
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'.