@@ -1325,6 +1325,61 @@ def test_attribute_entry_removed_on_reification(self):
13251325 """ )
13261326 assert_python_ok ("-c" , code )
13271327
1328+ def test_already_loaded_attribute_is_not_tracked (self ):
1329+ code = textwrap .dedent ("""
1330+ import sys
1331+ import math
1332+
1333+ lazy from math import pi
1334+ assert "math.pi" not in sys.lazy_modules, sys.lazy_modules
1335+ assert pi == math.pi
1336+ """ )
1337+ assert_python_ok ("-c" , code )
1338+
1339+ def test_cached_lazy_attribute_keeps_its_import_context (self ):
1340+ code = textwrap .dedent ("""
1341+ import builtins
1342+ import sys
1343+ import test.test_lazy_import.data.basic_from_unused
1344+
1345+ holder = "test.test_lazy_import.data.basic_from_unused"
1346+ target = "test.test_lazy_import.data.basic2"
1347+ default_import = builtins.__import__
1348+
1349+ def import_hook(name, *args):
1350+ if name == holder:
1351+ raise RuntimeError("cached placeholder imported its holder")
1352+ return default_import(name, *args)
1353+
1354+ namespace = {
1355+ "__builtins__": dict(builtins.__dict__, __import__=import_hook),
1356+ "__name__": "cached_import_test",
1357+ }
1358+ exec(f"lazy from {holder} import basic2", namespace)
1359+ assert holder + ".basic2" not in sys.lazy_modules, sys.lazy_modules
1360+ assert target in sys.lazy_modules, sys.lazy_modules
1361+ exec("assert basic2.x == 42", namespace)
1362+ assert target not in sys.lazy_modules, sys.lazy_modules
1363+ """ )
1364+ assert_python_ok ("-c" , code )
1365+
1366+ def test_cached_attribute_keeps_pending_module_tracked (self ):
1367+ code = textwrap .dedent ("""
1368+ import sys
1369+ import test.test_lazy_import.data.pkg as pkg
1370+ pkg.b = 42
1371+
1372+ lazy import test.test_lazy_import.data.pkg.b as pending
1373+ lazy from test.test_lazy_import.data.pkg import b
1374+ name = "test.test_lazy_import.data.pkg.b"
1375+ assert b == 42, b
1376+ assert name not in sys.modules, sys.modules
1377+ assert name in sys.lazy_modules, sys.lazy_modules
1378+ assert pending.foo() == "foo"
1379+ assert name not in sys.lazy_modules, sys.lazy_modules
1380+ """ )
1381+ assert_python_ok ("-c" , code )
1382+
13281383 def test_failed_reification_stays_tracked (self ):
13291384 """A lazy import that fails to resolve must stay tracked."""
13301385 code = textwrap .dedent ("""
@@ -1391,6 +1446,93 @@ def __spec__(self):
13911446 """ )
13921447 assert_python_ok ("-c" , code )
13931448
1449+ def test_spec_initializing_descriptor_is_not_run (self ):
1450+ code = textwrap .dedent ("""
1451+ import sys
1452+ import types
1453+
1454+ class Spec:
1455+ @property
1456+ def _initializing(self):
1457+ raise RuntimeError("_initializing descriptor was run")
1458+
1459+ @property
1460+ def __dict__(self):
1461+ raise RuntimeError("__dict__ descriptor was run")
1462+
1463+ class SlottedSpec:
1464+ __slots__ = ()
1465+
1466+ @property
1467+ def _initializing(self):
1468+ raise RuntimeError("_initializing descriptor was run")
1469+
1470+ for spec in (Spec(), SlottedSpec()):
1471+ module = types.ModuleType("custom_spec")
1472+ module.__spec__ = spec
1473+ sys.modules["custom_spec"] = module
1474+ lazy import custom_spec
1475+ assert "custom_spec" in sys.lazy_modules, sys.lazy_modules
1476+ """ )
1477+ assert_python_ok ("-c" , code )
1478+
1479+ def test_cached_attribute_does_not_check_spec_twice (self ):
1480+ code = textwrap .dedent ("""
1481+ import sys
1482+ import types
1483+
1484+ class Spec:
1485+ def __init__(self):
1486+ self.calls = 0
1487+
1488+ @property
1489+ def _initializing(self):
1490+ self.calls += 1
1491+ if self.calls == 2:
1492+ raise RuntimeError("_initializing was read twice")
1493+ return False
1494+
1495+ module = types.ModuleType("cached_spec")
1496+ module.__spec__ = Spec()
1497+ module.attr = 1
1498+ sys.modules["cached_spec"] = module
1499+ lazy from cached_spec import attr
1500+ assert attr == 1, attr
1501+ """ )
1502+ assert_python_ok ("-c" , code )
1503+
1504+ def test_spec_builtin_false_initializing_is_not_tracked (self ):
1505+ code = textwrap .dedent ("""
1506+ import sys
1507+ import types
1508+
1509+ for flag in (False, None, 0, 0.0, 0j, "", b"", bytearray(),
1510+ (), [], {}, set(), frozenset(), range(0), memoryview(b"")):
1511+ module = types.ModuleType("custom_spec")
1512+ module.__spec__ = types.SimpleNamespace(_initializing=flag)
1513+ sys.modules["custom_spec"] = module
1514+ lazy import custom_spec
1515+ assert "custom_spec" not in sys.lazy_modules, (flag, sys.lazy_modules)
1516+ """ )
1517+ assert_python_ok ("-c" , code )
1518+
1519+ def test_spec_initializing_truth_conversion_is_not_run (self ):
1520+ code = textwrap .dedent ("""
1521+ import sys
1522+ import types
1523+
1524+ class Flag(int):
1525+ def __bool__(self):
1526+ raise RuntimeError("_initializing truth conversion was run")
1527+
1528+ module = types.ModuleType("custom_spec")
1529+ module.__spec__ = types.SimpleNamespace(_initializing=Flag())
1530+ sys.modules["custom_spec"] = module
1531+ lazy import custom_spec
1532+ assert "custom_spec" in sys.lazy_modules, sys.lazy_modules
1533+ """ )
1534+ assert_python_ok ("-c" , code )
1535+
13941536 def test_pending_submodule_is_still_tracked (self ):
13951537 """`lazy from` a submodule that is not loaded must stay tracked."""
13961538 code = textwrap .dedent ("""
0 commit comments