|
16 | 16 | TYPE_CHECKING = False |
17 | 17 |
|
18 | 18 | if TYPE_CHECKING: |
| 19 | + from types import ModuleType |
19 | 20 | from typing import Any, Iterable, Iterator, Mapping |
20 | 21 | from .types import CompletionAction |
21 | 22 |
|
|
28 | 29 | "xml.parsers.expat": ["errors", "model"], |
29 | 30 | } |
30 | 31 |
|
| 32 | +AUTO_IMPORT_BLACKLIST = { |
| 33 | + # Standard library modules/submodules that have import side effects |
| 34 | + # and must not be automatically imported to complete attributes |
| 35 | + "antigravity", |
| 36 | + "this", |
| 37 | + "idlelib.idle", |
| 38 | +} |
| 39 | + |
31 | 40 |
|
32 | 41 | def make_default_module_completer() -> ModuleCompleter: |
33 | 42 | # Inside pyrepl, __package__ is set to None by default |
@@ -157,15 +166,7 @@ def _find_attributes(self, path: str, prefix: str) -> tuple[list[str], Completio |
157 | 166 | if not imported_module: |
158 | 167 | if path in self._failed_imports: # Do not propose to import again |
159 | 168 | return [], None |
160 | | - root = path.split(".")[0] |
161 | | - mod_info = next((m for m in self.global_cache if m.name == root), |
162 | | - None) |
163 | | - if mod_info and self._is_stdlib_module(mod_info): |
164 | | - # Stdlib module: auto-import (no risk of dangerous side-effect) |
165 | | - try: |
166 | | - imported_module = importlib.import_module(path) |
167 | | - except Exception: |
168 | | - sys.modules.pop(path, None) # Clean half-imported module |
| 169 | + imported_module = self._maybe_import_module(path) |
169 | 170 | if not imported_module: |
170 | 171 | return [], self._get_import_completion_action(path) |
171 | 172 | try: |
@@ -246,6 +247,21 @@ def global_cache(self) -> list[pkgutil.ModuleInfo]: |
246 | 247 | self._global_cache = list(pkgutil.iter_modules()) |
247 | 248 | return self._global_cache |
248 | 249 |
|
| 250 | + def _maybe_import_module(self, fqname: str) -> ModuleType | None: |
| 251 | + if fqname in AUTO_IMPORT_BLACKLIST or fqname.endswith(".__main__"): |
| 252 | + # Special-cased modules with known import side-effects |
| 253 | + return None |
| 254 | + root = fqname.split(".")[0] |
| 255 | + mod_info = next((m for m in self.global_cache if m.name == root), None) |
| 256 | + if not mod_info or not self._is_stdlib_module(mod_info): |
| 257 | + # Only import stdlib modules (no risk of import side-effects) |
| 258 | + return None |
| 259 | + try: |
| 260 | + return importlib.import_module(fqname) |
| 261 | + except Exception: |
| 262 | + sys.modules.pop(fqname, None) # Clean half-imported module |
| 263 | + return None |
| 264 | + |
249 | 265 | def _get_import_completion_action(self, path: str) -> CompletionAction: |
250 | 266 | prompt = ("[ module not imported, press again to import it " |
251 | 267 | "and propose attributes ]") |
|
0 commit comments