Anonymous avatar Anonymous committed d632494

Issue #14583: Fix importlib bug when a package's __init__.py would first import one of its modules then raise an error.

Comments (0)

Files changed (6)

Lib/importlib/_bootstrap.py

         # Return up to the first dot in 'name'. This is complicated by the fact
         # that 'name' may be relative.
         if level == 0:
-            return sys.modules[name.partition('.')[0]]
+            return _gcd_import(name.partition('.')[0])
         elif not name:
             return module
         else:

Lib/importlib/test/import_/test_packages.py

                     import_util.import_('pkg.module')
                 self.assertEqual(cm.exception.name, 'pkg')
 
+    def test_raising_parent_after_importing_child(self):
+        def __init__():
+            import pkg.module
+            1/0
+        mock = util.mock_modules('pkg.__init__', 'pkg.module',
+                                 module_code={'pkg': __init__})
+        with mock:
+            with util.import_state(meta_path=[mock]):
+                with self.assertRaises(ZeroDivisionError):
+                    import_util.import_('pkg')
+                self.assertFalse('pkg' in sys.modules)
+                self.assertTrue('pkg.module' in sys.modules)
+                with self.assertRaises(ZeroDivisionError):
+                    import_util.import_('pkg.module')
+                self.assertFalse('pkg' in sys.modules)
+                self.assertTrue('pkg.module' in sys.modules)
+
+    def test_raising_parent_after_relative_importing_child(self):
+        def __init__():
+            from . import module
+            1/0
+        mock = util.mock_modules('pkg.__init__', 'pkg.module',
+                                 module_code={'pkg': __init__})
+        with mock:
+            with util.import_state(meta_path=[mock]):
+                with self.assertRaises((ZeroDivisionError, ImportError)):
+                    # This raises ImportError on the "from . import module"
+                    # line, not sure why.
+                    import_util.import_('pkg')
+                self.assertFalse('pkg' in sys.modules)
+                with self.assertRaises((ZeroDivisionError, ImportError)):
+                    import_util.import_('pkg.module')
+                self.assertFalse('pkg' in sys.modules)
+                # XXX False
+                #self.assertTrue('pkg.module' in sys.modules)
+
+    def test_raising_parent_after_double_relative_importing_child(self):
+        def __init__():
+            from ..subpkg import module
+            1/0
+        mock = util.mock_modules('pkg.__init__', 'pkg.subpkg.__init__',
+                                 'pkg.subpkg.module',
+                                 module_code={'pkg.subpkg': __init__})
+        with mock:
+            with util.import_state(meta_path=[mock]):
+                with self.assertRaises((ZeroDivisionError, ImportError)):
+                    # This raises ImportError on the "from ..subpkg import module"
+                    # line, not sure why.
+                    import_util.import_('pkg.subpkg')
+                self.assertFalse('pkg.subpkg' in sys.modules)
+                with self.assertRaises((ZeroDivisionError, ImportError)):
+                    import_util.import_('pkg.subpkg.module')
+                self.assertFalse('pkg.subpkg' in sys.modules)
+                # XXX False
+                #self.assertTrue('pkg.subpkg.module' in sys.modules)
+
     def test_module_not_package(self):
         # Try to import a submodule from a non-package should raise ImportError.
         assert not hasattr(sys, '__path__')

Lib/importlib/test/util.py

         else:
             sys.modules[fullname] = self.modules[fullname]
             if fullname in self.module_code:
-                self.module_code[fullname]()
+                try:
+                    self.module_code[fullname]()
+                except Exception:
+                    del sys.modules[fullname]
+                    raise
             return self.modules[fullname]
 
     def __enter__(self):
 Library
 -------
 
+- Issue #14583: Fix importlib bug when a package's __init__.py would first
+  import one of its modules then raise an error.
+
 - Issue #14741: Fix missing support for Ellipsis ('...') in parser module.
 
 - Issue #14697: Fix missing support for set displays and set comprehensions in
                 goto error_with_unlock;
             }
 
+            if (PyUnicode_GET_LENGTH(PyTuple_GET_ITEM(partition, 1)) == 0) {
+                /* No dot in module name, simple exit */
+                Py_DECREF(partition);
+                final_mod = mod;
+                Py_INCREF(mod);
+                goto exit_with_unlock;
+            }
+
             front = PyTuple_GET_ITEM(partition, 0);
             Py_INCREF(front);
             Py_DECREF(partition);
 
             if (level == 0) {
-                final_mod = PyDict_GetItem(interp->modules, front);
-                if (final_mod == NULL) {
-                    PyErr_Format(PyExc_KeyError,
-                                 "%R not in sys.modules as expected", front);
-                }
-                else {
-                    Py_INCREF(final_mod);
-                }
+                final_mod = PyObject_CallFunctionObjArgs(builtins_import, front, NULL);
                 Py_DECREF(front);
             }
             else {
                                                   fromlist, builtins_import,
                                                   NULL);
     }
+
+  exit_with_unlock:
   error_with_unlock:
 #ifdef WITH_THREAD
     if (_PyImport_ReleaseLock() < 0) {

Binary file modified.

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.