cpython: Issue #19413: Restore pre-3.3 reload() semantics of re-finding modules.
http://hg.python.org/cpython/rev/88c3a1a3c2ff changeset: 86819:88c3a1a3c2ff parent: 86816:13a05ed33cf7 user: Eric Snow <ericsnowcurrently@gmail.com> date: Thu Oct 31 22:22:15 2013 -0600 summary: Issue #19413: Restore pre-3.3 reload() semantics of re-finding modules. files: Lib/importlib/__init__.py | 8 +- Lib/importlib/_bootstrap.py | 8 +- Lib/test/test_importlib/test_api.py | 120 + Misc/NEWS | 2 + Python/importlib.h | 1011 +++++++------- 5 files changed, 642 insertions(+), 507 deletions(-) diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -107,7 +107,7 @@ if not module or not isinstance(module, types.ModuleType): raise TypeError("reload() argument must be module") name = module.__name__ - if name not in sys.modules: + if sys.modules.get(name) is not module: msg = "module {} not in sys.modules" raise ImportError(msg.format(name), name=name) if name in _RELOADING: @@ -118,7 +118,11 @@ if parent_name and parent_name not in sys.modules: msg = "parent {!r} not in sys.modules" raise ImportError(msg.format(parent_name), name=parent_name) - module.__loader__.load_module(name) + loader = _bootstrap._find_module(name, None) + if loader is None: + raise ImportError(_bootstrap._ERR_MSG.format(name), name=name) + module.__loader__ = loader + loader.load_module(name) # The module may have replaced itself in sys.modules! return sys.modules[module.__name__] finally: diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -1510,15 +1510,19 @@ """Find a module's loader.""" if not sys.meta_path: _warnings.warn('sys.meta_path is empty', ImportWarning) + is_reload = name in sys.modules for finder in sys.meta_path: with _ImportLockContext(): loader = finder.find_module(name, path) if loader is not None: # The parent import may have already imported this module. - if name not in sys.modules: + if is_reload or name not in sys.modules: return loader else: - return sys.modules[name].__loader__ + try: + return sys.modules[name].__loader__ + except AttributeError: + return loader else: return None diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py --- a/Lib/test/test_importlib/test_api.py +++ b/Lib/test/test_importlib/test_api.py @@ -1,8 +1,10 @@ from . import util frozen_init, source_init = util.import_importlib('importlib') +frozen_util, source_util = util.import_importlib('importlib.util') frozen_machinery, source_machinery = util.import_importlib('importlib.machinery') +import os.path import sys from test import support import types @@ -190,11 +192,129 @@ self.assertEqual(actual.spam, 3) self.assertEqual(reloaded.spam, 3) + def test_reload_missing_loader(self): + with support.CleanImport('types'): + import types + loader = types.__loader__ + del types.__loader__ + reloaded = self.init.reload(types) + + self.assertIs(reloaded, types) + self.assertIs(sys.modules['types'], types) + self.assertEqual(reloaded.__loader__.path, loader.path) + + def test_reload_loader_replaced(self): + with support.CleanImport('types'): + import types + types.__loader__ = None + self.init.invalidate_caches() + reloaded = self.init.reload(types) + + self.assertIsNot(reloaded.__loader__, None) + self.assertIs(reloaded, types) + self.assertIs(sys.modules['types'], types) + + def test_reload_location_changed(self): + name = 'spam' + with support.temp_cwd(None) as cwd: + with util.uncache('spam'): + with support.DirsOnSysPath(cwd): + self.init.invalidate_caches() + path = os.path.join(cwd, name + '.py') + cached = self.util.cache_from_source(path) + expected = {'__name__': name, + '__package__': '', + '__file__': path, + '__cached__': cached, + '__doc__': None, + '__builtins__': __builtins__, + } + support.create_empty_file(path) + module = self.init.import_module(name) + ns = vars(module) + del ns['__initializing__'] + loader = ns.pop('__loader__') + self.assertEqual(loader.path, path) + self.assertEqual(ns, expected) + + self.init.invalidate_caches() + init_path = os.path.join(cwd, name, '__init__.py') + cached = self.util.cache_from_source(init_path) + expected = {'__name__': name, + '__package__': name, + '__file__': init_path, + '__cached__': cached, + '__path__': [os.path.dirname(init_path)], + '__doc__': None, + '__builtins__': __builtins__, + } + os.mkdir(name) + os.rename(path, init_path) + reloaded = self.init.reload(module) + ns = vars(reloaded) + del ns['__initializing__'] + loader = ns.pop('__loader__') + self.assertIs(reloaded, module) + self.assertEqual(loader.path, init_path) + self.assertEqual(ns, expected) + + def test_reload_namespace_changed(self): + self.maxDiff = None + name = 'spam' + with support.temp_cwd(None) as cwd: + with util.uncache('spam'): + with support.DirsOnSysPath(cwd): + self.init.invalidate_caches() + bad_path = os.path.join(cwd, name, '__init.py') + cached = self.util.cache_from_source(bad_path) + expected = {'__name__': name, + '__package__': name, + '__doc__': None, + } + os.mkdir(name) + with open(bad_path, 'w') as init_file: + init_file.write('eggs = None') + module = self.init.import_module(name) + ns = vars(module) + del ns['__initializing__'] + loader = ns.pop('__loader__') + path = ns.pop('__path__') + self.assertEqual(list(path), + [os.path.dirname(bad_path)] * 2) + with self.assertRaises(AttributeError): + # a NamespaceLoader + loader.path + self.assertEqual(ns, expected) + + self.init.invalidate_caches() + init_path = os.path.join(cwd, name, '__init__.py') + cached = self.util.cache_from_source(init_path) + expected = {'__name__': name, + '__package__': name, + '__file__': init_path, + '__cached__': cached, + '__path__': [os.path.dirname(init_path)], + '__doc__': None, + '__builtins__': __builtins__, + 'eggs': None, + } + os.rename(bad_path, init_path) + reloaded = self.init.reload(module) + ns = vars(reloaded) + del ns['__initializing__'] + loader = ns.pop('__loader__') + self.assertIs(reloaded, module) + self.assertEqual(loader.path, init_path) + self.assertEqual(ns, expected) + + class Frozen_ReloadTests(ReloadTests, unittest.TestCase): init = frozen_init + util = frozen_util class Source_ReloadTests(ReloadTests, unittest.TestCase): init = source_init + util = source_util class InvalidateCacheTests: diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -127,6 +127,8 @@ - Issue #8964: fix platform._sys_version to handle IronPython 2.6+. Patch by Martin Matusiak. +- Issue #19413: Restore pre-3.3 reload() semantics of re-finding modules. + - Issue #18958: Improve error message for json.load(s) while passing a string that starts with a UTF-8 BOM. diff --git a/Python/importlib.h b/Python/importlib.h --- a/Python/importlib.h +++ b/Python/importlib.h [stripped] -- Repository URL: http://hg.python.org/cpython
participants (1)
-
eric.snow