Skip to content

[3.5] bpo-30876: Relative import from unloaded package now reimports the package (GH-2639) #2677

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
merged 2 commits into from
Jul 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,10 +925,9 @@ def _sanity_check(name, package, level):
if level > 0:
if not isinstance(package, str):
raise TypeError('__package__ not set to a string')
elif package not in sys.modules:
msg = ('Parent module {!r} not loaded, cannot perform relative '
'import')
raise SystemError(msg.format(package))
elif not package:
raise ImportError('attempted relative import with no known parent '
'package')
if not name and level == 0:
raise ValueError('Empty module name')

Expand Down
21 changes: 18 additions & 3 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
EnvironmentVarGuard, TESTFN, check_warnings, forget, is_jython,
make_legacy_pyc, rmtree, run_unittest, swap_attr, swap_item, temp_umask,
unlink, unload, create_empty_file, cpython_only, TESTFN_UNENCODABLE,
temp_dir)
temp_dir, DirsOnSysPath)
from test.support import script_helper
from test.test_importlib.util import uncache


skip_if_dont_write_bytecode = unittest.skipIf(
Expand Down Expand Up @@ -600,11 +601,11 @@ def check_relative():

# Check relative import fails with only __package__ wrong
ns = dict(__package__='foo', __name__='test.notarealmodule')
self.assertRaises(SystemError, check_relative)
self.assertRaises(ImportError, check_relative)

# Check relative import fails with __package__ and __name__ wrong
ns = dict(__package__='foo', __name__='notarealpkg.notarealmodule')
self.assertRaises(SystemError, check_relative)
self.assertRaises(ImportError, check_relative)

# Check relative import fails with package set to a non-string
ns = dict(__package__=object())
Expand All @@ -619,6 +620,20 @@ def test_absolute_import_without_future(self):
self.fail("explicit relative import triggered an "
"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):
def test_override_builtin(self):
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_import/data/package2/submodule1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import sys
sys.modules.pop(__package__, None)
from . import submodule2
Empty file.
2 changes: 1 addition & 1 deletion Lib/test/test_importlib/import_/test___package__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def test_None_as___package__(self):

def test_bad__package__(self):
globals = {'__package__': '<not real>'}
with self.assertRaises(SystemError):
with self.assertRaises(ImportError):
self.__import__('', globals, {}, ['relimport'], 1)

def test_bunk__package__(self):
Expand Down
7 changes: 6 additions & 1 deletion Lib/test/test_importlib/import_/test_relative_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,13 @@ def test_relative_import_no_globals(self):
with self.assertRaises(KeyError):
self.__import__('sys', level=1)

def test_relative_import_no_package(self):
with self.assertRaises(ImportError):
self.__import__('a', {'__package__': '', '__spec__': None},
level=1)

def test_relative_import_no_package_exists_absolute(self):
with self.assertRaises(SystemError):
with self.assertRaises(ImportError):
self.__import__('sys', {'__package__': '', '__spec__': None},
level=1)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
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.
8 changes: 3 additions & 5 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -1461,11 +1461,9 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
Py_DECREF(partition);
}
}

if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
if (PyUnicode_GET_LENGTH(package) == 0) {
PyErr_SetString(PyExc_ImportError,
"attempted relative import with no known parent package");
goto error;
}
}
Expand Down
Loading