bpo-30436: Raise ModuleNotFoundError for importlib.util.find_spec() when parent isn...
authorMilan Oberkirch <zvyn@oberkirch.org>
Wed, 14 Jun 2017 21:34:50 +0000 (07:34 +1000)
committerBrett Cannon <brettcannon@users.noreply.github.com>
Wed, 14 Jun 2017 21:34:50 +0000 (14:34 -0700)
Previously AttributeError was raised, but that's not very reflective of the fact that the requested module can't be found since the specified parent isn't actually a package.

Doc/library/importlib.rst
Lib/importlib/util.py
Lib/test/test_cmd_line_script.py
Lib/test/test_importlib/test_util.py
Misc/NEWS

index 1df613076da8cf00fd42a2b693764139ac171ce7..45a02e568e1ba5490eeb2f5dae398e5f88410dbd 100644 (file)
@@ -1215,6 +1215,11 @@ an :term:`importer`.
 
    .. versionadded:: 3.4
 
+   .. versionchanged:: 3.7
+      Raises :exc:`ModuleNotFoundError` instead of :exc:`AttributeError` if
+      **package** is in fact not a package (i.e. lacks a :attr:`__path__`
+      attribute).
+
 .. function:: module_from_spec(spec)
 
    Create a new module based on **spec** and
index 6bdf0d445db1f58c2ce2ea3b7d50ae0d66f3f491..41c74d4cc6c2a6a576ab5ba03b4a35dc4724d76b 100644 (file)
@@ -84,11 +84,16 @@ def find_spec(name, package=None):
     if fullname not in sys.modules:
         parent_name = fullname.rpartition('.')[0]
         if parent_name:
-            # Use builtins.__import__() in case someone replaced it.
             parent = __import__(parent_name, fromlist=['__path__'])
-            return _find_spec(fullname, parent.__path__)
+            try:
+                parent_path = parent.__path__
+            except AttributeError as e:
+                raise ModuleNotFoundError(
+                    f"__path__ attribute not found on {parent_name!r}"
+                    f"while trying to find {fullname!r}", name=fullname) from e
         else:
-            return _find_spec(fullname, None)
+            parent_path = None
+        return _find_spec(fullname, parent_path)
     else:
         module = sys.modules[fullname]
         if module is None:
index 1587daf8f582dd000aaac9251c5df12f5c2301c8..0d0bcd784d2688542dbea8088f50613b702e47a6 100644 (file)
@@ -427,7 +427,7 @@ class CmdLineTest(unittest.TestCase):
         tests = (
             ('builtins', br'No code object available'),
             ('builtins.x', br'Error while finding module specification.*'
-                br'AttributeError'),
+                br'ModuleNotFoundError'),
             ('builtins.x.y', br'Error while finding module specification.*'
                 br'ModuleNotFoundError.*No module named.*not a package'),
             ('os.path', br'loader.*cannot handle'),
index ac18e5c34b87fae9e084bbabf08ea6229caa7574..56a0b0e7a518cd6b620ee4cad3cb815cf37db1c6 100644 (file)
@@ -522,6 +522,12 @@ class FindSpecTests:
             self.assertNotIn(name, sorted(sys.modules))
             self.assertNotIn(fullname, sorted(sys.modules))
 
+    def test_find_submodule_in_module(self):
+        # ModuleNotFoundError raised when a module is specified as
+        # a parent instead of a package.
+        with self.assertRaises(ModuleNotFoundError):
+            self.util.find_spec('module.name')
+
 
 (Frozen_FindSpecTests,
  Source_FindSpecTests
index 31e8ec073854cc5caf6ed52119c1de0bfe2d9102..7452b25bcf138b874224f54998dcf4b6f2ef2513 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -441,6 +441,10 @@ Library
 - bpo-30149: inspect.signature() now supports callables with
   variable-argument parameters wrapped with partialmethod.
   Patch by Dong-hee Na.
+  
+- bpo-30436: importlib.find_spec() raises ModuleNotFoundError instead of
+  AttributeError if the specified parent module is not a package
+  (i.e. lacks a __path__ attribute).
 
 - bpo-30301: Fix AttributeError when using SimpleQueue.empty() under
   *spawn* and *forkserver* start methods.