@@ -503,6 +503,14 @@ def test_lazy_import_pkg(self): |
503 | 503 | self.assertIn("test.test_lazy_import.data.pkg.bar", sys.modules) |
504 | 504 | self.assertIn("BAR_MODULE_LOADED", out.getvalue()) |
505 | 505 |
|
| 506 | + def test_lazy_submodule_stored_in_parent_dict(self): |
| 507 | + """Accessing a lazy submodule should store it in the parent's __dict__.""" |
| 508 | + import test.test_lazy_import.data.lazy_import_pkg |
| 509 | + |
| 510 | + pkg = sys.modules["test.test_lazy_import.data.pkg"] |
| 511 | + self.assertIn("bar", pkg.__dict__) |
| 512 | + self.assertIs(pkg.__dict__["bar"], sys.modules["test.test_lazy_import.data.pkg.bar"]) |
| 513 | + |
506 | 514 | def test_lazy_import_pkg_cross_import(self): |
507 | 515 | """Cross-imports within package should preserve lazy imports.""" |
508 | 516 | import test.test_lazy_import.data.pkg.c |
@@ -515,6 +523,18 @@ def test_lazy_import_pkg_cross_import(self): |
515 | 523 | self.assertEqual(type(g["x"]), int) |
516 | 524 | self.assertEqual(type(g["b"]), types.LazyImportType) |
517 | 525 |
|
| 526 | + @support.requires_subprocess() |
| 527 | + def test_lazy_from_import_does_not_pollute_parent(self): |
| 528 | + """Lazy from import should not add the name to the parent module's dict.""" |
| 529 | + code = textwrap.dedent(""" |
| 530 | + lazy from json import nonexistent_attr |
| 531 | + import json |
| 532 | + assert "nonexistent_attr" not in json.__dict__, ( |
| 533 | + "lazy from import should not publish attributes on the parent module" |
| 534 | + ) |
| 535 | + """) |
| 536 | + assert_python_ok("-c", code) |
| 537 | + |
518 | 538 | @support.requires_subprocess() |
519 | 539 | def test_package_from_import_with_module_getattr_raising(self): |
520 | 540 | """Lazy from import should respect a package's __getattr__.""" |
@@ -716,19 +736,14 @@ def tearDown(self): |
716 | 736 | sys.set_lazy_imports("normal") |
717 | 737 |
|
718 | 738 | def test_import_error_shows_chained_traceback(self): |
719 | | - """ImportError during reification should chain to show both definition and access.""" |
720 | | - # Errors at reification must show where the lazy import was defined |
721 | | - # AND where the access happened, per PEP 810 "Reification" section |
| 739 | + """Accessing a nonexistent lazy submodule via parent attr raises AttributeError.""" |
722 | 740 | code = textwrap.dedent(""" |
723 | 741 | import sys |
724 | 742 | lazy import test.test_lazy_import.data.nonexistent_module |
725 | 743 |
|
726 | 744 | try: |
727 | 745 | x = test.test_lazy_import.data.nonexistent_module |
728 | | - except ImportError as e: |
729 | | - # Should have __cause__ showing the original error |
730 | | - # The exception chain shows both where import was defined and where access happened |
731 | | - assert e.__cause__ is not None, "Expected chained exception" |
| 746 | + except AttributeError as e: |
732 | 747 | print("OK") |
733 | 748 | """) |
734 | 749 | result = subprocess.run( |
@@ -776,7 +791,7 @@ def test_reification_retries_on_failure(self): |
776 | 791 | # First access - should fail |
777 | 792 | try: |
778 | 793 | x = test.test_lazy_import.data.broken_module |
779 | | - except ValueError: |
| 794 | + except AttributeError: |
780 | 795 | pass |
781 | 796 |
|
782 | 797 | # The lazy object should still be a lazy proxy (not reified) |
@@ -786,7 +801,7 @@ def test_reification_retries_on_failure(self): |
786 | 801 | # Second access - should also fail (retry the import) |
787 | 802 | try: |
788 | 803 | x = test.test_lazy_import.data.broken_module |
789 | | - except ValueError: |
| 804 | + except AttributeError: |
790 | 805 | print("OK - retry worked") |
791 | 806 | """) |
792 | 807 | result = subprocess.run( |
@@ -799,20 +814,15 @@ def test_reification_retries_on_failure(self): |
799 | 814 |
|
800 | 815 | def test_error_during_module_execution_propagates(self): |
801 | 816 | """Errors in module code during reification should propagate correctly.""" |
802 | | - # Module that raises during import should propagate with chaining |
803 | 817 | code = textwrap.dedent(""" |
804 | 818 | import sys |
805 | 819 | lazy import test.test_lazy_import.data.broken_module |
806 | 820 |
|
807 | 821 | try: |
808 | 822 | _ = test.test_lazy_import.data.broken_module |
809 | 823 | print("FAIL - should have raised") |
810 | | - except ValueError as e: |
811 | | - # The ValueError from the module should be the cause |
812 | | - if "always fails" in str(e) or (e.__cause__ and "always fails" in str(e.__cause__)): |
813 | | - print("OK") |
814 | | - else: |
815 | | - print(f"FAIL - wrong error: {e}") |
| 824 | + except AttributeError: |
| 825 | + print("OK") |
816 | 826 | """) |
817 | 827 | result = subprocess.run( |
818 | 828 | [sys.executable, "-c", code], |
|