Skip to content
Merged
5 changes: 5 additions & 0 deletions Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions Lib/importlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_cmd_line_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ def test_dash_m_errors(self):
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'),
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_importlib/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,12 @@ def test_find_relative_module_missing_package(self):
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
Expand Down
4 changes: 4 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,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.
Expand Down