@@ -1205,6 +1205,36 @@ def tearDown(self):
12051205 sys .set_lazy_imports_filter (None )
12061206 sys .set_lazy_imports ("normal" )
12071207
1208+ def _run_subprocess_with_modules (self , code , files ):
1209+ with tempfile .TemporaryDirectory () as tmpdir :
1210+ for relpath , contents in files .items ():
1211+ path = os .path .join (tmpdir , relpath )
1212+ os .makedirs (os .path .dirname (path ), exist_ok = True )
1213+ with open (path , "w" , encoding = "utf-8" ) as file :
1214+ file .write (textwrap .dedent (contents ))
1215+
1216+ env = os .environ .copy ()
1217+ env ["PYTHONPATH" ] = os .pathsep .join (
1218+ entry for entry in (tmpdir , env .get ("PYTHONPATH" )) if entry
1219+ )
1220+ env ["PYTHON_LAZY_IMPORTS" ] = "normal"
1221+
1222+ result = subprocess .run (
1223+ [sys .executable , "-c" , textwrap .dedent (code )],
1224+ capture_output = True ,
1225+ cwd = tmpdir ,
1226+ env = env ,
1227+ text = True ,
1228+ )
1229+ return result
1230+
1231+ def _assert_subprocess_ok (self , code , files ):
1232+ result = self ._run_subprocess_with_modules (code , files )
1233+ self .assertEqual (
1234+ result .returncode , 0 , f"stdout: { result .stdout } , stderr: { result .stderr } "
1235+ )
1236+ return result
1237+
12081238 def test_filter_receives_correct_arguments_for_import (self ):
12091239 """Filter should receive (importer, name, fromlist=None) for 'import x'."""
12101240 code = textwrap .dedent ("""
@@ -1290,6 +1320,159 @@ def deny_filter(importer, name, fromlist):
12901320 self .assertEqual (result .returncode , 0 , f"stderr: { result .stderr } " )
12911321 self .assertIn ("EAGER" , result .stdout )
12921322
1323+ def test_filter_distinguishes_absolute_and_relative_from_imports (self ):
1324+ """Relative imports should pass resolved module names to the filter."""
1325+ files = {
1326+ "target.py" : """
1327+ VALUE = "absolute"
1328+ """ ,
1329+ "pkg/__init__.py" : "" ,
1330+ "pkg/target.py" : """
1331+ VALUE = "relative"
1332+ """ ,
1333+ "pkg/runner.py" : """
1334+ import sys
1335+
1336+ seen = []
1337+
1338+ def my_filter(importer, name, fromlist):
1339+ seen.append((importer, name, fromlist))
1340+ return True
1341+
1342+ sys.set_lazy_imports_filter(my_filter)
1343+
1344+ lazy from target import VALUE as absolute_value
1345+ lazy from .target import VALUE as relative_value
1346+
1347+ assert seen == [
1348+ (__name__, "target", ("VALUE",)),
1349+ (__name__, "pkg.target", ("VALUE",)),
1350+ ], seen
1351+ """ ,
1352+ }
1353+
1354+ result = self ._assert_subprocess_ok (
1355+ """
1356+ import pkg.runner
1357+ print("OK")
1358+ """ ,
1359+ files ,
1360+ )
1361+ self .assertIn ("OK" , result .stdout )
1362+
1363+ def test_filter_receives_resolved_name_for_relative_package_import (self ):
1364+ """'lazy from . import x' should report the resolved package name."""
1365+ files = {
1366+ "pkg/__init__.py" : "" ,
1367+ "pkg/sibling.py" : """
1368+ VALUE = 1
1369+ """ ,
1370+ "pkg/runner.py" : """
1371+ import sys
1372+
1373+ seen = []
1374+
1375+ def my_filter(importer, name, fromlist):
1376+ seen.append((importer, name, fromlist))
1377+ return True
1378+
1379+ sys.set_lazy_imports_filter(my_filter)
1380+
1381+ lazy from . import sibling
1382+
1383+ assert seen == [
1384+ (__name__, "pkg", ("sibling",)),
1385+ ], seen
1386+ """ ,
1387+ }
1388+
1389+ result = self ._assert_subprocess_ok (
1390+ """
1391+ import pkg.runner
1392+ print("OK")
1393+ """ ,
1394+ files ,
1395+ )
1396+ self .assertIn ("OK" , result .stdout )
1397+
1398+ def test_filter_receives_resolved_name_for_parent_relative_import (self ):
1399+ """Parent relative imports should also use the resolved module name."""
1400+ files = {
1401+ "pkg/__init__.py" : "" ,
1402+ "pkg/target.py" : """
1403+ VALUE = 1
1404+ """ ,
1405+ "pkg/sub/__init__.py" : "" ,
1406+ "pkg/sub/runner.py" : """
1407+ import sys
1408+
1409+ seen = []
1410+
1411+ def my_filter(importer, name, fromlist):
1412+ seen.append((importer, name, fromlist))
1413+ return True
1414+
1415+ sys.set_lazy_imports_filter(my_filter)
1416+
1417+ lazy from ..target import VALUE
1418+
1419+ assert seen == [
1420+ (__name__, "pkg.target", ("VALUE",)),
1421+ ], seen
1422+ """ ,
1423+ }
1424+
1425+ result = self ._assert_subprocess_ok (
1426+ """
1427+ import pkg.sub.runner
1428+ print("OK")
1429+ """ ,
1430+ files ,
1431+ )
1432+ self .assertIn ("OK" , result .stdout )
1433+
1434+ def test_filter_can_force_eager_only_for_resolved_relative_target (self ):
1435+ """Resolved names should let filters treat relative and absolute imports differently."""
1436+ files = {
1437+ "target.py" : """
1438+ VALUE = "absolute"
1439+ """ ,
1440+ "pkg/__init__.py" : "" ,
1441+ "pkg/target.py" : """
1442+ VALUE = "relative"
1443+ """ ,
1444+ "pkg/runner.py" : """
1445+ import sys
1446+
1447+ def my_filter(importer, name, fromlist):
1448+ return name != "pkg.target"
1449+
1450+ sys.set_lazy_imports_filter(my_filter)
1451+
1452+ lazy from target import VALUE as absolute_value
1453+ lazy from .target import VALUE as relative_value
1454+
1455+ assert "pkg.target" in sys.modules, sorted(
1456+ name for name in sys.modules
1457+ if name in {"target", "pkg.target"}
1458+ )
1459+ assert "target" not in sys.modules, sorted(
1460+ name for name in sys.modules
1461+ if name in {"target", "pkg.target"}
1462+ )
1463+ assert relative_value == "relative", relative_value
1464+ """ ,
1465+ }
1466+
1467+ result = self ._assert_subprocess_ok (
1468+ """
1469+ import pkg.runner
1470+ print("OK")
1471+ """ ,
1472+ files ,
1473+ )
1474+ self .assertIn ("OK" , result .stdout )
1475+
12931476
12941477class AdditionalSyntaxRestrictionTests (unittest .TestCase ):
12951478 """Additional syntax restriction tests per PEP 810."""
0 commit comments