From 8c3f05e9f0f0b30a3d4a2433e92471794d8258af Mon Sep 17 00:00:00 2001 From: Milan Oberkirch Date: Thu, 15 Jun 2017 07:34:50 +1000 Subject: bpo-30436: Raise ModuleNotFoundError for importlib.util.find_spec() when parent isn't a package (GH-1899) 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 | 5 +++++ Lib/importlib/util.py | 11 ++++++++--- Lib/test/test_cmd_line_script.py | 2 +- Lib/test/test_importlib/test_util.py | 6 ++++++ Misc/NEWS | 4 ++++ 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 1df6130..45a02e5 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -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 diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 6bdf0d4..41c74d4 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -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: diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 1587daf..0d0bcd7 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -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'), diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index ac18e5c..56a0b0e 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -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 diff --git a/Misc/NEWS b/Misc/NEWS index 31e8ec0..7452b25 100644 --- 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. -- cgit v0.12