Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Do not propose hardcoded submodules if local import
  • Loading branch information
loic-simon committed Aug 31, 2025
commit fbfe884e66a79de1df33768836a6632b044da991
17 changes: 16 additions & 1 deletion Lib/_pyrepl/_module_completer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from __future__ import annotations

import importlib
import os
import pkgutil
import sys
import token
import tokenize
from importlib.machinery import FileFinder
from io import StringIO
from contextlib import contextmanager
from dataclasses import dataclass
Expand Down Expand Up @@ -50,6 +53,7 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None:
self.namespace = namespace or {}
self._global_cache: list[pkgutil.ModuleInfo] = []
self._curr_sys_path: list[str] = sys.path[:]
self._stdlib_path = os.path.dirname(importlib.__path__[0])

def get_completions(self, line: str) -> list[str] | None:
"""Return the next possible import completions for 'line'."""
Expand Down Expand Up @@ -104,16 +108,27 @@ def _find_modules(self, path: str, prefix: str) -> list[str]:
return []

modules: Iterable[pkgutil.ModuleInfo] = self.global_cache
is_stdlib_import: bool | None = None
for segment in path.split('.'):
modules = [mod_info for mod_info in modules
if mod_info.ispkg and mod_info.name == segment]
if is_stdlib_import is None:
# Top-level import decide if we import from stdlib or not
is_stdlib_import = all(
self._is_stdlib_module(mod_info) for mod_info in modules
)
modules = self.iter_submodules(modules)

module_names = [module.name for module in modules]
module_names.extend(HARDCODED_SUBMODULES.get(path, ()))
if is_stdlib_import:
module_names.extend(HARDCODED_SUBMODULES.get(path, ()))
return [module_name for module_name in module_names
if self.is_suggestion_match(module_name, prefix)]

def _is_stdlib_module(self, module_info: pkgutil.ModuleInfo) -> bool:
return (isinstance(module_info.module_finder, FileFinder)
and module_info.module_finder.path == self._stdlib_path)

def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
if prefix:
return module_name.startswith(prefix)
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,18 @@ def test_hardcoded_stdlib_submodules(self):
output = reader.readline()
self.assertEqual(output, expected)

def test_hardcoded_stdlib_submodules_not_proposed_if_local_import(self):
with tempfile.TemporaryDirectory() as _dir:
dir = pathlib.Path(_dir)
(dir / "collections").mkdir()
(dir / "collections" / "__init__.py").touch()
(dir / "collections" / "foo.py").touch()
with patch.object(sys, "path", [dir, *sys.path]):
events = code_to_events("import collections.\t\n")
reader = self.prepare_reader(events, namespace={})
output = reader.readline()
self.assertEqual(output, "import collections.foo")

def test_get_path_and_prefix(self):
cases = (
('', ('', '')),
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Fix some standard library sumbodules missing from the :term:`REPL` auto-completion of imports.
Fix some standard library submodules missing from the :term:`REPL` auto-completion of imports.
Loading