|
| 1 | +import importlib |
1 | 2 | import io
|
2 | 3 | import itertools
|
3 | 4 | import os
|
|
26 | 27 | code_to_events,
|
27 | 28 | )
|
28 | 29 | from _pyrepl.console import Event
|
29 |
| -from _pyrepl._module_completer import ImportParser, ModuleCompleter |
30 |
| -from _pyrepl.readline import (ReadlineAlikeReader, ReadlineConfig, |
31 |
| - _ReadlineWrapper) |
| 30 | +from _pyrepl._module_completer import ( |
| 31 | + ImportParser, |
| 32 | + ModuleCompleter, |
| 33 | + HARDCODED_SUBMODULES, |
| 34 | +) |
| 35 | +from _pyrepl.readline import ( |
| 36 | + ReadlineAlikeReader, |
| 37 | + ReadlineConfig, |
| 38 | + _ReadlineWrapper, |
| 39 | +) |
32 | 40 | from _pyrepl.readline import multiline_input as readline_multiline_input
|
33 | 41 |
|
34 | 42 | try:
|
@@ -930,7 +938,6 @@ def test_func(self):
|
930 | 938 |
|
931 | 939 | class TestPyReplModuleCompleter(TestCase):
|
932 | 940 | def setUp(self):
|
933 |
| - import importlib |
934 | 941 | # Make iter_modules() search only the standard library.
|
935 | 942 | # This makes the test more reliable in case there are
|
936 | 943 | # other user packages/scripts on PYTHONPATH which can
|
@@ -1013,14 +1020,6 @@ def test_sub_module_private_completions(self):
|
1013 | 1020 | self.assertEqual(output, expected)
|
1014 | 1021 |
|
1015 | 1022 | def test_builtin_completion_top_level(self):
|
1016 |
| - import importlib |
1017 |
| - # Make iter_modules() search only the standard library. |
1018 |
| - # This makes the test more reliable in case there are |
1019 |
| - # other user packages/scripts on PYTHONPATH which can |
1020 |
| - # intefere with the completions. |
1021 |
| - lib_path = os.path.dirname(importlib.__path__[0]) |
1022 |
| - sys.path = [lib_path] |
1023 |
| - |
1024 | 1023 | cases = (
|
1025 | 1024 | ("import bui\t\n", "import builtins"),
|
1026 | 1025 | ("from bui\t\n", "from builtins"),
|
@@ -1076,6 +1075,32 @@ def test_no_fallback_on_regular_completion(self):
|
1076 | 1075 | output = reader.readline()
|
1077 | 1076 | self.assertEqual(output, expected)
|
1078 | 1077 |
|
| 1078 | + def test_hardcoded_stdlib_submodules(self): |
| 1079 | + cases = ( |
| 1080 | + ("import collections.\t\n", "import collections.abc"), |
| 1081 | + ("from os import \t\n", "from os import path"), |
| 1082 | + ("import xml.parsers.expat.\t\te\t\n\n", "import xml.parsers.expat.errors"), |
| 1083 | + ("from xml.parsers.expat import \t\tm\t\n\n", "from xml.parsers.expat import model"), |
| 1084 | + ) |
| 1085 | + for code, expected in cases: |
| 1086 | + with self.subTest(code=code): |
| 1087 | + events = code_to_events(code) |
| 1088 | + reader = self.prepare_reader(events, namespace={}) |
| 1089 | + output = reader.readline() |
| 1090 | + self.assertEqual(output, expected) |
| 1091 | + |
| 1092 | + def test_hardcoded_stdlib_submodules_not_proposed_if_local_import(self): |
| 1093 | + with tempfile.TemporaryDirectory() as _dir: |
| 1094 | + dir = pathlib.Path(_dir) |
| 1095 | + (dir / "collections").mkdir() |
| 1096 | + (dir / "collections" / "__init__.py").touch() |
| 1097 | + (dir / "collections" / "foo.py").touch() |
| 1098 | + with patch.object(sys, "path", [dir, *sys.path]): |
| 1099 | + events = code_to_events("import collections.\t\n") |
| 1100 | + reader = self.prepare_reader(events, namespace={}) |
| 1101 | + output = reader.readline() |
| 1102 | + self.assertEqual(output, "import collections.foo") |
| 1103 | + |
1079 | 1104 | def test_get_path_and_prefix(self):
|
1080 | 1105 | cases = (
|
1081 | 1106 | ('', ('', '')),
|
@@ -1204,6 +1229,19 @@ def test_parse_error(self):
|
1204 | 1229 | with self.subTest(code=code):
|
1205 | 1230 | self.assertEqual(actual, None)
|
1206 | 1231 |
|
| 1232 | + |
| 1233 | +class TestHardcodedSubmodules(TestCase): |
| 1234 | + def test_hardcoded_stdlib_submodules_are_importable(self): |
| 1235 | + for parent_path, submodules in HARDCODED_SUBMODULES.items(): |
| 1236 | + for module_name in submodules: |
| 1237 | + path = f"{parent_path}.{module_name}" |
| 1238 | + with self.subTest(path=path): |
| 1239 | + # We can't use importlib.util.find_spec here, |
| 1240 | + # since some hardcoded submodules parents are |
| 1241 | + # not proper packages |
| 1242 | + importlib.import_module(path) |
| 1243 | + |
| 1244 | + |
1207 | 1245 | class TestPasteEvent(TestCase):
|
1208 | 1246 | def prepare_reader(self, events):
|
1209 | 1247 | console = FakeConsole(events)
|
|
0 commit comments