Skip to content
11 changes: 8 additions & 3 deletions Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1433,13 +1433,18 @@ an :term:`importer`.
``importlib.util.resolve_name('sys', __package__)`` without doing a
check to see if the **package** argument is needed.

:exc:`ValueError` is raised if **name** is a relative module name but
package is a false value (e.g. ``None`` or the empty string).
:exc:`ValueError` is also raised a relative name would escape its containing
:exc:`ImportError` is raised if **name** is a relative module name but
**package** is a false value (e.g. ``None`` or the empty string).
:exc:`ImportError` is also raised a relative name would escape its containing
package (e.g. requesting ``..bacon`` from within the ``spam`` package).

.. versionadded:: 3.3
Comment thread
ncoghlan marked this conversation as resolved.

.. versionchanged:: 3.9
To improve consistency with import statements, raise
:exc:`ImportError` instead of :exc:`ValueError` for invalid relative
import attempts.

.. function:: find_spec(name, package=None)

Find the :term:`spec <module spec>` for a module, optionally relative to
Expand Down
20 changes: 20 additions & 0 deletions Doc/whatsnew/3.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ New Features
Other Language Changes
======================

* :func:`builtins.__import__` now raises :exc:`ImportError` instead of
:exc:`ValueError` as used to occur when a relative import went past
its top-level package.
(Contributed by Ngalim Siregar in :issue:`37444`.)


* Python now gets the absolute path of the script filename specified on
the command line (ex: ``python3 script.py``): the ``__file__`` attribute of
the ``__main__`` module, ``sys.argv[0]`` and ``sys.path[0]`` become an
Expand Down Expand Up @@ -118,6 +124,13 @@ pprint
:mod:`pprint` can now pretty-print :class:`types.SimpleNamespace`.
(Contributed by Carl Bordum Hansen in :issue:`37376`.)

importlib
---------

To improve consistency with import statements, :func:`importlib.util.resolve_name`
now raises :exc:`ImportError` instead of :exc:`ValueError` for invalid relative
import attempts.
(Contributed by Ngalim Siregar in :issue:`37444`.)

Optimizations
=============
Expand Down Expand Up @@ -176,4 +189,11 @@ Porting to Python 3.9
This section lists previously described changes and other bugfixes
that may require changes to your code.

Changes in the Python API
-------------------------

* :func:`builtins.__import__` and :func:`importlib.util.resolve_name` now raise
:exc:`ImportError` where it previously raised :exc:`ValueError`. Callers
catching the specific exception type and supporting both Python 3.9 and
earlier versions will need to catch both:
``except (ImportError, ValueError):``
2 changes: 1 addition & 1 deletion Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ def _resolve_name(name, package, level):
"""Resolve a relative module name to an absolute one."""
bits = package.rsplit('.', level - 1)
if len(bits) < level:
raise ValueError('attempted relative import beyond top-level package')
raise ImportError('attempted relative import beyond top-level package')
base = bits[0]
return '{}.{}'.format(base, name) if name else base

Expand Down
4 changes: 2 additions & 2 deletions Lib/importlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def resolve_name(name, package):
if not name.startswith('.'):
return name
elif not package:
raise ValueError(f'no package specified for {repr(name)} '
'(required for relative module names)')
raise ImportError(f'no package specified for {repr(name)} '
Comment thread
ncoghlan marked this conversation as resolved.
'(required for relative module names)')
level = 0
for character in name:
if character != '.':
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_importlib/import_/test_relative_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def test_too_high_from_package(self):
{'__name__': 'pkg', '__path__': ['blah']})
def callback(global_):
self.__import__('pkg')
with self.assertRaises(ValueError):
with self.assertRaises(ImportError):
self.__import__('', global_, fromlist=['top_level'],
level=2)
self.relative_import_test(create, globals_, callback)
Expand All @@ -167,7 +167,7 @@ def test_too_high_from_module(self):
globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
def callback(global_):
self.__import__('pkg')
with self.assertRaises(ValueError):
with self.assertRaises(ImportError):
self.__import__('', global_, fromlist=['top_level'],
level=2)
self.relative_import_test(create, globals_, callback)
Expand Down
6 changes: 3 additions & 3 deletions Lib/test/test_importlib/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ def test_absolute_within_package(self):

def test_no_package(self):
# .bacon in ''
with self.assertRaises(ValueError):
with self.assertRaises(ImportError):
self.util.resolve_name('.bacon', '')

def test_in_package(self):
Expand All @@ -390,7 +390,7 @@ def test_other_package(self):

def test_escape(self):
# ..bacon in spam
with self.assertRaises(ValueError):
with self.assertRaises(ImportError):
self.util.resolve_name('..bacon', 'spam')


Expand Down Expand Up @@ -518,7 +518,7 @@ def test_find_relative_module_missing_package(self):
with util.temp_module(name, pkg=True) as pkg_dir:
fullname, _ = util.submodule(name, subname, pkg_dir)
relname = '.' + subname
with self.assertRaises(ValueError):
with self.assertRaises(ImportError):
self.util.find_spec(relname)
self.assertNotIn(name, sorted(sys.modules))
self.assertNotIn(fullname, sorted(sys.modules))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update differing exception between :meth:`builtins.__import__` and :meth:`importlib.__import__`.
2 changes: 1 addition & 1 deletion Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -1671,7 +1671,7 @@ resolve_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level
goto error;
}
else if (last_dot == -1) {
_PyErr_SetString(tstate, PyExc_ValueError,
_PyErr_SetString(tstate, PyExc_ImportError,
"attempted relative import beyond top-level "
"package");
goto error;
Expand Down
192 changes: 96 additions & 96 deletions Python/importlib.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.