]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-149981: Test lazy import corner cases with module-level `__getattr__` (#149982)
authorsobolevn <mail@sobolevn.me>
Thu, 21 May 2026 09:21:45 +0000 (12:21 +0300)
committerGitHub <noreply@github.com>
Thu, 21 May 2026 09:21:45 +0000 (12:21 +0300)
Lib/test/test_lazy_import/__init__.py
Lib/test/test_lazy_import/data/module_with_getattr.py
Lib/test/test_lazy_import/data/pkg/__init__.py

index 9b440b8859d662bc810b5c8b2634da9b72f90879..1298d532b91b97c56d393dcf6fd86aafadc1ae5c 100644 (file)
@@ -98,6 +98,59 @@ class LazyImportTests(unittest.TestCase):
         """)
         assert_python_ok("-c", code)
 
+    @support.requires_subprocess()
+    def test_from_import_with_module_getattr_raising(self):
+        """Lazy from import should respect module-level __getattr__."""
+        code = textwrap.dedent("""
+            lazy from test.test_lazy_import.data.module_with_getattr import raising_attr
+
+            try:
+                raising_attr
+            except ValueError as exc:
+                assert str(exc) == 'from_getattr', exc
+            else:
+                assert False, f'ValueError is not raised: {raising_attr}'
+        """)
+        assert_python_ok("-c", code)
+
+    @support.requires_subprocess()
+    def test_from_import_with_module_getattr_missing(self):
+        """Lazy from import should respect module-level __getattr__."""
+        for attr in ("missing_attr", "import_error_attr"):
+            with self.subTest(attr=attr):
+                code = textwrap.dedent(f"""
+                    lazy from test.test_lazy_import.data.module_with_getattr import {attr}
+
+                    try:
+                        {attr}
+                    except ImportError as exc:
+                        assert '{attr}' in str(exc), exc
+                        assert exc.__cause__ is not None
+                    else:
+                        assert False, ('ImportError is not raised', {attr})
+                """)
+                assert_python_ok("-c", code)
+
+    @support.requires_subprocess()
+    def test_from_import_with_module_getattr_warning(self):
+        """Lazy from import should respect module-level __getattr__."""
+        code = textwrap.dedent("""
+            import warnings
+
+            with warnings.catch_warnings(record=True) as log:
+                lazy from test.test_lazy_import.data.module_with_getattr import warning_attr
+
+            assert log == []
+
+            with warnings.catch_warnings(record=True) as log:
+                warning_attr
+            assert warning_attr == 'from_warning_attr', warning_attr
+            assert len(log) == 1, log
+            assert isinstance(log[0].message, UserWarning), log
+            assert str(log[0].message) == 'from_getattr', log
+        """)
+        assert_python_ok("-c", code)
+
     @support.requires_subprocess()
     def test_from_import_with_imported_module_getattr(self):
         """Lazy from import should not shadow an imported module's __getattr__."""
@@ -463,6 +516,59 @@ class PackageTests(unittest.TestCase):
         self.assertEqual(type(g["x"]), int)
         self.assertEqual(type(g["b"]), types.LazyImportType)
 
+    @support.requires_subprocess()
+    def test_package_from_import_with_module_getattr_raising(self):
+        """Lazy from import should respect a package's __getattr__."""
+        code = textwrap.dedent("""
+            lazy from test.test_lazy_import.data.pkg import raising_attr
+
+            try:
+                raising_attr
+            except ValueError as exc:
+                assert str(exc) == 'from_getattr', exc
+            else:
+                assert False, f'ValueError is not raised: {raising_attr}'
+        """)
+        assert_python_ok("-c", code)
+
+    @support.requires_subprocess()
+    def test_package_from_import_with_module_getattr_missing(self):
+        """Lazy from import should respect package's __getattr__."""
+        for attr in ("missing_attr", "import_error_attr"):
+            with self.subTest(attr=attr):
+                code = textwrap.dedent(f"""
+                    lazy from test.test_lazy_import.data.pkg import {attr}
+
+                    try:
+                        {attr}
+                    except ImportError as exc:
+                        assert '{attr}' in str(exc), exc
+                        assert exc.__cause__ is not None
+                    else:
+                        assert False, ('ImportError is not raised', {attr})
+                """)
+                assert_python_ok("-c", code)
+
+    @support.requires_subprocess()
+    def test_from_import_with_module_getattr_warning(self):
+        """Lazy from import should respect package's __getattr__."""
+        code = textwrap.dedent("""
+            import warnings
+
+            with warnings.catch_warnings(record=True) as log:
+                lazy from test.test_lazy_import.data.pkg import warning_attr
+
+            assert log == []
+
+            with warnings.catch_warnings(record=True) as log:
+                warning_attr
+            assert warning_attr == 'from_warning_attr', warning_attr
+            assert len(log) == 1, log
+            assert isinstance(log[0].message, UserWarning), log
+            assert str(log[0].message) == 'from_getattr', log
+        """)
+        assert_python_ok("-c", code)
+
     @support.requires_subprocess()
     def test_package_from_import_with_module_getattr(self):
         """Lazy from import should respect a package's __getattr__."""
index 2ac01a90d76e620a55b2c2a0e1486fd6e32aff85..db3a2301075c2ee5f5af3bd5961c537cd200260a 100644 (file)
@@ -1,4 +1,12 @@
 def __getattr__(name):
     if name == "dynamic_attr":
         return "from_getattr"
+    elif name == "raising_attr":
+        raise ValueError("from_getattr")
+    elif name == "import_error_attr":
+        raise ImportError(name)
+    elif name == "warning_attr":
+        import warnings
+        warnings.warn("from_getattr", category=UserWarning)
+        return "from_warning_attr"
     raise AttributeError(name)
index e526aab94131b861a01f2f92597c29c928520a9e..5f7b8662596cac6bb0c2df74b2bd3744c584c9a2 100644 (file)
@@ -3,4 +3,12 @@ x = 42
 def __getattr__(name):
     if name == "dynamic_attr":
         return "from_getattr"
+    elif name == "raising_attr":
+        raise ValueError("from_getattr")
+    elif name == "import_error_attr":
+        raise ImportError(name)
+    elif name == "warning_attr":
+        import warnings
+        warnings.warn("from_getattr", category=UserWarning)
+        return "from_warning_attr"
     raise AttributeError(name)