|
| 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: |
@@ -922,7 +930,6 @@ def test_func(self): |
922 | 930 |
|
923 | 931 | class TestPyReplModuleCompleter(TestCase): |
924 | 932 | def setUp(self): |
925 | | - import importlib |
926 | 933 | # Make iter_modules() search only the standard library. |
927 | 934 | # This makes the test more reliable in case there are |
928 | 935 | # other user packages/scripts on PYTHONPATH which can |
@@ -1005,14 +1012,6 @@ def test_sub_module_private_completions(self): |
1005 | 1012 | self.assertEqual(output, expected) |
1006 | 1013 |
|
1007 | 1014 | def test_builtin_completion_top_level(self): |
1008 | | - import importlib |
1009 | | - # Make iter_modules() search only the standard library. |
1010 | | - # This makes the test more reliable in case there are |
1011 | | - # other user packages/scripts on PYTHONPATH which can |
1012 | | - # intefere with the completions. |
1013 | | - lib_path = os.path.dirname(importlib.__path__[0]) |
1014 | | - sys.path = [lib_path] |
1015 | | - |
1016 | 1015 | cases = ( |
1017 | 1016 | ("import bui\t\n", "import builtins"), |
1018 | 1017 | ("from bui\t\n", "from builtins"), |
@@ -1068,6 +1067,32 @@ def test_no_fallback_on_regular_completion(self): |
1068 | 1067 | output = reader.readline() |
1069 | 1068 | self.assertEqual(output, expected) |
1070 | 1069 |
|
| 1070 | + def test_hardcoded_stdlib_submodules(self): |
| 1071 | + cases = ( |
| 1072 | + ("import collections.\t\n", "import collections.abc"), |
| 1073 | + ("from os import \t\n", "from os import path"), |
| 1074 | + ("import xml.parsers.expat.\t\te\t\n\n", "import xml.parsers.expat.errors"), |
| 1075 | + ("from xml.parsers.expat import \t\tm\t\n\n", "from xml.parsers.expat import model"), |
| 1076 | + ) |
| 1077 | + for code, expected in cases: |
| 1078 | + with self.subTest(code=code): |
| 1079 | + events = code_to_events(code) |
| 1080 | + reader = self.prepare_reader(events, namespace={}) |
| 1081 | + output = reader.readline() |
| 1082 | + self.assertEqual(output, expected) |
| 1083 | + |
| 1084 | + def test_hardcoded_stdlib_submodules_not_proposed_if_local_import(self): |
| 1085 | + with tempfile.TemporaryDirectory() as _dir: |
| 1086 | + dir = pathlib.Path(_dir) |
| 1087 | + (dir / "collections").mkdir() |
| 1088 | + (dir / "collections" / "__init__.py").touch() |
| 1089 | + (dir / "collections" / "foo.py").touch() |
| 1090 | + with patch.object(sys, "path", [dir, *sys.path]): |
| 1091 | + events = code_to_events("import collections.\t\n") |
| 1092 | + reader = self.prepare_reader(events, namespace={}) |
| 1093 | + output = reader.readline() |
| 1094 | + self.assertEqual(output, "import collections.foo") |
| 1095 | + |
1071 | 1096 | def test_get_path_and_prefix(self): |
1072 | 1097 | cases = ( |
1073 | 1098 | ('', ('', '')), |
@@ -1196,6 +1221,19 @@ def test_parse_error(self): |
1196 | 1221 | with self.subTest(code=code): |
1197 | 1222 | self.assertEqual(actual, None) |
1198 | 1223 |
|
| 1224 | + |
| 1225 | +class TestHardcodedSubmodules(TestCase): |
| 1226 | + def test_hardcoded_stdlib_submodules_are_importable(self): |
| 1227 | + for parent_path, submodules in HARDCODED_SUBMODULES.items(): |
| 1228 | + for module_name in submodules: |
| 1229 | + path = f"{parent_path}.{module_name}" |
| 1230 | + with self.subTest(path=path): |
| 1231 | + # We can't use importlib.util.find_spec here, |
| 1232 | + # since some hardcoded submodules parents are |
| 1233 | + # not proper packages |
| 1234 | + importlib.import_module(path) |
| 1235 | + |
| 1236 | + |
1199 | 1237 | class TestPasteEvent(TestCase): |
1200 | 1238 | def prepare_reader(self, events): |
1201 | 1239 | console = FakeConsole(events) |
|
0 commit comments