Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6ed2776
PyREPL module completion: check for already imported modules
loic-simon Sep 30, 2025
48fd43f
Add blurb
loic-simon Sep 30, 2025
7ac428e
Better convey intent
loic-simon Oct 1, 2025
6515e2f
[TEMP] debug tests on windows using modern technology (print statements)
loic-simon Oct 1, 2025
7dbb906
[TEMP] More debugging, where is my module??
loic-simon Oct 2, 2025
ac3065a
[TEMP] More debugging, where is my module?? (bis)
loic-simon Oct 2, 2025
75a33da
[TEMP] Day 57, deep into debugging, I still don't know where is my mo…
loic-simon Oct 2, 2025
ce124b1
[TEMP] Moar logs
loic-simon Oct 2, 2025
ee7047f
Merge branch 'main' into pyrepl-module-completion-check-for-already-i…
loic-simon Oct 3, 2025
3f362cd
[TEMP] Is it a FileFinder cache issue??
loic-simon Oct 3, 2025
ed8ce73
[TEMP] Looks like a cache issue indeed
loic-simon Oct 3, 2025
19c49bb
Tests: clean FileFinder cache
loic-simon Oct 3, 2025
16e44af
Remove all debugging junk
loic-simon Oct 3, 2025
14f6175
Small if refactor
loic-simon Oct 5, 2025
bdd7bdf
Merge branch 'pyrepl-module-completion-check-for-already-imported-mod…
loic-simon Oct 5, 2025
78e4737
Full test coverage for new code
loic-simon Oct 11, 2025
e3f1ddb
Merge branch 'python:main' into pyrepl-module-completion-check-for-al…
loic-simon Oct 11, 2025
2644400
Merge branch 'main' into pyrepl-module-completion-check-for-already-i…
tomasr8 Dec 28, 2025
5fa70cf
Merge branch 'main' into pyrepl-module-completion-check-for-already-i…
loic-simon Jan 1, 2026
d332e14
Check __spec__.has_location + refactor
loic-simon Jan 1, 2026
1a5327c
Rename private helper
loic-simon Jan 1, 2026
d48f243
Simplify implementation
loic-simon Jan 2, 2026
e235e20
Fix find_spec call
loic-simon Jan 2, 2026
f6757fe
Remove unused import
loic-simon Jan 2, 2026
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
20 changes: 19 additions & 1 deletion Lib/_pyrepl/_module_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,25 @@ def _find_modules(self, path: str, prefix: str) -> list[str]:
if path is None:
return []

modules: Iterable[pkgutil.ModuleInfo] = self.global_cache
modules: Iterable[pkgutil.ModuleInfo]
imported_module = sys.modules.get(path.split('.')[0])
if imported_module:
# Module already imported: only look for its submodules,
# even if a module with the same name would be higher in path
imported_path = (imported_module.__spec__
and imported_module.__spec__.origin)
if imported_path:
if os.path.basename(imported_path) == "__init__.py": # package
imported_path = os.path.dirname(imported_path)
import_location = os.path.dirname(imported_path)
modules = list(pkgutil.iter_modules([import_location]))
else:
# Module already imported but without spec/origin:
# propose no suggestions
modules = []
Comment thread
loic-simon marked this conversation as resolved.
Outdated
else:
modules = self.global_cache

is_stdlib_import: bool | None = None
for segment in path.split('.'):
modules = [mod_info for mod_info in modules
Expand Down
69 changes: 67 additions & 2 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,17 +1090,82 @@ def test_hardcoded_stdlib_submodules(self):
self.assertEqual(output, expected)

def test_hardcoded_stdlib_submodules_not_proposed_if_local_import(self):
with tempfile.TemporaryDirectory() as _dir:
with (tempfile.TemporaryDirectory() as _dir,
patch.object(sys, "modules", {})): # hide imported module
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]):
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_already_imported_stdlib_module_no_other_suggestions(self):
with (tempfile.TemporaryDirectory() as _dir,
patch.object(sys, "path", [_dir, *sys.path])):
dir = pathlib.Path(_dir)
(dir / "collections").mkdir()
(dir / "collections" / "__init__.py").touch()
(dir / "collections" / "foo.py").touch()

# collections found in dir, but was already imported
# from stdlib at startup -> suggest stdlib submodules only
events = code_to_events("import collections.\t\n")
reader = self.prepare_reader(events, namespace={})
output = reader.readline()
self.assertEqual(output, "import collections.abc")

def test_already_imported_custom_module_no_other_suggestions(self):
with (tempfile.TemporaryDirectory() as _dir1,
tempfile.TemporaryDirectory() as _dir2,
patch.object(sys, "path", [_dir2, _dir1, *sys.path])):
dir1 = pathlib.Path(_dir1)
(dir1 / "mymodule").mkdir()
(dir1 / "mymodule" / "__init__.py").touch()
(dir1 / "mymodule" / "foo.py").touch()
importlib.import_module("mymodule")

dir2 = pathlib.Path(_dir2)
(dir2 / "mymodule").mkdir()
(dir2 / "mymodule" / "__init__.py").touch()
(dir2 / "mymodule" / "bar.py").touch()
# mymodule found in dir2 before dir1, but it was already imported
# from dir1 -> suggest dir1 submodules only
events = code_to_events("import mymodule.\t\n")
reader = self.prepare_reader(events, namespace={})
output = reader.readline()
self.assertEqual(output, "import mymodule.foo")

del sys.modules["mymodule"]
# mymodule not imported anymore -> suggest dir2 submodules
events = code_to_events("import mymodule.\t\n")
reader = self.prepare_reader(events, namespace={})
output = reader.readline()
self.assertEqual(output, "import mymodule.bar")

def test_already_imported_custom_file_no_suggestions(self):
# Same as before, but mymodule from dir1 has no submodules
# -> propose nothing
with (tempfile.TemporaryDirectory() as _dir1,
tempfile.TemporaryDirectory() as _dir2,
patch.object(sys, "path", [_dir2, _dir1, *sys.path])):
dir1 = pathlib.Path(_dir1)
(dir1 / "mymodule").mkdir()
(dir1 / "mymodule.py").touch()
importlib.import_module("mymodule")

dir2 = pathlib.Path(_dir2)
(dir2 / "mymodule").mkdir()
(dir2 / "mymodule" / "__init__.py").touch()
(dir2 / "mymodule" / "bar.py").touch()
events = code_to_events("import mymodule.\t\n")
reader = self.prepare_reader(events, namespace={})
output = reader.readline()
self.assertEqual(output, "import mymodule.")
del sys.modules["mymodule"]

def test_get_path_and_prefix(self):
cases = (
('', ('', '')),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix edge-cases around already imported modules in the :term:`REPL`
auto-completion of imports.
Loading