gh-155194: Fix not raising on non-module import (#155189)

DinoV <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/dffac6163e693cf80ed42cdc8e2cb5c0cb9577d7
commit: dffac6163e693cf80ed42cdc8e2cb5c0cb9577d7
branch: main
author: Dino Viehland <[email protected]>
committer: DinoV <[email protected]>
date: 2026-08-14T14:17:23-07:00
summary:

gh-155194: Fix not raising on non-module import (#155189)

* Fix not raising on non-module import

* Fix traceback tests

* Fix doc string and add new test

* Fix up feedback on tests

* Add test case for lazy import dotted.name as name

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 899ecec1ba2afa4..b12e209707a9de3 100644
--- a/Lib/test/test_lazy_import/__init__.py
+++ b/Lib/test/test_lazy_import/__init__.py
@@ -678,17 +678,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 e38d0942e463e9c..8e4c28562a6cbe5 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -5611,11 +5611,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)
@@ -5624,9 +5624,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")
@@ -5638,9 +5638,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 46fa26e01572cc2..d3d24a13898d992 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -2690,6 +2690,7 @@ TESTSUBDIRS=	idlelib/idle_test \
 		test/test_lazy_import/data/pkg \
 		test/test_lazy_import/data/badsyntax \
 		test/test_lazy_import/data/circular_import_pkg \
+		test/test_lazy_import/data/lazypkg \
 		test/test_lazy_import/data/metasyntactic \
 		test/test_lazy_import/data/metasyntactic/foo \
 		test/test_lazy_import/data/metasyntactic/foo/ack \
diff --git a/Python/import.c b/Python/import.c
index 5ca78a971fa54c6..47d5296a2fec9b1 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -3940,19 +3940,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);
@@ -3978,23 +3965,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.