Commit c5fa4494 authored by Ngalim Siregar's avatar Ngalim Siregar Committed by Miss Islington (bot)

bpo-37444: Update differing exception between builtins and importlib (GH-14869)



Imports now raise `TypeError` instead of `ValueError` for relative import failures. This makes things consistent between `builtins.__import__` and `importlib.__import__` as well as using a more natural import for the failure.


https://bugs.python.org/issue37444



Automerge-Triggered-By: @brettcannon
parent 8e568ef2
...@@ -1433,13 +1433,18 @@ an :term:`importer`. ...@@ -1433,13 +1433,18 @@ an :term:`importer`.
``importlib.util.resolve_name('sys', __package__)`` without doing a ``importlib.util.resolve_name('sys', __package__)`` without doing a
check to see if the **package** argument is needed. check to see if the **package** argument is needed.
:exc:`ValueError` is raised if **name** is a relative module name but :exc:`ImportError` is raised if **name** is a relative module name but
package is a false value (e.g. ``None`` or the empty string). **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 also raised a relative name would escape its containing
package (e.g. requesting ``..bacon`` from within the ``spam`` package). package (e.g. requesting ``..bacon`` from within the ``spam`` package).
.. versionadded:: 3.3 .. 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) .. function:: find_spec(name, package=None)
Find the :term:`spec <module spec>` for a module, optionally relative to Find the :term:`spec <module spec>` for a module, optionally relative to
......
...@@ -75,6 +75,12 @@ New Features ...@@ -75,6 +75,12 @@ New Features
Other Language Changes 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 * Python now gets the absolute path of the script filename specified on
the command line (ex: ``python3 script.py``): the ``__file__`` attribute of the command line (ex: ``python3 script.py``): the ``__file__`` attribute of
the ``__main__`` module, ``sys.argv[0]`` and ``sys.path[0]`` become an the ``__main__`` module, ``sys.argv[0]`` and ``sys.path[0]`` become an
...@@ -118,6 +124,13 @@ pprint ...@@ -118,6 +124,13 @@ pprint
:mod:`pprint` can now pretty-print :class:`types.SimpleNamespace`. :mod:`pprint` can now pretty-print :class:`types.SimpleNamespace`.
(Contributed by Carl Bordum Hansen in :issue:`37376`.) (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 Optimizations
============= =============
...@@ -180,4 +193,11 @@ Porting to Python 3.9 ...@@ -180,4 +193,11 @@ Porting to Python 3.9
This section lists previously described changes and other bugfixes This section lists previously described changes and other bugfixes
that may require changes to your code. 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):``
...@@ -873,7 +873,7 @@ def _resolve_name(name, package, level): ...@@ -873,7 +873,7 @@ def _resolve_name(name, package, level):
"""Resolve a relative module name to an absolute one.""" """Resolve a relative module name to an absolute one."""
bits = package.rsplit('.', level - 1) bits = package.rsplit('.', level - 1)
if len(bits) < level: 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] base = bits[0]
return '{}.{}'.format(base, name) if name else base return '{}.{}'.format(base, name) if name else base
......
...@@ -29,7 +29,7 @@ def resolve_name(name, package): ...@@ -29,7 +29,7 @@ def resolve_name(name, package):
if not name.startswith('.'): if not name.startswith('.'):
return name return name
elif not package: elif not package:
raise ValueError(f'no package specified for {repr(name)} ' raise ImportError(f'no package specified for {repr(name)} '
'(required for relative module names)') '(required for relative module names)')
level = 0 level = 0
for character in name: for character in name:
......
...@@ -156,7 +156,7 @@ class RelativeImports: ...@@ -156,7 +156,7 @@ class RelativeImports:
{'__name__': 'pkg', '__path__': ['blah']}) {'__name__': 'pkg', '__path__': ['blah']})
def callback(global_): def callback(global_):
self.__import__('pkg') self.__import__('pkg')
with self.assertRaises(ValueError): with self.assertRaises(ImportError):
self.__import__('', global_, fromlist=['top_level'], self.__import__('', global_, fromlist=['top_level'],
level=2) level=2)
self.relative_import_test(create, globals_, callback) self.relative_import_test(create, globals_, callback)
...@@ -167,7 +167,7 @@ class RelativeImports: ...@@ -167,7 +167,7 @@ class RelativeImports:
globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'} globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
def callback(global_): def callback(global_):
self.__import__('pkg') self.__import__('pkg')
with self.assertRaises(ValueError): with self.assertRaises(ImportError):
self.__import__('', global_, fromlist=['top_level'], self.__import__('', global_, fromlist=['top_level'],
level=2) level=2)
self.relative_import_test(create, globals_, callback) self.relative_import_test(create, globals_, callback)
......
...@@ -375,7 +375,7 @@ class ResolveNameTests: ...@@ -375,7 +375,7 @@ class ResolveNameTests:
def test_no_package(self): def test_no_package(self):
# .bacon in '' # .bacon in ''
with self.assertRaises(ValueError): with self.assertRaises(ImportError):
self.util.resolve_name('.bacon', '') self.util.resolve_name('.bacon', '')
def test_in_package(self): def test_in_package(self):
...@@ -390,7 +390,7 @@ class ResolveNameTests: ...@@ -390,7 +390,7 @@ class ResolveNameTests:
def test_escape(self): def test_escape(self):
# ..bacon in spam # ..bacon in spam
with self.assertRaises(ValueError): with self.assertRaises(ImportError):
self.util.resolve_name('..bacon', 'spam') self.util.resolve_name('..bacon', 'spam')
...@@ -518,7 +518,7 @@ class FindSpecTests: ...@@ -518,7 +518,7 @@ class FindSpecTests:
with util.temp_module(name, pkg=True) as pkg_dir: with util.temp_module(name, pkg=True) as pkg_dir:
fullname, _ = util.submodule(name, subname, pkg_dir) fullname, _ = util.submodule(name, subname, pkg_dir)
relname = '.' + subname relname = '.' + subname
with self.assertRaises(ValueError): with self.assertRaises(ImportError):
self.util.find_spec(relname) self.util.find_spec(relname)
self.assertNotIn(name, sorted(sys.modules)) self.assertNotIn(name, sorted(sys.modules))
self.assertNotIn(fullname, sorted(sys.modules)) self.assertNotIn(fullname, sorted(sys.modules))
......
Update differing exception between :meth:`builtins.__import__` and :meth:`importlib.__import__`.
...@@ -1671,7 +1671,7 @@ resolve_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level ...@@ -1671,7 +1671,7 @@ resolve_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level
goto error; goto error;
} }
else if (last_dot == -1) { else if (last_dot == -1) {
_PyErr_SetString(tstate, PyExc_ValueError, _PyErr_SetString(tstate, PyExc_ImportError,
"attempted relative import beyond top-level " "attempted relative import beyond top-level "
"package"); "package");
goto error; goto error;
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment