[3.15] gh-155194: Fix not raising on non-module import (GH-155189) (#155983)

pablogsal <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/e531dd4b7e6961aab54c19ee90bd80420e24f82d
commit: e531dd4b7e6961aab54c19ee90bd80420e24f82d
branch: 3.15
author: Brittany Reynoso <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-08-19T11:01:31+01:00
summary:

[3.15] gh-155194: Fix not raising on non-module import (GH-155189) (#155983)

files:
A Lib/test/test_lazy_import/data/lazypkg/__init__.py
A Lib/test/test_lazy_import/data/lazypkg/bar.py
M Lib/test/test_lazy_import/__init__.py
M Lib/test/test_traceback.py
M Makefile.pre.in
M Python/import.c

diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py
index 0a53d2559c91f01..6951a67d309bf99 100644
--- a/Lib/test/test_lazy_import/__init__.py
+++ b/Lib/test/test_lazy_import/__init__.py
@@ -686,17 +686,53 @@ def test_lazy_modules_tracks_lazy_imports(self):
 class ErrorHandlingTests(LazyImportTestCase):
     """Tests for error handling during lazy import reification."""
 
-    def test_missing_lazy_submodule_raises_attribute_error(self):
-        """Accessing a nonexistent lazy submodule via parent attr raises AttributeError."""
+    def test_missing_lazy_submodule_raises_module_not_found_error(self):
+        """Accessing a nonexistent lazy submodule via parent attr raises ModuleNotFoundError."""
         code = textwrap.dedent("""
             lazy import test.test_lazy_import.data.nonexistent_module
 
             try:
                 _ = test.test_lazy_import.data.nonexistent_module
-            except AttributeError:
+            except ModuleNotFoundError:
                 pass
             else:
-                raise AssertionError("AttributeError was not raised")
+                raise AssertionError("ModuleNotFoundError was not raised")
+        """)
+        assert_python_ok("-c", code)
+
+    def test_non_package_lazily_imported(self):
+        """Accessing a nonexistent lazy name via parent attr raises ModuleNotFoundError."""
+        code = textwrap.dedent("""
+            lazy import math.pi
+
+            try:
+                _ = math.pi
+            except ModuleNotFoundError:
+                pass
+            else:
+                raise AssertionError("ModuleNotFoundError was not raised")
+        """)
+        assert_python_ok("-c", code)
+
+    def test_non_package_lazily_imported_as(self):
+        """Doing a dotted lazy import as still works"""
+        code = textwrap.dedent("""
+            lazy import math.pi as pi
+            pi
+        """)
+        assert_python_ok("-c", code)
+
+    def test_missing_attribute_raises_import_error(self):
+        """Accessing a nonexistent lazy name via from import raises ImportError."""
+        code = textwrap.dedent("""
+            lazy from sys import doesnotexist
+
+            try:
+                _ = doesnotexist
+            except ImportError:
+                pass
+            else:
+                raise AssertionError("ImportError was not raised")
         """)
         assert_python_ok("-c", code)
 
diff --git a/Lib/test/test_lazy_import/data/lazypkg/__init__.py b/Lib/test/test_lazy_import/data/lazypkg/__init__.py
new file mode 100644
index 000000000000000..276b51823fee32a
--- /dev/null
+++ b/Lib/test/test_lazy_import/data/lazypkg/__init__.py
@@ -0,0 +1 @@
+lazy from . import bar
diff --git a/Lib/test/test_lazy_import/data/lazypkg/bar.py b/Lib/test/test_lazy_import/data/lazypkg/bar.py
new file mode 100644
index 000000000000000..b8d8b60b886b88a
--- /dev/null
+++ b/Lib/test/test_lazy_import/data/lazypkg/bar.py
@@ -0,0 +1,2 @@
+print("BAR_MODULE_LOADED")
+def f(): pass
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index bb64153b91c92cc..6b4f1ee6de6c518 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -5596,11 +5596,11 @@ class TestLazyImportSuggestions(unittest.TestCase):
 
     def test_attribute_error_does_not_reify_lazy_imports(self):
         """Printing an AttributeError should not trigger lazy import reification."""
-        # pkg.bar prints "BAR_MODULE_LOADED" when imported.
+        # lazypkg.bar prints "BAR_MODULE_LOADED" when imported.
         # If lazy import is reified during suggestion computation, we'll see it.
         code = textwrap.dedent("""
-            lazy import test.test_lazy_import.data.pkg.bar
-            test.test_lazy_import.data.pkg.nonexistent
+            lazy import test.test_lazy_import.data.lazypkg
+            test.test_lazy_import.data.lazypkg.nonexistent
         """)
         rc, stdout, stderr = assert_python_failure('-c', code)
         self.assertNotIn(b"BAR_MODULE_LOADED", stdout)
@@ -5609,9 +5609,9 @@ def test_traceback_formatting_does_not_reify_lazy_imports(self):
         """Formatting a traceback should not trigger lazy import reification."""
         code = textwrap.dedent("""
             import traceback
-            lazy import test.test_lazy_import.data.pkg.bar
+            lazy import test.test_lazy_import.data.lazypkg
             try:
-                test.test_lazy_import.data.pkg.nonexistent
+                test.test_lazy_import.data.lazypkg.nonexistent
             except AttributeError:
                 traceback.format_exc()
             print("OK")
@@ -5623,9 +5623,9 @@ def test_traceback_formatting_does_not_reify_lazy_imports(self):
     def test_suggestion_still_works_for_non_lazy_attributes(self):
         """Suggestions should still work for non-lazy module attributes."""
         code = textwrap.dedent("""
-            lazy import test.test_lazy_import.data.pkg.bar
+            lazy import test.test_lazy_import.data.lazypkg
             # Typo for __name__
-            test.test_lazy_import.data.pkg.__nme__
+            test.test_lazy_import.data.lazypkg.__nme__
         """)
         rc, stdout, stderr = assert_python_failure('-c', code)
         self.assertIn(b"__name__", stderr)
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 77dde715a4852dc..1f4c366d30c44f9 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -2772,6 +2772,7 @@ TESTSUBDIRS=	idlelib/idle_test \
 		test/test_lazy_import/data \
 		test/test_lazy_import/data/pkg \
 		test/test_lazy_import/data/badsyntax \
+		test/test_lazy_import/data/lazypkg \
 		test/test_module \
 		test/test_multiprocessing_fork \
 		test/test_multiprocessing_forkserver \
diff --git a/Python/import.c b/Python/import.c
index 287f5f611f55342..95f5c20bf4894a8 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -3937,19 +3937,6 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
         goto error;
     }
 
-    Py_ssize_t dot = -1;
-    int full = 0;
-    if (lz->lz_attr != NULL) {
-        full = 1;
-    }
-    if (!full) {
-        dot = PyUnicode_FindChar(lz->lz_from, '.', 0,
-                                 PyUnicode_GET_LENGTH(lz->lz_from), 1);
-    }
-    if (dot < 0) {
-        full = 1;
-    }
-
     if (lz->lz_attr != NULL) {
         if (PyUnicode_Check(lz->lz_attr)) {
             fromlist = PyTuple_New(1);
@@ -3975,23 +3962,10 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
         PyErr_SetString(PyExc_ImportError, "__import__ not found");
         goto error;
     }
-    if (full) {
-        obj = _PyEval_ImportNameWithImport(
-            tstate, import_func, globals, globals,
-            lz->lz_from, fromlist, _PyLong_GetZero()
-        );
-    }
-    else {
-        PyObject *name = PyUnicode_Substring(lz->lz_from, 0, dot);
-        if (name == NULL) {
-            goto error;
-        }
-        obj = _PyEval_ImportNameWithImport(
-            tstate, import_func, globals, globals,
-            name, fromlist, _PyLong_GetZero()
-        );
-        Py_DECREF(name);
-    }
+    obj = _PyEval_ImportNameWithImport(
+        tstate, import_func, globals, globals,
+        lz->lz_from, fromlist, _PyLong_GetZero()
+    );
     if (obj == NULL) {
         goto error;
     }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.