Commit 8a9cd20e authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

bpo-30876: Relative import from unloaded package now reimports the package (#2639)

instead of failing with SystemError.

Relative import from non-package now fails with ImportError rather than
SystemError.
parent 6d13b22e
...@@ -919,10 +919,6 @@ def _sanity_check(name, package, level): ...@@ -919,10 +919,6 @@ def _sanity_check(name, package, level):
elif not package: elif not package:
raise ImportError('attempted relative import with no known parent ' raise ImportError('attempted relative import with no known parent '
'package') 'package')
elif package not in sys.modules:
msg = ('Parent module {!r} not loaded, cannot perform relative '
'import')
raise SystemError(msg.format(package))
if not name and level == 0: if not name and level == 0:
raise ValueError('Empty module name') raise ValueError('Empty module name')
......
...@@ -23,8 +23,9 @@ from test.support import ( ...@@ -23,8 +23,9 @@ from test.support import (
EnvironmentVarGuard, TESTFN, check_warnings, forget, is_jython, EnvironmentVarGuard, TESTFN, check_warnings, forget, is_jython,
make_legacy_pyc, rmtree, run_unittest, swap_attr, swap_item, temp_umask, make_legacy_pyc, rmtree, run_unittest, swap_attr, swap_item, temp_umask,
unlink, unload, create_empty_file, cpython_only, TESTFN_UNENCODABLE, unlink, unload, create_empty_file, cpython_only, TESTFN_UNENCODABLE,
temp_dir) temp_dir, DirsOnSysPath)
from test.support import script_helper from test.support import script_helper
from test.test_importlib.util import uncache
skip_if_dont_write_bytecode = unittest.skipIf( skip_if_dont_write_bytecode = unittest.skipIf(
...@@ -670,11 +671,11 @@ class RelativeImportTests(unittest.TestCase): ...@@ -670,11 +671,11 @@ class RelativeImportTests(unittest.TestCase):
# Check relative import fails with only __package__ wrong # Check relative import fails with only __package__ wrong
ns = dict(__package__='foo', __name__='test.notarealmodule') ns = dict(__package__='foo', __name__='test.notarealmodule')
self.assertRaises(SystemError, check_relative) self.assertRaises(ModuleNotFoundError, check_relative)
# Check relative import fails with __package__ and __name__ wrong # Check relative import fails with __package__ and __name__ wrong
ns = dict(__package__='foo', __name__='notarealpkg.notarealmodule') ns = dict(__package__='foo', __name__='notarealpkg.notarealmodule')
self.assertRaises(SystemError, check_relative) self.assertRaises(ModuleNotFoundError, check_relative)
# Check relative import fails with package set to a non-string # Check relative import fails with package set to a non-string
ns = dict(__package__=object()) ns = dict(__package__=object())
...@@ -689,6 +690,20 @@ class RelativeImportTests(unittest.TestCase): ...@@ -689,6 +690,20 @@ class RelativeImportTests(unittest.TestCase):
self.fail("explicit relative import triggered an " self.fail("explicit relative import triggered an "
"implicit absolute import") "implicit absolute import")
def test_import_from_non_package(self):
path = os.path.join(os.path.dirname(__file__), 'data', 'package2')
with uncache('submodule1', 'submodule2'), DirsOnSysPath(path):
with self.assertRaises(ImportError):
import submodule1
self.assertNotIn('submodule1', sys.modules)
self.assertNotIn('submodule2', sys.modules)
def test_import_from_unloaded_package(self):
with uncache('package2', 'package2.submodule1', 'package2.submodule2'), \
DirsOnSysPath(os.path.join(os.path.dirname(__file__), 'data')):
import package2.submodule1
package2.submodule1.submodule2
class OverridingImportBuiltinTests(unittest.TestCase): class OverridingImportBuiltinTests(unittest.TestCase):
def test_override_builtin(self): def test_override_builtin(self):
......
import sys
sys.modules.pop(__package__, None)
from . import submodule2
...@@ -81,7 +81,7 @@ class Using__package__: ...@@ -81,7 +81,7 @@ class Using__package__:
def test_bad__package__(self): def test_bad__package__(self):
globals = {'__package__': '<not real>'} globals = {'__package__': '<not real>'}
with self.assertRaises(SystemError): with self.assertRaises(ModuleNotFoundError):
self.__import__('', globals, {}, ['relimport'], 1) self.__import__('', globals, {}, ['relimport'], 1)
def test_bunk__package__(self): def test_bunk__package__(self):
......
Relative import from unloaded package now reimports the package instead of
failing with SystemError. Relative import from non-package now fails with
ImportError rather than SystemError.
...@@ -1344,7 +1344,6 @@ resolve_name(PyObject *name, PyObject *globals, int level) ...@@ -1344,7 +1344,6 @@ resolve_name(PyObject *name, PyObject *globals, int level)
PyObject *abs_name; PyObject *abs_name;
PyObject *package = NULL; PyObject *package = NULL;
PyObject *spec; PyObject *spec;
PyInterpreterState *interp = PyThreadState_GET()->interp;
Py_ssize_t last_dot; Py_ssize_t last_dot;
PyObject *base; PyObject *base;
int level_up; int level_up;
...@@ -1448,12 +1447,6 @@ resolve_name(PyObject *name, PyObject *globals, int level) ...@@ -1448,12 +1447,6 @@ resolve_name(PyObject *name, PyObject *globals, int level)
"attempted relative import with no known parent package"); "attempted relative import with no known parent package");
goto error; goto error;
} }
else if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
for (level_up = 1; level_up < level; level_up += 1) { for (level_up = 1; level_up < level; level_up += 1) {
last_dot = PyUnicode_FindChar(package, '.', 0, last_dot, -1); last_dot = PyUnicode_FindChar(package, '.', 0, last_dot, -1);
......
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