Skip to content

Commit 46f97b8

Browse files
Issue #15767: Use ModuleNotFoundError.
1 parent c943265 commit 46f97b8

File tree

16 files changed

+337
-284
lines changed

16 files changed

+337
-284
lines changed

Doc/c-api/exceptions.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,13 @@ an error value).
306306
:mod:`warnings` module and the :option:`-W` option in the command line
307307
documentation. There is no C API for warning control.
308308
309+
.. c:function:: PyObject* PyErr_SetImportErrorSubclass(PyObject *msg, PyObject *name, PyObject *path)
310+
311+
Much like :c:func:`PyErr_SetImportError` but this function allows for
312+
specifying a subclass of :exc:`ImportError` to raise.
313+
314+
.. versionadded:: 3.4
315+
309316
310317
.. c:function:: int PyErr_WarnExplicitObject(PyObject *category, PyObject *message, PyObject *filename, int lineno, PyObject *module, PyObject *registry)
311318

Doc/reference/import.rst

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ implement import semantics.
3636

3737
When a module is first imported, Python searches for the module and if found,
3838
it creates a module object [#fnmo]_, initializing it. If the named module
39-
cannot be found, an :exc:`ImportError` is raised. Python implements various
39+
cannot be found, an :exc:`ModuleNotFoundError` is raised. Python implements various
4040
strategies to search for the named module when the import machinery is
4141
invoked. These strategies can be modified and extended by using various hooks
4242
described in the sections below.
@@ -167,7 +167,7 @@ arguments to the :keyword:`import` statement, or from the parameters to the
167167
This name will be used in various phases of the import search, and it may be
168168
the dotted path to a submodule, e.g. ``foo.bar.baz``. In this case, Python
169169
first tries to import ``foo``, then ``foo.bar``, and finally ``foo.bar.baz``.
170-
If any of the intermediate imports fail, an :exc:`ImportError` is raised.
170+
If any of the intermediate imports fail, an :exc:`ModuleNotFoundError` is raised.
171171

172172

173173
The module cache
@@ -186,15 +186,15 @@ object.
186186
During import, the module name is looked up in :data:`sys.modules` and if
187187
present, the associated value is the module satisfying the import, and the
188188
process completes. However, if the value is ``None``, then an
189-
:exc:`ImportError` is raised. If the module name is missing, Python will
189+
:exc:`ModuleNotFoundError` is raised. If the module name is missing, Python will
190190
continue searching for the module.
191191

192192
:data:`sys.modules` is writable. Deleting a key may not destroy the
193193
associated module (as other modules may hold references to it),
194194
but it will invalidate the cache entry for the named module, causing
195195
Python to search anew for the named module upon its next
196196
import. The key can also be assigned to ``None``, forcing the next import
197-
of the module to result in an :exc:`ImportError`.
197+
of the module to result in an :exc:`ModuleNotFoundError`.
198198

199199
Beware though, as if you keep a reference to the module object,
200200
invalidate its cache entry in :data:`sys.modules`, and then re-import the
@@ -288,8 +288,8 @@ the named module or not.
288288
If the meta path finder knows how to handle the named module, it returns a
289289
spec object. If it cannot handle the named module, it returns ``None``. If
290290
:data:`sys.meta_path` processing reaches the end of its list without returning
291-
a spec, then an :exc:`ImportError` is raised. Any other exceptions raised
292-
are simply propagated up, aborting the import process.
291+
a spec, then a :exc:`ModuleNotFoundError` is raised. Any other exceptions
292+
raised are simply propagated up, aborting the import process.
293293

294294
The :meth:`~importlib.abc.MetaPathFinder.find_spec()` method of meta path
295295
finders is called with two or three arguments. The first is the fully
@@ -298,9 +298,9 @@ The second argument is the path entries to use for the module search. For
298298
top-level modules, the second argument is ``None``, but for submodules or
299299
subpackages, the second argument is the value of the parent package's
300300
``__path__`` attribute. If the appropriate ``__path__`` attribute cannot
301-
be accessed, an :exc:`ImportError` is raised. The third argument is an
302-
existing module object that will be the target of loading later. The
303-
import system passes in a target module only during reload.
301+
be accessed, an :exc:`ModuleNotFoundError` is raised. The third argument
302+
is an existing module object that will be the target of loading later.
303+
The import system passes in a target module only during reload.
304304

305305
The meta path may be traversed multiple times for a single import request.
306306
For example, assuming none of the modules involved has already been cached,
@@ -887,7 +887,7 @@ import statements within that module.
887887

888888
To selectively prevent import of some modules from a hook early on the
889889
meta path (rather than disabling the standard import system entirely),
890-
it is sufficient to raise :exc:`ImportError` directly from
890+
it is sufficient to raise :exc:`ModuleNoFoundError` directly from
891891
:meth:`~importlib.abc.MetaPathFinder.find_spec` instead of returning
892892
``None``. The latter indicates that the meta path search should continue,
893893
while raising an exception terminates it immediately.

Doc/whatsnew/3.6.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ Some smaller changes made to the core Python language are:
350350
:ref:`py36-traceback` for an example).
351351
(Contributed by Emanuel Barry in :issue:`26823`.)
352352

353+
* Import now raises the new exception :exc:`ModuleNotFoundError`
354+
(subclass of :exc:`ImportError`) when it cannot find a module. Code
355+
that current checks for ImportError (in try-except) will still work.
356+
353357

354358
New Modules
355359
===========
@@ -959,6 +963,9 @@ Changes in the Python API
959963
* When :meth:`importlib.abc.Loader.exec_module` is defined,
960964
:meth:`importlib.abc.Loader.create_module` must also be defined.
961965

966+
* :c:func:`PyErr_SetImportError` now sets :exc:`TypeError` when its **msg**
967+
argument is not set. Previously only ``NULL`` was returned.
968+
962969
* The format of the ``co_lnotab`` attribute of code objects changed to support
963970
negative line number delta. By default, Python does not emit bytecode with
964971
negative line number delta. Functions using ``frame.f_lineno``,

Include/pyerrors.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ PyAPI_FUNC(PyObject *) PyErr_SetExcFromWindowsErr(PyObject *, int);
284284

285285
PyAPI_FUNC(PyObject *) PyErr_SetExcWithArgsKwargs(PyObject *, PyObject *,
286286
PyObject *);
287+
288+
PyAPI_FUNC(PyObject *) PyErr_SetImportErrorSubclass(PyObject *, PyObject *,
289+
PyObject *, PyObject *);
287290
PyAPI_FUNC(PyObject *) PyErr_SetImportError(PyObject *, PyObject *,
288291
PyObject *);
289292

Lib/importlib/_bootstrap.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -943,10 +943,10 @@ def _find_and_load_unlocked(name, import_):
943943
path = parent_module.__path__
944944
except AttributeError:
945945
msg = (_ERR_MSG + '; {!r} is not a package').format(name, parent)
946-
raise ImportError(msg, name=name) from None
946+
raise ModuleNotFoundError(msg, name=name) from None
947947
spec = _find_spec(name, path)
948948
if spec is None:
949-
raise ImportError(_ERR_MSG.format(name), name=name)
949+
raise ModuleNotFoundError(_ERR_MSG.format(name), name=name)
950950
else:
951951
module = _load_unlocked(spec)
952952
if parent:
@@ -982,10 +982,11 @@ def _gcd_import(name, package=None, level=0):
982982
_imp.release_lock()
983983
message = ('import of {} halted; '
984984
'None in sys.modules'.format(name))
985-
raise ImportError(message, name=name)
985+
raise ModuleNotFoundError(message, name=name)
986986
_lock_unlock_module(name)
987987
return module
988988

989+
989990
def _handle_fromlist(module, fromlist, import_):
990991
"""Figure out what __import__ should return.
991992
@@ -1007,13 +1008,12 @@ def _handle_fromlist(module, fromlist, import_):
10071008
from_name = '{}.{}'.format(module.__name__, x)
10081009
try:
10091010
_call_with_frames_removed(import_, from_name)
1010-
except ImportError as exc:
1011+
except ModuleNotFoundError as exc:
10111012
# Backwards-compatibility dictates we ignore failed
10121013
# imports triggered by fromlist for modules that don't
10131014
# exist.
1014-
if str(exc).startswith(_ERR_MSG_PREFIX):
1015-
if exc.name == from_name:
1016-
continue
1015+
if exc.name == from_name:
1016+
continue
10171017
raise
10181018
return module
10191019

Lib/pydoc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def safeimport(path, forceload=0, cache={}):
350350
elif exc is SyntaxError:
351351
# A SyntaxError occurred before we could execute the module.
352352
raise ErrorDuringImport(value.filename, info)
353-
elif exc is ImportError and value.name == path:
353+
elif issubclass(exc, ImportError) and value.name == path:
354354
# No such module in the path.
355355
return None
356356
else:

Lib/test/test_cmd_line_script.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ def test_dash_m_errors(self):
428428
('builtins.x', br'Error while finding module specification.*'
429429
br'AttributeError'),
430430
('builtins.x.y', br'Error while finding module specification.*'
431-
br'ImportError.*No module named.*not a package'),
431+
br'ModuleNotFoundError.*No module named.*not a package'),
432432
('os.path', br'loader.*cannot handle'),
433433
('importlib', br'No module named.*'
434434
br'is a package and cannot be directly executed'),

Lib/test/test_import/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,18 @@ def setUp(self):
6969
def tearDown(self):
7070
unload(TESTFN)
7171

72+
def test_import_raises_ModuleNotFoundError(self):
73+
with self.assertRaises(ModuleNotFoundError):
74+
import something_that_should_not_exist_anywhere
75+
76+
def test_from_import_missing_module_raises_ModuleNotFoundError(self):
77+
with self.assertRaises(ModuleNotFoundError):
78+
from something_that_should_not_exist_anywhere import blah
79+
80+
def test_from_import_missing_attr_raises_ImportError(self):
81+
with self.assertRaises(ImportError):
82+
from importlib import something_that_should_not_exist_anywhere
83+
7284
def test_case_sensitivity(self):
7385
# Brief digression to test that import is case-sensitive: if we got
7486
# this far, we know for sure that "random" exists.

Lib/test/test_importlib/import_/test_api.py

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

46+
def test_raises_ModuleNotFoundError(self):
47+
with self.assertRaises(ModuleNotFoundError):
48+
util.import_importlib('some module that does not exist')
49+
4650
def test_name_requires_rparition(self):
4751
# Raise TypeError if a non-string is passed in for the module name.
4852
with self.assertRaises(TypeError):

Lib/test/test_importlib/import_/test_fromlist.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,16 @@ def test_module_from_package(self):
7373
self.assertTrue(hasattr(module, 'module'))
7474
self.assertEqual(module.module.__name__, 'pkg.module')
7575

76-
def test_module_from_package_triggers_ImportError(self):
77-
# If a submodule causes an ImportError because it tries to import
78-
# a module which doesn't exist, that should let the ImportError
79-
# propagate.
76+
def test_module_from_package_triggers_ModuleNotFoundError(self):
77+
# If a submodule causes an ModuleNotFoundError because it tries
78+
# to import a module which doesn't exist, that should let the
79+
# ModuleNotFoundError propagate.
8080
def module_code():
8181
import i_do_not_exist
8282
with util.mock_modules('pkg.__init__', 'pkg.mod',
8383
module_code={'pkg.mod': module_code}) as importer:
8484
with util.import_state(meta_path=[importer]):
85-
with self.assertRaises(ImportError) as exc:
85+
with self.assertRaises(ModuleNotFoundError) as exc:
8686
self.__import__('pkg', fromlist=['mod'])
8787
self.assertEqual('i_do_not_exist', exc.exception.name)
8888

Lib/test/test_pydoc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ class C(builtins.object)
263263
Use help(str) for help on the str class.'''.replace('\n', os.linesep)
264264

265265
# output pattern for module with bad imports
266-
badimport_pattern = "problem in %s - ImportError: No module named %r"
266+
badimport_pattern = "problem in %s - ModuleNotFoundError: No module named %r"
267267

268268
expected_dynamicattribute_pattern = """
269269
Help on class DA in module %s:

Lib/test/test_site.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_addpackage_import_bad_exec(self):
138138
re.escape(os.path.join(pth_dir, pth_fn)))
139139
# XXX: ditto previous XXX comment.
140140
self.assertRegex(err_out.getvalue(), 'Traceback')
141-
self.assertRegex(err_out.getvalue(), 'ImportError')
141+
self.assertRegex(err_out.getvalue(), 'ModuleNotFoundError')
142142

143143
@unittest.skipIf(sys.platform == "win32", "Windows does not raise an "
144144
"error for file paths containing null characters")

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9966,6 +9966,10 @@ C-API
99669966
PyImport_ExecCodeModuleWithPathnames() (and thus by extension
99679967
PyImport_ExecCodeModule() and PyImport_ExecCodeModuleEx()).
99689968

9969+
- Issue #15767: Added PyErr_SetImportErrorSubclass().
9970+
9971+
- PyErr_SetImportError() now sets TypeError when its msg argument is set.
9972+
99699973
- Issue #9369: The types of `char*` arguments of PyObject_CallFunction() and
99709974
PyObject_CallMethod() now changed to `const char*`. Based on patches by
99719975
Jörg Müller and Lars Buitinck.

Python/errors.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -697,35 +697,45 @@ PyObject *PyErr_SetFromWindowsErrWithUnicodeFilename(
697697
#endif /* MS_WINDOWS */
698698

699699
PyObject *
700-
PyErr_SetImportError(PyObject *msg, PyObject *name, PyObject *path)
700+
PyErr_SetImportErrorSubclass(PyObject *exception, PyObject *msg,
701+
PyObject *name, PyObject *path)
701702
{
703+
int issubclass;
702704
PyObject *kwargs, *error;
703705

704-
if (msg == NULL) {
706+
issubclass = PyObject_IsSubclass(exception, PyExc_ImportError);
707+
if (issubclass < 0) {
708+
return NULL;
709+
}
710+
else if (!issubclass) {
711+
PyErr_SetString(PyExc_TypeError, "expected a subclass of ImportError");
705712
return NULL;
706713
}
707714

708-
kwargs = PyDict_New();
709-
if (kwargs == NULL) {
715+
if (msg == NULL) {
716+
PyErr_SetString(PyExc_TypeError, "expected a message argument");
710717
return NULL;
711718
}
712719

713720
if (name == NULL) {
714721
name = Py_None;
715722
}
716-
717723
if (path == NULL) {
718724
path = Py_None;
719725
}
720726

727+
kwargs = PyDict_New();
728+
if (kwargs == NULL) {
729+
return NULL;
730+
}
721731
if (PyDict_SetItemString(kwargs, "name", name) < 0) {
722732
goto done;
723733
}
724734
if (PyDict_SetItemString(kwargs, "path", path) < 0) {
725735
goto done;
726736
}
727737

728-
error = _PyObject_FastCallDict(PyExc_ImportError, &msg, 1, kwargs);
738+
error = _PyObject_FastCallDict(exception, &msg, 1, kwargs);
729739
if (error != NULL) {
730740
PyErr_SetObject((PyObject *)Py_TYPE(error), error);
731741
Py_DECREF(error);
@@ -736,6 +746,12 @@ PyErr_SetImportError(PyObject *msg, PyObject *name, PyObject *path)
736746
return NULL;
737747
}
738748

749+
PyObject *
750+
PyErr_SetImportError(PyObject *msg, PyObject *name, PyObject *path)
751+
{
752+
return PyErr_SetImportErrorSubclass(PyExc_ImportError, msg, name, path);
753+
}
754+
739755
void
740756
_PyErr_BadInternalCall(const char *filename, int lineno)
741757
{

Python/import.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1539,7 +1539,8 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
15391539
PyObject *msg = PyUnicode_FromFormat("import of %R halted; "
15401540
"None in sys.modules", abs_name);
15411541
if (msg != NULL) {
1542-
PyErr_SetImportError(msg, abs_name, NULL);
1542+
PyErr_SetImportErrorSubclass(PyExc_ModuleNotFoundError, msg,
1543+
abs_name, NULL);
15431544
Py_DECREF(msg);
15441545
}
15451546
mod = NULL;

0 commit comments

Comments
 (0)