[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,9 +1,12 @@
from __future__ import annotations
import importlib
import os
import pkgutil
import sys
import token
import tokenize
from importlib.machinery import FileFinder
from io import StringIO
from contextlib import contextmanager
from dataclasses import dataclass
@ -16,6 +19,15 @@
from typing import Any, Iterable, Iterator, Mapping
HARDCODED_SUBMODULES = {
# Standard library submodules that are not detected by pkgutil.iter_modules
# but can be imported, so should be proposed in completion
"collections": ["abc"],
"os": ["path"],
"xml.parsers.expat": ["errors", "model"],
}
def make_default_module_completer() -> ModuleCompleter:
# Inside pyrepl, __package__ is set to None by default
return ModuleCompleter(namespace={'__package__': None})
@ -41,6 +53,7 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None:
self.namespace = namespace or {}
self._global_cache: list[pkgutil.ModuleInfo] = []
self._curr_sys_path: list[str] = sys.path[:]
self._stdlib_path = os.path.dirname(importlib.__path__[0])
def get_completions(self, line: str) -> list[str] | None:
"""Return the next possible import completions for 'line'."""
@ -95,12 +108,26 @@ def _find_modules(self, path: str, prefix: str) -> list[str]:
return []
modules: Iterable[pkgutil.ModuleInfo] = self.global_cache
is_stdlib_import: bool | None = None
for segment in path.split('.'):
modules = [mod_info for mod_info in modules
if mod_info.ispkg and mod_info.name == segment]
if is_stdlib_import is None:
# Top-level import decide if we import from stdlib or not
is_stdlib_import = all(
self._is_stdlib_module(mod_info) for mod_info in modules
)
modules = self.iter_submodules(modules)
return [module.name for module in modules
if self.is_suggestion_match(module.name, prefix)]
module_names = [module.name for module in modules]
if is_stdlib_import:
module_names.extend(HARDCODED_SUBMODULES.get(path, ()))
return [module_name for module_name in module_names
if self.is_suggestion_match(module_name, prefix)]
def _is_stdlib_module(self, module_info: pkgutil.ModuleInfo) -> bool:
return (isinstance(module_info.module_finder, FileFinder)
and module_info.module_finder.path == self._stdlib_path)
def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
if prefix: