Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b1611e2

Browse files
committed
Issue #15767: Introduce ModuleNotFoundError, a subclass of
ImportError. The exception is raised by import when a module could not be found. Technically this is defined as no viable loader could be found for the specified module. This includes ``from ... import`` statements so that the module usage is consistent for all situations where import couldn't find what was requested. This should allow for the common idiom of:: try: import something except ImportError: pass to be updated to using ModuleNotFoundError and not accidentally mask ImportError messages that should propagate (e.g. issues with a loader). This work was driven by the fact that the ``from ... import`` statement needed to be able to tell the difference between an ImportError that simply couldn't find a module (and thus silence the exception so that ceval can raise it) and an ImportError that represented an actual problem.
1 parent 638ce07 commit b1611e2

17 files changed

Lines changed: 424 additions & 408 deletions

File tree

Doc/c-api/exceptions.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,8 @@ the variables:
686686
+-----------------------------------------+---------------------------------+----------+
687687
| :c:data:`PyExc_ImportError` | :exc:`ImportError` | |
688688
+-----------------------------------------+---------------------------------+----------+
689+
| :c:data:`PyExc_ModuleNotFoundError` | :exc:`ModuleNotFoundError` | |
690+
+-----------------------------------------+---------------------------------+----------+
689691
| :c:data:`PyExc_IndexError` | :exc:`IndexError` | |
690692
+-----------------------------------------+---------------------------------+----------+
691693
| :c:data:`PyExc_InterruptedError` | :exc:`InterruptedError` | |

Doc/library/exceptions.rst

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ The following exceptions are the exceptions that are usually raised.
169169

170170
.. exception:: ImportError
171171

172-
Raised when an :keyword:`import` statement fails to find the module definition
173-
or when a ``from ... import`` fails to find a name that is to be imported.
172+
Raised when the :keyword:`import` statement has troubles trying to load a
173+
module.
174174

175175
The :attr:`name` and :attr:`path` attributes can be set using keyword-only
176176
arguments to the constructor. When set they represent the name of the module
@@ -180,6 +180,15 @@ The following exceptions are the exceptions that are usually raised.
180180
.. versionchanged:: 3.3
181181
Added the :attr:`name` and :attr:`path` attributes.
182182

183+
.. exception:: ModuleNotFoundError
184+
185+
A subclass of :exc:`ImportError` which is raised by :keyword:`import` when a
186+
module could not be located. This includes ``from ... import`` statements as
187+
the specific attribute being requested cannot be known a priori to be a module
188+
or some other type of object.
189+
190+
.. versionadded:: 3.4
191+
183192

184193
.. exception:: IndexError
185194

Doc/whatsnew/3.4.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ Some smaller changes made to the core Python language are:
137137

138138
* Unicode database updated to UCD version 6.2.
139139

140+
* Import now raises the new exception :exc:`ModuleNotFoundError` (subclass of
141+
:exc:`ImportError`) when it cannot find something.
142+
140143

141144

142145
New Modules

Include/pyerrors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ PyAPI_DATA(PyObject *) PyExc_EOFError;
152152
PyAPI_DATA(PyObject *) PyExc_FloatingPointError;
153153
PyAPI_DATA(PyObject *) PyExc_OSError;
154154
PyAPI_DATA(PyObject *) PyExc_ImportError;
155+
PyAPI_DATA(PyObject *) PyExc_ModuleNotFoundError;
155156
PyAPI_DATA(PyObject *) PyExc_IndexError;
156157
PyAPI_DATA(PyObject *) PyExc_KeyError;
157158
PyAPI_DATA(PyObject *) PyExc_KeyboardInterrupt;

Lib/importlib/_bootstrap.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,11 +1553,7 @@ def _find_and_load_unlocked(name, import_):
15531553
raise ImportError(msg, name=name)
15541554
loader = _find_module(name, path)
15551555
if loader is None:
1556-
exc = ImportError(_ERR_MSG.format(name), name=name)
1557-
# TODO(brett): switch to a proper ModuleNotFound exception in Python
1558-
# 3.4.
1559-
exc._not_found = True
1560-
raise exc
1556+
raise ModuleNotFoundError(_ERR_MSG.format(name), name=name)
15611557
elif name not in sys.modules:
15621558
# The parent import may have already imported this module.
15631559
loader.load_module(name)
@@ -1643,15 +1639,12 @@ def _handle_fromlist(module, fromlist, import_):
16431639
from_name = '{}.{}'.format(module.__name__, x)
16441640
try:
16451641
_call_with_frames_removed(import_, from_name)
1646-
except ImportError as exc:
1642+
except ModuleNotFoundError as exc:
16471643
# Backwards-compatibility dictates we ignore failed
16481644
# imports triggered by fromlist for modules that don't
16491645
# exist.
1650-
# TODO(brett): In Python 3.4, have import raise
1651-
# ModuleNotFound and catch that.
1652-
if getattr(exc, '_not_found', False):
1653-
if exc.name == from_name:
1654-
continue
1646+
if exc.name == from_name:
1647+
continue
16551648
raise
16561649
return module
16571650

Lib/pydoc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ def safeimport(path, forceload=0, cache={}):
317317
elif exc is SyntaxError:
318318
# A SyntaxError occurred before we could execute the module.
319319
raise ErrorDuringImport(value.filename, info)
320-
elif exc is ImportError and value.name == path:
320+
elif issubclass(exc, ImportError) and value.name == path:
321321
# No such module in the path.
322322
return None
323323
else:

Lib/test/exception_hierarchy.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ BaseException
1313
+-- BufferError
1414
+-- EOFError
1515
+-- ImportError
16+
+-- ModuleNotFoundError
1617
+-- LookupError
1718
| +-- IndexError
1819
| +-- KeyError

Lib/test/test_exceptions.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -953,8 +953,5 @@ def test_non_str_argument(self):
953953
self.assertEqual(str(arg), str(exc))
954954

955955

956-
def test_main():
957-
run_unittest(ExceptionTests, ImportErrorTests)
958-
959956
if __name__ == '__main__':
960957
unittest.main()

Lib/test/test_import.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,15 @@ def setUp(self):
6868
def tearDown(self):
6969
unload(TESTFN)
7070

71-
setUp = tearDown
71+
def test_import_raises_ModuleNotFoundError(self):
72+
with self.assertRaises(ModuleNotFoundError):
73+
import something_that_should_not_exist_anywhere
74+
75+
def test_from_import_raises_ModuleNotFoundError(self):
76+
with self.assertRaises(ModuleNotFoundError):
77+
from something_that_should_not_exist_anywhere import blah
78+
with self.assertRaises(ModuleNotFoundError):
79+
from importlib import something_that_should_not_exist_anywhere
7280

7381
def test_case_sensitivity(self):
7482
# Brief digression to test that import is case-sensitive: if we got
@@ -487,7 +495,7 @@ def test_foreign_code(self):
487495
header = f.read(12)
488496
code = marshal.load(f)
489497
constants = list(code.co_consts)
490-
foreign_code = test_main.__code__
498+
foreign_code = importlib.import_module.__code__
491499
pos = constants.index(1)
492500
constants[pos] = foreign_code
493501
code = type(code)(code.co_argcount, code.co_kwonlyargcount,
@@ -1014,16 +1022,5 @@ def load_module(*args):
10141022
importlib.SourceLoader.load_module = old_load_module
10151023

10161024

1017-
def test_main(verbose=None):
1018-
run_unittest(ImportTests, PycacheTests, FilePermissionTests,
1019-
PycRewritingTests, PathsTests, RelativeImportTests,
1020-
OverridingImportBuiltinTests,
1021-
ImportlibBootstrapTests,
1022-
TestSymbolicallyLinkedPackage,
1023-
ImportTracebackTests)
1024-
1025-
10261025
if __name__ == '__main__':
1027-
# Test needs to be a package, so we can do relative imports.
1028-
from test.test_import import test_main
1029-
test_main()
1026+
unittest.main()

Lib/test/test_importlib/import_/test_api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class APITest(unittest.TestCase):
2222
"""Test API-specific details for __import__ (e.g. raising the right
2323
exception when passing in an int for the module name)."""
2424

25+
def test_raises_ModuleNotFoundError(self):
26+
with self.assertRaises(ModuleNotFoundError):
27+
util.import_('some module that does not exist')
28+
2529
def test_name_requires_rparition(self):
2630
# Raise TypeError if a non-string is passed in for the module name.
2731
with self.assertRaises(TypeError):

0 commit comments

Comments
 (0)