From 8d942296bb6cc45e519447484084bee1507d664b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 7 Jan 2014 15:52:42 -0500 Subject: Issue #19719: Update various finder and loader ABCs such that their old methods now provide implementations when PEP 451 APIs are present. This should help with backwards-compatibility with code which has not been updated to work with PEP 451. --- Doc/library/importlib.rst | 16 ++++- Lib/importlib/abc.py | 32 +++++++-- Lib/test/test_importlib/test_abc.py | 132 ++++++++++++++++++++++++++++++++++++ Misc/NEWS | 4 ++ 4 files changed, 176 insertions(+), 8 deletions(-) diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 82d6d6d..dc29b8a 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -275,9 +275,13 @@ ABC hierarchy:: will be the value of :attr:`__path__` from the parent package. If a loader cannot be found, ``None`` is returned. + If :meth:`find_spec` is defined, backwards-compatible functionality is + provided. + .. versionchanged:: 3.4 Returns ``None`` when called instead of raising - :exc:`NotImplementedError`. + :exc:`NotImplementedError`. Can use :meth:`find_spec` to provide + functionality. .. deprecated:: 3.4 Use :meth:`find_spec` instead. @@ -325,8 +329,12 @@ ABC hierarchy:: ``portion`` is the empty list then no loader or location for a namespace package were found (i.e. failure to find anything for the module). + If :meth:`find_spec` is defined then backwards-compatible functionality is + provided. + .. versionchanged:: 3.4 Returns ``(None, [])`` instead of raising :exc:`NotImplementedError`. + Uses :meth:`find_spec` when available to provide functionality. .. deprecated:: 3.4 Use :meth:`find_spec` instead. @@ -413,9 +421,13 @@ ABC hierarchy:: :func:`importlib.util.module_for_loader` decorator can handle the details for :attr:`__package__`. + When :meth:`exec_module` is available then backwards-compatible + functionality is provided. + .. versionchanged:: 3.4 Raise :exc:`ImportError` when called instead of - :exc:`NotImplementedError`. + :exc:`NotImplementedError`. Functionality provided when + :meth:`exec_module` is available. .. deprecated:: 3.4 The recommended API for loading a module is :meth:`exec_module` diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py index 584a41b..558abd3 100644 --- a/Lib/importlib/abc.py +++ b/Lib/importlib/abc.py @@ -49,10 +49,15 @@ class MetaPathFinder(Finder): If no module is found, return None. The fullname is a str and the path is a list of strings or None. - This method is deprecated in favor of finder.find_spec(). + This method is deprecated in favor of finder.find_spec(). If find_spec() + exists then backwards-compatible functionality is provided for this + method. """ - return None + if not hasattr(self, 'find_spec'): + return None + found = self.find_spec(fullname, path) + return found.loader if found is not None else None def invalidate_caches(self): """An optional method for clearing the finder's cache, if any. @@ -81,10 +86,21 @@ class PathEntryFinder(Finder): The portion will be discarded if another path entry finder locates the module as a normal module or package. - This method is deprecated in favor of finder.find_spec(). + This method is deprecated in favor of finder.find_spec(). If find_spec() + is provided than backwards-compatible functionality is provided. """ - return None, [] + if not hasattr(self, 'find_spec'): + return None, [] + found = self.find_spec(fullname) + if found is not None: + if not found.submodule_search_locations: + portions = [] + else: + portions = found.submodule_search_locations + return found.loader, portions + else: + return None, [] find_module = _bootstrap._find_module_shim @@ -124,10 +140,14 @@ class Loader(metaclass=abc.ABCMeta): ImportError is raised on failure. - This method is deprecated in favor of loader.exec_module(). + This method is deprecated in favor of loader.exec_module(). If + exec_module() exists then it is used to provide a backwards-compatible + functionality for this method. """ - raise ImportError + if not hasattr(self, 'exec_module'): + raise ImportError + return _bootstrap._load_module_shim(self, fullname) def module_repr(self, module): """Return a module's repr. diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py index 7c8e8fe..09b7294 100644 --- a/Lib/test/test_importlib/test_abc.py +++ b/Lib/test/test_importlib/test_abc.py @@ -14,6 +14,7 @@ from . import util frozen_init, source_init = util.import_importlib('importlib') frozen_abc, source_abc = util.import_importlib('importlib.abc') +machinery = util.import_importlib('importlib.machinery') frozen_util, source_util = util.import_importlib('importlib.util') ##### Inheritance ############################################################## @@ -285,6 +286,137 @@ class ExecutionLoaderDefaultsTests: tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests) Frozen_ELDefaultTests, Source_ELDefaultsTests = tests +##### MetaPathFinder concrete methods ########################################## + +class MetaPathFinderFindModuleTests: + + @classmethod + def finder(cls, spec): + class MetaPathSpecFinder(cls.abc.MetaPathFinder): + + def find_spec(self, fullname, path, target=None): + self.called_for = fullname, path + return spec + + return MetaPathSpecFinder() + + def test_no_spec(self): + finder = self.finder(None) + path = ['a', 'b', 'c'] + name = 'blah' + found = finder.find_module(name, path) + self.assertIsNone(found) + self.assertEqual(name, finder.called_for[0]) + self.assertEqual(path, finder.called_for[1]) + + def test_spec(self): + loader = object() + spec = self.util.spec_from_loader('blah', loader) + finder = self.finder(spec) + found = finder.find_module('blah', None) + self.assertIs(found, spec.loader) + + +Frozen_MPFFindModuleTests, Source_MPFFindModuleTests = util.test_both( + MetaPathFinderFindModuleTests, + abc=(frozen_abc, source_abc), + util=(frozen_util, source_util)) + +##### PathEntryFinder concrete methods ######################################### + +class PathEntryFinderFindLoaderTests: + + @classmethod + def finder(cls, spec): + class PathEntrySpecFinder(cls.abc.PathEntryFinder): + + def find_spec(self, fullname, target=None): + self.called_for = fullname + return spec + + return PathEntrySpecFinder() + + def test_no_spec(self): + finder = self.finder(None) + name = 'blah' + found = finder.find_loader(name) + self.assertIsNone(found[0]) + self.assertEqual([], found[1]) + self.assertEqual(name, finder.called_for) + + def test_spec_with_loader(self): + loader = object() + spec = self.util.spec_from_loader('blah', loader) + finder = self.finder(spec) + found = finder.find_loader('blah') + self.assertIs(found[0], spec.loader) + + def test_spec_with_portions(self): + spec = self.machinery.ModuleSpec('blah', None) + paths = ['a', 'b', 'c'] + spec.submodule_search_locations = paths + finder = self.finder(spec) + found = finder.find_loader('blah') + self.assertIsNone(found[0]) + self.assertEqual(paths, found[1]) + + +Frozen_PEFFindLoaderTests, Source_PEFFindLoaderTests = util.test_both( + PathEntryFinderFindLoaderTests, + abc=(frozen_abc, source_abc), + machinery=machinery, + util=(frozen_util, source_util)) + + +##### Loader concrete methods ################################################## +class LoaderLoadModuleTests: + + def loader(self): + class SpecLoader(self.abc.Loader): + found = None + def exec_module(self, module): + self.found = module + + def is_package(self, fullname): + """Force some non-default module state to be set.""" + return True + + return SpecLoader() + + def test_fresh(self): + loader = self.loader() + name = 'blah' + with util.uncache(name): + loader.load_module(name) + module = loader.found + self.assertIs(sys.modules[name], module) + self.assertEqual(loader, module.__loader__) + self.assertEqual(loader, module.__spec__.loader) + self.assertEqual(name, module.__name__) + self.assertEqual(name, module.__spec__.name) + self.assertIsNotNone(module.__path__) + self.assertIsNotNone(module.__path__, + module.__spec__.submodule_search_locations) + + def test_reload(self): + name = 'blah' + loader = self.loader() + module = types.ModuleType(name) + module.__spec__ = self.util.spec_from_loader(name, loader) + module.__loader__ = loader + with util.uncache(name): + sys.modules[name] = module + loader.load_module(name) + found = loader.found + self.assertIs(found, sys.modules[name]) + self.assertIs(module, sys.modules[name]) + + +Frozen_LoaderLoadModuleTests, Source_LoaderLoadModuleTests = util.test_both( + LoaderLoadModuleTests, + abc=(frozen_abc, source_abc), + util=(frozen_util, source_util)) + ##### InspectLoader concrete methods ########################################### class InspectLoaderSourceToCodeTests: diff --git a/Misc/NEWS b/Misc/NEWS index 4602993..d774926 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,10 @@ Core and Builtins Library ------- +- Issue #19719: Make importlib.abc.MetaPathFinder.find_module(), + PathEntryFinder.find_loader(), and Loader.load_module() use PEP 451 APIs to + help with backwards-compatibility. + - Issue #20144: inspect.Signature now supports parsing simple symbolic constants as parameter default values in __text_signature__. -- cgit v0.12