Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd912e9a

Browse files
miss-islingtonloic-simonambv
authored
[3.14]gh-69605: Hardcode some stdlib submodules in PyREPL module completion (os.path, collections.abc...) (GH-138268) (GH-138943)
(cherry picked from commit537133d)Co-authored-by: Loïc Simon <loic.simon@napta.io>Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parentcde02ae commitd912e9a

File tree

3 files changed

+80
-14
lines changed

3 files changed

+80
-14
lines changed

‎Lib/_pyrepl/_module_completer.py‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from __future__importannotations
22

3+
importimportlib
4+
importos
35
importpkgutil
46
importsys
57
importtoken
68
importtokenize
9+
fromimportlib.machineryimportFileFinder
710
fromioimportStringIO
811
fromcontextlibimportcontextmanager
912
fromdataclassesimportdataclass
@@ -16,6 +19,15 @@
1619
fromtypingimportAny,Iterable,Iterator,Mapping
1720

1821

22+
HARDCODED_SUBMODULES= {
23+
# Standard library submodules that are not detected by pkgutil.iter_modules
24+
# but can be imported, so should be proposed in completion
25+
"collections": ["abc"],
26+
"os": ["path"],
27+
"xml.parsers.expat": ["errors","model"],
28+
}
29+
30+
1931
defmake_default_module_completer()->ModuleCompleter:
2032
# Inside pyrepl, __package__ is set to None by default
2133
returnModuleCompleter(namespace={'__package__':None})
@@ -41,6 +53,7 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None:
4153
self.namespace=namespaceor {}
4254
self._global_cache:list[pkgutil.ModuleInfo]= []
4355
self._curr_sys_path:list[str]=sys.path[:]
56+
self._stdlib_path=os.path.dirname(importlib.__path__[0])
4457

4558
defget_completions(self,line:str)->list[str]|None:
4659
"""Return the next possible import completions for 'line'."""
@@ -95,12 +108,26 @@ def _find_modules(self, path: str, prefix: str) -> list[str]:
95108
return []
96109

97110
modules:Iterable[pkgutil.ModuleInfo]=self.global_cache
111+
is_stdlib_import:bool|None=None
98112
forsegmentinpath.split('.'):
99113
modules= [mod_infoformod_infoinmodules
100114
ifmod_info.ispkgandmod_info.name==segment]
115+
ifis_stdlib_importisNone:
116+
# Top-level import decide if we import from stdlib or not
117+
is_stdlib_import=all(
118+
self._is_stdlib_module(mod_info)formod_infoinmodules
119+
)
101120
modules=self.iter_submodules(modules)
102-
return [module.nameformoduleinmodules
103-
ifself.is_suggestion_match(module.name,prefix)]
121+
122+
module_names= [module.nameformoduleinmodules]
123+
ifis_stdlib_import:
124+
module_names.extend(HARDCODED_SUBMODULES.get(path, ()))
125+
return [module_nameformodule_nameinmodule_names
126+
ifself.is_suggestion_match(module_name,prefix)]
127+
128+
def_is_stdlib_module(self,module_info:pkgutil.ModuleInfo)->bool:
129+
return (isinstance(module_info.module_finder,FileFinder)
130+
andmodule_info.module_finder.path==self._stdlib_path)
104131

105132
defis_suggestion_match(self,module_name:str,prefix:str)->bool:
106133
ifprefix:

‎Lib/test/test_pyrepl/test_pyrepl.py‎

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
importimportlib
12
importio
23
importitertools
34
importos
@@ -26,9 +27,16 @@
2627
code_to_events,
2728
)
2829
from_pyrepl.consoleimportEvent
29-
from_pyrepl._module_completerimportImportParser,ModuleCompleter
30-
from_pyrepl.readlineimport (ReadlineAlikeReader,ReadlineConfig,
31-
_ReadlineWrapper)
30+
from_pyrepl._module_completerimport (
31+
ImportParser,
32+
ModuleCompleter,
33+
HARDCODED_SUBMODULES,
34+
)
35+
from_pyrepl.readlineimport (
36+
ReadlineAlikeReader,
37+
ReadlineConfig,
38+
_ReadlineWrapper,
39+
)
3240
from_pyrepl.readlineimportmultiline_inputasreadline_multiline_input
3341

3442
try:
@@ -930,7 +938,6 @@ def test_func(self):
930938

931939
classTestPyReplModuleCompleter(TestCase):
932940
defsetUp(self):
933-
importimportlib
934941
# Make iter_modules() search only the standard library.
935942
# This makes the test more reliable in case there are
936943
# other user packages/scripts on PYTHONPATH which can
@@ -1013,14 +1020,6 @@ def test_sub_module_private_completions(self):
10131020
self.assertEqual(output,expected)
10141021

10151022
deftest_builtin_completion_top_level(self):
1016-
importimportlib
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-
10241023
cases= (
10251024
("import bui\t\n","import builtins"),
10261025
("from bui\t\n","from builtins"),
@@ -1076,6 +1075,32 @@ def test_no_fallback_on_regular_completion(self):
10761075
output=reader.readline()
10771076
self.assertEqual(output,expected)
10781077

1078+
deftest_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+
forcode,expectedincases:
1086+
withself.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+
deftest_hardcoded_stdlib_submodules_not_proposed_if_local_import(self):
1093+
withtempfile.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+
withpatch.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+
10791104
deftest_get_path_and_prefix(self):
10801105
cases= (
10811106
('', ('','')),
@@ -1204,6 +1229,19 @@ def test_parse_error(self):
12041229
withself.subTest(code=code):
12051230
self.assertEqual(actual,None)
12061231

1232+
1233+
classTestHardcodedSubmodules(TestCase):
1234+
deftest_hardcoded_stdlib_submodules_are_importable(self):
1235+
forparent_path,submodulesinHARDCODED_SUBMODULES.items():
1236+
formodule_nameinsubmodules:
1237+
path=f"{parent_path}.{module_name}"
1238+
withself.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+
12071245
classTestPasteEvent(TestCase):
12081246
defprepare_reader(self,events):
12091247
console=FakeConsole(events)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix some standard library submodules missing from the:term:`REPL` auto-completion of imports.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp