summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett Cannon <brett@python.org>2014-01-07 15:52:42 -0500
committerBrett Cannon <brett@python.org>2014-01-07 15:52:42 -0500
commit5ac6bbb3586c49842e81bce3970674a4af934b2a (patch)
tree85def77d246bad07000407154f2d94a10c0415e5
parent9c6f8614acd6301e0ea40699623bb5df2c72976f (diff)
downloadcpython-5ac6bbb3586c49842e81bce3970674a4af934b2a.tar.gz
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.
-rw-r--r--Doc/library/importlib.rst16
-rw-r--r--Lib/importlib/abc.py32
-rw-r--r--Lib/test/test_importlib/test_abc.py132
-rw-r--r--Misc/NEWS4
4 files changed, 176 insertions, 8 deletions
diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
index 82d6d6d748..dc29b8aa63 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 584a41bb6d..558abd3d9a 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 7c8e8fe550..09b72949ca 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 4602993e0a..d774926c08 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__.