Skip to content

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

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 1 commit into from
Jul 16, 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
4 changes: 0 additions & 4 deletions Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,10 +919,6 @@ def _sanity_check(name, package, level):
elif not package:
raise ImportError('attempted relative import with no known parent '
'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:
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 @@ -24,8 +24,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 @@ -640,11 +641,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(ModuleNotFoundError, 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(ModuleNotFoundError, check_relative)

# Check relative import fails with package set to a non-string
ns = dict(__package__=object())
Expand All @@ -659,6 +660,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 @@ -81,7 +81,7 @@ def test_warn_when_package_and_spec_disagree(self):

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

def test_bunk__package__(self):
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.
7 changes: 0 additions & 7 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,6 @@ resolve_name(PyObject *name, PyObject *globals, int level)
PyObject *abs_name;
PyObject *package = NULL;
PyObject *spec;
PyInterpreterState *interp = PyThreadState_GET()->interp;
Py_ssize_t last_dot;
PyObject *base;
int level_up;
Expand Down Expand Up @@ -1449,12 +1448,6 @@ resolve_name(PyObject *name, PyObject *globals, int level)
"attempted relative import with no known parent package");
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) {
last_dot = PyUnicode_FindChar(package, '.', 0, last_dot, -1);
Expand Down
720 changes: 356 additions & 364 deletions Python/importlib.h

Large diffs are not rendered by default.