]> granicus.if.org Git - python/commitdiff
Issue #19719: Update various finder and loader ABCs such that their
authorBrett Cannon <brett@python.org>
Tue, 7 Jan 2014 20:52:42 +0000 (15:52 -0500)
committerBrett Cannon <brett@python.org>
Tue, 7 Jan 2014 20:52:42 +0000 (15:52 -0500)
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
Lib/importlib/abc.py
Lib/test/test_importlib/test_abc.py
Misc/NEWS

index 82d6d6d7482d057f658437d19a439902695d6d16..dc29b8aa635385f4fdd4b9f8074f4acb100ff733 100644 (file)
@@ -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`
index 584a41bb6db63a77c4f280b7beb96df1059e769e..558abd3d9a409e2c187f84994b4f55d8b65447a4 100644 (file)
@@ -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.
index 7c8e8fe55024746126b3ecf8de648f161099d3c2..09b72949ca5f8ec4fbc32d073a0b317c94f17bb6 100644 (file)
@@ -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:
index 4602993e0a795c04ec989027d808d90a356e29f4..d774926c084fce0b90b9b298243216ad20a50c47 100644 (file)
--- 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__.