[3.14] gh-69605: Hardcode some stdlib submodules in PyREPL module completion (os.path, collections.abc...) (GH-138268) (GH-138943)

(cherry picked from commit 537133d2b6)

Co-authored-by: Loïc Simon <loic.simon@napta.io>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
Miss Islington (bot) 2025-10-07 21:24:01 +02:00 committed by GitHub
parent cde02ae782
commit d912e9a852
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 80 additions and 14 deletions

View file

@ -1,3 +1,4 @@
import importlib
import io
import itertools
import os
@ -26,9 +27,16 @@
code_to_events,
)
from _pyrepl.console import Event
from _pyrepl._module_completer import ImportParser, ModuleCompleter
from _pyrepl.readline import (ReadlineAlikeReader, ReadlineConfig,
_ReadlineWrapper)
from _pyrepl._module_completer import (
ImportParser,
ModuleCompleter,
HARDCODED_SUBMODULES,
)
from _pyrepl.readline import (
ReadlineAlikeReader,
ReadlineConfig,
_ReadlineWrapper,
)
from _pyrepl.readline import multiline_input as readline_multiline_input
try:
@ -930,7 +938,6 @@ def test_func(self):
class TestPyReplModuleCompleter(TestCase):
def setUp(self):
import importlib
# Make iter_modules() search only the standard library.
# This makes the test more reliable in case there are
# other user packages/scripts on PYTHONPATH which can
@ -1013,14 +1020,6 @@ def test_sub_module_private_completions(self):
self.assertEqual(output, expected)
def test_builtin_completion_top_level(self):
import importlib
# Make iter_modules() search only the standard library.
# This makes the test more reliable in case there are
# other user packages/scripts on PYTHONPATH which can
# intefere with the completions.
lib_path = os.path.dirname(importlib.__path__[0])
sys.path = [lib_path]
cases = (
("import bui\t\n", "import builtins"),
("from bui\t\n", "from builtins"),
@ -1076,6 +1075,32 @@ def test_no_fallback_on_regular_completion(self):
output = reader.readline()
self.assertEqual(output, expected)
def test_hardcoded_stdlib_submodules(self):
cases = (
("import collections.\t\n", "import collections.abc"),
("from os import \t\n", "from os import path"),
("import xml.parsers.expat.\t\te\t\n\n", "import xml.parsers.expat.errors"),
("from xml.parsers.expat import \t\tm\t\n\n", "from xml.parsers.expat import model"),
)
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_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 = (
('', ('', '')),
@ -1204,6 +1229,19 @@ def test_parse_error(self):
with self.subTest(code=code):
self.assertEqual(actual, None)
class TestHardcodedSubmodules(TestCase):
def test_hardcoded_stdlib_submodules_are_importable(self):
for parent_path, submodules in HARDCODED_SUBMODULES.items():
for module_name in submodules:
path = f"{parent_path}.{module_name}"
with self.subTest(path=path):
# We can't use importlib.util.find_spec here,
# since some hardcoded submodules parents are
# not proper packages
importlib.import_module(path)
class TestPasteEvent(TestCase):
def prepare_reader(self, events):
console = FakeConsole(events)