Skip to content

Commit a403b1c

Browse files
committed
Improve & test auto-import blacklist
1 parent 134fd78 commit a403b1c

2 files changed

Lines changed: 61 additions & 5 deletions

File tree

Lib/_pyrepl/_module_completer.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import importlib
44
import os
55
import pkgutil
6+
import re
67
import sys
78
import token
89
import tokenize
@@ -32,9 +33,11 @@
3233
AUTO_IMPORT_BLACKLIST = {
3334
# Standard library modules/submodules that have import side effects
3435
# and must not be automatically imported to complete attributes
35-
"antigravity",
36-
"this",
37-
"idlelib.idle",
36+
re.compile(r"antigravity"),
37+
re.compile(r"this"),
38+
re.compile(r"idlelib\..+"),
39+
re.compile(r"test\..+"),
40+
re.compile(r".+\.__main__"),
3841
}
3942

4043

@@ -259,7 +262,7 @@ def global_cache(self) -> list[pkgutil.ModuleInfo]:
259262
return self._global_cache
260263

261264
def _maybe_import_module(self, fqname: str) -> ModuleType | None:
262-
if fqname in AUTO_IMPORT_BLACKLIST or fqname.endswith(".__main__"):
265+
if any(pattern.fullmatch(fqname) for pattern in AUTO_IMPORT_BLACKLIST):
263266
# Special-cased modules with known import side-effects
264267
return None
265268
root = fqname.split(".")[0]

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from collections.abc import Iterator
2+
import contextlib
13
import importlib
24
import io
35
import itertools
@@ -13,7 +15,14 @@
1315
from pkgutil import ModuleInfo
1416
from unittest import TestCase, skipUnless, skipIf, SkipTest
1517
from unittest.mock import patch
16-
from test.support import force_not_colorized, make_clean_env, Py_DEBUG
18+
import warnings
19+
from test.support import (
20+
captured_stdout,
21+
captured_stderr,
22+
force_not_colorized,
23+
make_clean_env,
24+
Py_DEBUG,
25+
)
1726
from test.support import has_subprocess_support, SHORT_TIMEOUT, STDLIB_DIR
1827
from test.support.import_helper import import_module
1928
from test.support.os_helper import EnvironmentVarGuard, unlink
@@ -1512,6 +1521,50 @@ def test_suggestions_and_messages(self) -> None:
15121521
new_imports = sys.modules.keys() - _imported
15131522
self.assertSetEqual(new_imports, expected_imports)
15141523

1524+
class TestModuleCompleterAutomaticImports(TestCase):
1525+
"""Out of TestPyReplModuleCompleter case because it blocks module import."""
1526+
1527+
@classmethod
1528+
def setUpClass(cls) -> None:
1529+
super().setUpClass()
1530+
cls._audit_events: set[str] | None = None
1531+
def _hook(name: str, _args: tuple):
1532+
if cls._audit_events is not None:
1533+
cls._audit_events.add(name)
1534+
sys.addaudithook(_hook)
1535+
1536+
@classmethod
1537+
@contextlib.contextmanager
1538+
def _capture_audit_events(cls) -> Iterator[set[str]]:
1539+
cls._audit_events = set()
1540+
try:
1541+
yield cls._audit_events
1542+
finally:
1543+
cls._audit_events = None
1544+
1545+
def test_no_side_effects(self):
1546+
from test.test___all__ import AllTest # TODO: extract to a helper?
1547+
1548+
completer = ModuleCompleter()
1549+
for _, modname in AllTest().walk_modules(completer._stdlib_path, ""):
1550+
with self.subTest(modname=modname):
1551+
with (captured_stdout() as out,
1552+
captured_stderr() as err,
1553+
self._capture_audit_events() as audit_events,
1554+
patch("tkinter._tkinter.create") as tk_mock,
1555+
warnings.catch_warnings(action="ignore"),
1556+
patch.dict(sys.modules)):
1557+
completer._maybe_import_module(modname)
1558+
# Test no module is imported that
1559+
# 1. prints any text
1560+
self.assertEqual(out.getvalue(), "")
1561+
self.assertEqual(err.getvalue(), "")
1562+
# 2. spawn any subprocess (eg. webbrowser.open)
1563+
self.assertNotIn("subprocess.Popen", audit_events)
1564+
# 3. launch a Tk window
1565+
tk_mock.assert_not_called()
1566+
1567+
15151568
class TestHardcodedSubmodules(TestCase):
15161569
@patch.dict(sys.modules)
15171570
def test_hardcoded_stdlib_submodules_are_importable(self):

0 commit comments

Comments
 (0)