Skip to content

Commit 8c3f05e

Browse files
zvynbrettcannon
authored andcommitted
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.
1 parent 32fd874 commit 8c3f05e

File tree

5 files changed

+24
-4
lines changed

5 files changed

+24
-4
lines changed

Doc/library/importlib.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,11 @@ an :term:`importer`.
12151215

12161216
.. versionadded:: 3.4
12171217

1218+
.. versionchanged:: 3.7
1219+
Raises :exc:`ModuleNotFoundError` instead of :exc:`AttributeError` if
1220+
**package** is in fact not a package (i.e. lacks a :attr:`__path__`
1221+
attribute).
1222+
12181223
.. function:: module_from_spec(spec)
12191224

12201225
Create a new module based on **spec** and

Lib/importlib/util.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,16 @@ def find_spec(name, package=None):
8484
if fullname not in sys.modules:
8585
parent_name = fullname.rpartition('.')[0]
8686
if parent_name:
87-
# Use builtins.__import__() in case someone replaced it.
8887
parent = __import__(parent_name, fromlist=['__path__'])
89-
return _find_spec(fullname, parent.__path__)
88+
try:
89+
parent_path = parent.__path__
90+
except AttributeError as e:
91+
raise ModuleNotFoundError(
92+
f"__path__ attribute not found on {parent_name!r}"
93+
f"while trying to find {fullname!r}", name=fullname) from e
9094
else:
91-
return _find_spec(fullname, None)
95+
parent_path = None
96+
return _find_spec(fullname, parent_path)
9297
else:
9398
module = sys.modules[fullname]
9499
if module is None:

Lib/test/test_cmd_line_script.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ def test_dash_m_errors(self):
427427
tests = (
428428
('builtins', br'No code object available'),
429429
('builtins.x', br'Error while finding module specification.*'
430-
br'AttributeError'),
430+
br'ModuleNotFoundError'),
431431
('builtins.x.y', br'Error while finding module specification.*'
432432
br'ModuleNotFoundError.*No module named.*not a package'),
433433
('os.path', br'loader.*cannot handle'),

Lib/test/test_importlib/test_util.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,12 @@ def test_find_relative_module_missing_package(self):
522522
self.assertNotIn(name, sorted(sys.modules))
523523
self.assertNotIn(fullname, sorted(sys.modules))
524524

525+
def test_find_submodule_in_module(self):
526+
# ModuleNotFoundError raised when a module is specified as
527+
# a parent instead of a package.
528+
with self.assertRaises(ModuleNotFoundError):
529+
self.util.find_spec('module.name')
530+
525531

526532
(Frozen_FindSpecTests,
527533
Source_FindSpecTests

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ Library
441441
- bpo-30149: inspect.signature() now supports callables with
442442
variable-argument parameters wrapped with partialmethod.
443443
Patch by Dong-hee Na.
444+
445+
- bpo-30436: importlib.find_spec() raises ModuleNotFoundError instead of
446+
AttributeError if the specified parent module is not a package
447+
(i.e. lacks a __path__ attribute).
444448

445449
- bpo-30301: Fix AttributeError when using SimpleQueue.empty() under
446450
*spawn* and *forkserver* start methods.

0 commit comments

Comments
 (0)