Skip to content

Commit c824cc8

Browse files
[3.5] Backport bpo-30876 (GH-2639), bpo-18018 and bpo-26367. (#2677)
* bpo-30876: 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. (cherry picked from commit 8a9cd20) * bpo-18018: Import raises ImportError instead of SystemError if a relative import is attempted without a known parent package. * bpo-26367: importlib.__init__() raises ImportError like builtins.__import__() when ``level`` is specified but without an accompanying package specified.
1 parent e78dc0a commit c824cc8

File tree

9 files changed

+402
-382
lines changed

9 files changed

+402
-382
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -925,10 +925,9 @@ def _sanity_check(name, package, level):
925925
if level > 0:
926926
if not isinstance(package, str):
927927
raise TypeError('__package__ not set to a string')
928-
elif package not in sys.modules:
929-
msg = ('Parent module {!r} not loaded, cannot perform relative '
930-
'import')
931-
raise SystemError(msg.format(package))
928+
elif not package:
929+
raise ImportError('attempted relative import with no known parent '
930+
'package')
932931
if not name and level == 0:
933932
raise ValueError('Empty module name')
934933

Lib/test/test_import/__init__.py

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

2829

2930
skip_if_dont_write_bytecode = unittest.skipIf(
@@ -600,11 +601,11 @@ def check_relative():
600601

601602
# Check relative import fails with only __package__ wrong
602603
ns = dict(__package__='foo', __name__='test.notarealmodule')
603-
self.assertRaises(SystemError, check_relative)
604+
self.assertRaises(ImportError, check_relative)
604605

605606
# Check relative import fails with __package__ and __name__ wrong
606607
ns = dict(__package__='foo', __name__='notarealpkg.notarealmodule')
607-
self.assertRaises(SystemError, check_relative)
608+
self.assertRaises(ImportError, check_relative)
608609

609610
# Check relative import fails with package set to a non-string
610611
ns = dict(__package__=object())
@@ -619,6 +620,20 @@ def test_absolute_import_without_future(self):
619620
self.fail("explicit relative import triggered an "
620621
"implicit absolute import")
621622

623+
def test_import_from_non_package(self):
624+
path = os.path.join(os.path.dirname(__file__), 'data', 'package2')
625+
with uncache('submodule1', 'submodule2'), DirsOnSysPath(path):
626+
with self.assertRaises(ImportError):
627+
import submodule1
628+
self.assertNotIn('submodule1', sys.modules)
629+
self.assertNotIn('submodule2', sys.modules)
630+
631+
def test_import_from_unloaded_package(self):
632+
with uncache('package2', 'package2.submodule1', 'package2.submodule2'), \
633+
DirsOnSysPath(os.path.join(os.path.dirname(__file__), 'data')):
634+
import package2.submodule1
635+
package2.submodule1.submodule2
636+
622637

623638
class OverridingImportBuiltinTests(unittest.TestCase):
624639
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
@@ -61,7 +61,7 @@ def test_None_as___package__(self):
6161

6262
def test_bad__package__(self):
6363
globals = {'__package__': '<not real>'}
64-
with self.assertRaises(SystemError):
64+
with self.assertRaises(ImportError):
6565
self.__import__('', globals, {}, ['relimport'], 1)
6666

6767
def test_bunk__package__(self):

Lib/test/test_importlib/import_/test_relative_imports.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,13 @@ def test_relative_import_no_globals(self):
207207
with self.assertRaises(KeyError):
208208
self.__import__('sys', level=1)
209209

210+
def test_relative_import_no_package(self):
211+
with self.assertRaises(ImportError):
212+
self.__import__('a', {'__package__': '', '__spec__': None},
213+
level=1)
214+
210215
def test_relative_import_no_package_exists_absolute(self):
211-
with self.assertRaises(SystemError):
216+
with self.assertRaises(ImportError):
212217
self.__import__('sys', {'__package__': '', '__spec__': None},
213218
level=1)
214219

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: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,11 +1461,9 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
14611461
Py_DECREF(partition);
14621462
}
14631463
}
1464-
1465-
if (PyDict_GetItem(interp->modules, package) == NULL) {
1466-
PyErr_Format(PyExc_SystemError,
1467-
"Parent module %R not loaded, cannot perform relative "
1468-
"import", package);
1464+
if (PyUnicode_GET_LENGTH(package) == 0) {
1465+
PyErr_SetString(PyExc_ImportError,
1466+
"attempted relative import with no known parent package");
14691467
goto error;
14701468
}
14711469
}

0 commit comments

Comments
 (0)