sys.set_lazy_imports_filter(None)
sys.set_lazy_imports("normal")
+ def _run_subprocess_with_modules(self, code, files):
+ with tempfile.TemporaryDirectory() as tmpdir:
+ for relpath, contents in files.items():
+ path = os.path.join(tmpdir, relpath)
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, "w", encoding="utf-8") as file:
+ file.write(textwrap.dedent(contents))
+
+ env = os.environ.copy()
+ env["PYTHONPATH"] = os.pathsep.join(
+ entry for entry in (tmpdir, env.get("PYTHONPATH")) if entry
+ )
+ env["PYTHON_LAZY_IMPORTS"] = "normal"
+
+ result = subprocess.run(
+ [sys.executable, "-c", textwrap.dedent(code)],
+ capture_output=True,
+ cwd=tmpdir,
+ env=env,
+ text=True,
+ )
+ return result
+
+ def _assert_subprocess_ok(self, code, files):
+ result = self._run_subprocess_with_modules(code, files)
+ self.assertEqual(
+ result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}"
+ )
+ return result
+
def test_filter_receives_correct_arguments_for_import(self):
"""Filter should receive (importer, name, fromlist=None) for 'import x'."""
code = textwrap.dedent("""
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
self.assertIn("EAGER", result.stdout)
+ def test_filter_distinguishes_absolute_and_relative_from_imports(self):
+ """Relative imports should pass resolved module names to the filter."""
+ files = {
+ "target.py": """
+ VALUE = "absolute"
+ """,
+ "pkg/__init__.py": "",
+ "pkg/target.py": """
+ VALUE = "relative"
+ """,
+ "pkg/runner.py": """
+ import sys
+
+ seen = []
+
+ def my_filter(importer, name, fromlist):
+ seen.append((importer, name, fromlist))
+ return True
+
+ sys.set_lazy_imports_filter(my_filter)
+
+ lazy from target import VALUE as absolute_value
+ lazy from .target import VALUE as relative_value
+
+ assert seen == [
+ (__name__, "target", ("VALUE",)),
+ (__name__, "pkg.target", ("VALUE",)),
+ ], seen
+ """,
+ }
+
+ result = self._assert_subprocess_ok(
+ """
+ import pkg.runner
+ print("OK")
+ """,
+ files,
+ )
+ self.assertIn("OK", result.stdout)
+
+ def test_filter_receives_resolved_name_for_relative_package_import(self):
+ """'lazy from . import x' should report the resolved package name."""
+ files = {
+ "pkg/__init__.py": "",
+ "pkg/sibling.py": """
+ VALUE = 1
+ """,
+ "pkg/runner.py": """
+ import sys
+
+ seen = []
+
+ def my_filter(importer, name, fromlist):
+ seen.append((importer, name, fromlist))
+ return True
+
+ sys.set_lazy_imports_filter(my_filter)
+
+ lazy from . import sibling
+
+ assert seen == [
+ (__name__, "pkg", ("sibling",)),
+ ], seen
+ """,
+ }
+
+ result = self._assert_subprocess_ok(
+ """
+ import pkg.runner
+ print("OK")
+ """,
+ files,
+ )
+ self.assertIn("OK", result.stdout)
+
+ def test_filter_receives_resolved_name_for_parent_relative_import(self):
+ """Parent relative imports should also use the resolved module name."""
+ files = {
+ "pkg/__init__.py": "",
+ "pkg/target.py": """
+ VALUE = 1
+ """,
+ "pkg/sub/__init__.py": "",
+ "pkg/sub/runner.py": """
+ import sys
+
+ seen = []
+
+ def my_filter(importer, name, fromlist):
+ seen.append((importer, name, fromlist))
+ return True
+
+ sys.set_lazy_imports_filter(my_filter)
+
+ lazy from ..target import VALUE
+
+ assert seen == [
+ (__name__, "pkg.target", ("VALUE",)),
+ ], seen
+ """,
+ }
+
+ result = self._assert_subprocess_ok(
+ """
+ import pkg.sub.runner
+ print("OK")
+ """,
+ files,
+ )
+ self.assertIn("OK", result.stdout)
+
+ def test_filter_can_force_eager_only_for_resolved_relative_target(self):
+ """Resolved names should let filters treat relative and absolute imports differently."""
+ files = {
+ "target.py": """
+ VALUE = "absolute"
+ """,
+ "pkg/__init__.py": "",
+ "pkg/target.py": """
+ VALUE = "relative"
+ """,
+ "pkg/runner.py": """
+ import sys
+
+ def my_filter(importer, name, fromlist):
+ return name != "pkg.target"
+
+ sys.set_lazy_imports_filter(my_filter)
+
+ lazy from target import VALUE as absolute_value
+ lazy from .target import VALUE as relative_value
+
+ assert "pkg.target" in sys.modules, sorted(
+ name for name in sys.modules
+ if name in {"target", "pkg.target"}
+ )
+ assert "target" not in sys.modules, sorted(
+ name for name in sys.modules
+ if name in {"target", "pkg.target"}
+ )
+ assert relative_value == "relative", relative_value
+ """,
+ }
+
+ result = self._assert_subprocess_ok(
+ """
+ import pkg.runner
+ print("OK")
+ """,
+ files,
+ )
+ self.assertIn("OK", result.stdout)
+
class AdditionalSyntaxRestrictionTests(unittest.TestCase):
"""Additional syntax restriction tests per PEP 810."""