Skip to content

bpo-37444: Update differing exception between builtins and importlib #14869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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

.. 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)} '
'(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.