Skip to content

Commit 28343e3

Browse files
[3.6] bpo-30876: Relative import from unloaded package now reimports the package (GH-2639) (#2676)
instead of failing with SystemError. Relative import from non-package now fails with ImportError rather than SystemError. (cherry picked from commit 8a9cd20)
1 parent 65de1f3 commit 28343e3

File tree

8 files changed

+381
-379
lines changed

8 files changed

+381
-379
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -919,10 +919,6 @@ def _sanity_check(name, package, level):
919919
elif not package:
920920
raise ImportError('attempted relative import with no known parent '
921921
'package')
922-
elif package not in sys.modules:
923-
msg = ('Parent module {!r} not loaded, cannot perform relative '
924-
'import')
925-
raise SystemError(msg.format(package))
926922
if not name and level == 0:
927923
raise ValueError('Empty module name')
928924

Lib/test/test_import/__init__.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
EnvironmentVarGuard, TESTFN, check_warnings, forget, is_jython,
2525
make_legacy_pyc, rmtree, run_unittest, swap_attr, swap_item, temp_umask,
2626
unlink, unload, create_empty_file, cpython_only, TESTFN_UNENCODABLE,
27-
temp_dir)
27+
temp_dir, DirsOnSysPath)
2828
from test.support import script_helper
29+
from test.test_importlib.util import uncache
2930

3031

3132
skip_if_dont_write_bytecode = unittest.skipIf(
@@ -640,11 +641,11 @@ def check_relative():
640641

641642
# Check relative import fails with only __package__ wrong
642643
ns = dict(__package__='foo', __name__='test.notarealmodule')
643-
self.assertRaises(SystemError, check_relative)
644+
self.assertRaises(ModuleNotFoundError, check_relative)
644645

645646
# Check relative import fails with __package__ and __name__ wrong
646647
ns = dict(__package__='foo', __name__='notarealpkg.notarealmodule')
647-
self.assertRaises(SystemError, check_relative)
648+
self.assertRaises(ModuleNotFoundError, check_relative)
648649

649650
# Check relative import fails with package set to a non-string
650651
ns = dict(__package__=object())
@@ -659,6 +660,20 @@ def test_absolute_import_without_future(self):
659660
self.fail("explicit relative import triggered an "
660661
"implicit absolute import")
661662

663+
def test_import_from_non_package(self):
664+
path = os.path.join(os.path.dirname(__file__), 'data', 'package2')
665+
with uncache('submodule1', 'submodule2'), DirsOnSysPath(path):
666+
with self.assertRaises(ImportError):
667+
import submodule1
668+
self.assertNotIn('submodule1', sys.modules)
669+
self.assertNotIn('submodule2', sys.modules)
670+
671+
def test_import_from_unloaded_package(self):
672+
with uncache('package2', 'package2.submodule1', 'package2.submodule2'), \
673+
DirsOnSysPath(os.path.join(os.path.dirname(__file__), 'data')):
674+
import package2.submodule1
675+
package2.submodule1.submodule2
676+
662677

663678
class OverridingImportBuiltinTests(unittest.TestCase):
664679
def test_override_builtin(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import sys
2+
sys.modules.pop(__package__, None)
3+
from . import submodule2

Lib/test/test_import/data/package2/submodule2.py

Whitespace-only changes.

Lib/test/test_importlib/import_/test___package__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_warn_when_package_and_spec_disagree(self):
8181

8282
def test_bad__package__(self):
8383
globals = {'__package__': '<not real>'}
84-
with self.assertRaises(SystemError):
84+
with self.assertRaises(ModuleNotFoundError):
8585
self.__import__('', globals, {}, ['relimport'], 1)
8686

8787
def test_bunk__package__(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Relative import from unloaded package now reimports the package instead of
2+
failing with SystemError. Relative import from non-package now fails with
3+
ImportError rather than SystemError.

Python/import.c

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,6 @@ resolve_name(PyObject *name, PyObject *globals, int level)
13451345
PyObject *abs_name;
13461346
PyObject *package = NULL;
13471347
PyObject *spec;
1348-
PyInterpreterState *interp = PyThreadState_GET()->interp;
13491348
Py_ssize_t last_dot;
13501349
PyObject *base;
13511350
int level_up;
@@ -1449,12 +1448,6 @@ resolve_name(PyObject *name, PyObject *globals, int level)
14491448
"attempted relative import with no known parent package");
14501449
goto error;
14511450
}
1452-
else if (PyDict_GetItem(interp->modules, package) == NULL) {
1453-
PyErr_Format(PyExc_SystemError,
1454-
"Parent module %R not loaded, cannot perform relative "
1455-
"import", package);
1456-
goto error;
1457-
}
14581451

14591452
for (level_up = 1; level_up < level; level_up += 1) {
14601453
last_dot = PyUnicode_FindChar(package, '.', 0, last_dot, -1);

Python/importlib.h

Lines changed: 356 additions & 364 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)