|
| 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