Commit 9b40dd23 authored by Brett Cannon's avatar Brett Cannon

Do some cleanup in importlib:

+ Ditch using arguments to super().
+ Ditch subclassing from object directly.
+ Move directory check out of chaining path hook to file path hook/finder.
+ Rename some classes to better reflect they are finders, not importers.
parent 5baea9ab
...@@ -174,51 +174,43 @@ class FrozenImporter: ...@@ -174,51 +174,43 @@ class FrozenImporter:
raise raise
class ChainedImporter(object): def chained_path_hook(*path_hooks):
"""Create a closure which sequentially checks path hooks to see which ones
(if any) can work with a path."""
def path_hook(entry):
"""Check to see if 'entry' matches any of the enclosed path hooks."""
finders = []
for hook in path_hooks:
try:
finder = hook(entry)
except ImportError:
continue
else:
finders.append(finder)
if not finders:
raise ImportError("no finder found")
else:
return ChainedFinder(*finders)
return path_hook
class ChainedFinder:
"""Finder that sequentially calls other finders.""" """Finder that sequentially calls other finders."""
def __init__(self, *importers): def __init__(self, *finders):
self._importers = importers self._finders = finders
def find_module(self, fullname, path=None): def find_module(self, fullname, path=None):
for importer in self._importers: for finder in self._finders:
result = importer.find_module(fullname, path) result = finder.find_module(fullname, path)
if result: if result:
return result return result
else: else:
return None return None
# XXX Don't make filesystem-specific and instead make generic for any path
# hooks.
def chaining_fs_path_hook(*path_hooks):
"""Create a closure which calls the path hooks sequentially looking for
which path hooks can handle a path entry.
Passed-in path hooks work as any other path hooks, raising ImportError if
they cannot handle the path, otherwise returning a finder.
"""
def chained_fs_path_hook(path_entry):
"""Closure which sees which of the captured path hooks can handle the
path entry."""
absolute_path = _path_absolute(path_entry)
if not _path_isdir(absolute_path):
raise ImportError("only directories are supported")
accepted = []
for path_hook in path_hooks:
try:
accepted.append(path_hook(absolute_path))
except ImportError:
continue
if not accepted:
raise ImportError("no path hooks could handle %s" % path_entry)
return ChainedImporter(*accepted)
return chained_fs_path_hook
def check_name(method): def check_name(method):
"""Decorator to verify that the module being requested matches the one the """Decorator to verify that the module being requested matches the one the
loader can handle. loader can handle.
...@@ -235,11 +227,11 @@ def check_name(method): ...@@ -235,11 +227,11 @@ def check_name(method):
return inner return inner
class _ExtensionFileLoader(object): class _ExtensionFileLoader:
"""Loader for extension modules. """Loader for extension modules.
The constructor is designed to work with FileImporter. The constructor is designed to work with FileFinder.
""" """
...@@ -323,10 +315,10 @@ def module_for_loader(fxn): ...@@ -323,10 +315,10 @@ def module_for_loader(fxn):
return decorated return decorated
class _PyFileLoader(object): class _PyFileLoader:
# XXX Still smart to have this as a separate class? Or would it work # XXX Still smart to have this as a separate class? Or would it work
# better to integrate with PyFileImporter? Could cache _is_pkg info. # better to integrate with PyFileFinder? Could cache _is_pkg info.
# FileImporter can be changed to return self instead of a specific loader # FileFinder can be changed to return self instead of a specific loader
# call. Otherwise _base_path can be calculated on the fly without issue if # call. Otherwise _base_path can be calculated on the fly without issue if
# it is known whether a module should be treated as a path or package to # it is known whether a module should be treated as a path or package to
# minimize stat calls. Could even go as far as to stat the directory the # minimize stat calls. Could even go as far as to stat the directory the
...@@ -515,9 +507,9 @@ class _PyFileLoader(object): ...@@ -515,9 +507,9 @@ class _PyFileLoader(object):
return self._is_pkg return self._is_pkg
class FileImporter(object): class FileFinder:
"""Base class for file importers. """Base class for file finders.
Subclasses are expected to define the following attributes: Subclasses are expected to define the following attributes:
...@@ -541,10 +533,13 @@ class FileImporter(object): ...@@ -541,10 +533,13 @@ class FileImporter(object):
Can be used as an entry on sys.path_hook. Can be used as an entry on sys.path_hook.
""" """
self._path_entry = path_entry absolute_path = _path_absolute(path_entry)
if not _path_isdir(absolute_path):
raise ImportError("only directories are supported")
self._path_entry = absolute_path
def find_module(self, fullname, path=None): def find_module(self, fullname, path=None):
tail_module = fullname.rsplit('.', 1)[-1] tail_module = fullname.rpartition('.')[2]
package_directory = None package_directory = None
if self._possible_package: if self._possible_package:
for ext in self._suffixes: for ext in self._suffixes:
...@@ -571,7 +566,7 @@ class FileImporter(object): ...@@ -571,7 +566,7 @@ class FileImporter(object):
return None return None
class ExtensionFileImporter(FileImporter): class ExtensionFileFinder(FileFinder):
"""Importer for extension files.""" """Importer for extension files."""
...@@ -582,10 +577,10 @@ class ExtensionFileImporter(FileImporter): ...@@ -582,10 +577,10 @@ class ExtensionFileImporter(FileImporter):
# Assigning to _suffixes here instead of at the class level because # Assigning to _suffixes here instead of at the class level because
# imp is not imported at the time of class creation. # imp is not imported at the time of class creation.
self._suffixes = suffix_list(imp.C_EXTENSION) self._suffixes = suffix_list(imp.C_EXTENSION)
super(ExtensionFileImporter, self).__init__(path_entry) super().__init__(path_entry)
class PyFileImporter(FileImporter): class PyFileFinder(FileFinder):
"""Importer for source/bytecode files.""" """Importer for source/bytecode files."""
...@@ -598,7 +593,7 @@ class PyFileImporter(FileImporter): ...@@ -598,7 +593,7 @@ class PyFileImporter(FileImporter):
# optimization by the loader. # optimization by the loader.
self._suffixes = suffix_list(imp.PY_SOURCE) self._suffixes = suffix_list(imp.PY_SOURCE)
self._suffixes += suffix_list(imp.PY_COMPILED) self._suffixes += suffix_list(imp.PY_COMPILED)
super(PyFileImporter, self).__init__(path_entry) super().__init__(path_entry)
class PathFinder: class PathFinder:
...@@ -664,31 +659,30 @@ class PathFinder: ...@@ -664,31 +659,30 @@ class PathFinder:
return None return None
_DEFAULT_PATH_HOOK = chained_path_hook(ExtensionFileFinder, PyFileFinder)
class _DefaultPathFinder(PathFinder): class _DefaultPathFinder(PathFinder):
"""Subclass of PathFinder that implements implicit semantics for """Subclass of PathFinder that implements implicit semantics for
__import__.""" __import__."""
_default_hook = staticmethod(chaining_fs_path_hook(ExtensionFileImporter,
PyFileImporter))
@classmethod @classmethod
def _path_hooks(cls, path): def _path_hooks(cls, path):
"""Search sys.path_hooks as well as implicit path hooks.""" """Search sys.path_hooks as well as implicit path hooks."""
try: try:
return super()._path_hooks(path) return super()._path_hooks(path)
except ImportError: except ImportError:
implicit_hooks = [cls._default_hook, imp.NullImporter] implicit_hooks = [_DEFAULT_PATH_HOOK, imp.NullImporter]
return super()._path_hooks(path, implicit_hooks) return super()._path_hooks(path, implicit_hooks)
@classmethod @classmethod
def _path_importer_cache(cls, path): def _path_importer_cache(cls, path):
"""Use the default path hook when None is stored in """Use the default path hook when None is stored in
sys.path_importer_cache.""" sys.path_importer_cache."""
return super()._path_importer_cache(path, cls._default_hook) return super()._path_importer_cache(path, _DEFAULT_PATH_HOOK)
class ImportLockContext(object): class ImportLockContext:
"""Context manager for the import lock.""" """Context manager for the import lock."""
......
...@@ -13,7 +13,7 @@ class ExtensionModuleCaseSensitivityTest(unittest.TestCase): ...@@ -13,7 +13,7 @@ class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
good_name = ext_util.NAME good_name = ext_util.NAME
bad_name = good_name.upper() bad_name = good_name.upper()
assert good_name != bad_name assert good_name != bad_name
finder = importlib.ExtensionFileImporter(ext_util.PATH) finder = importlib.ExtensionFileFinder(ext_util.PATH)
return finder.find_module(bad_name) return finder.find_module(bad_name)
def test_case_sensitive(self): def test_case_sensitive(self):
......
...@@ -9,7 +9,7 @@ class FinderTests(abc.FinderTests): ...@@ -9,7 +9,7 @@ class FinderTests(abc.FinderTests):
"""Test the finder for extension modules.""" """Test the finder for extension modules."""
def find_module(self, fullname): def find_module(self, fullname):
importer = importlib.ExtensionFileImporter(util.PATH) importer = importlib.ExtensionFileFinder(util.PATH)
return importer.find_module(fullname) return importer.find_module(fullname)
def test_module(self): def test_module(self):
......
...@@ -14,7 +14,7 @@ class PathHookTests(unittest.TestCase): ...@@ -14,7 +14,7 @@ class PathHookTests(unittest.TestCase):
# XXX Should it only work for directories containing an extension module? # XXX Should it only work for directories containing an extension module?
def hook(self, entry): def hook(self, entry):
return importlib.ExtensionFileImporter(entry) return importlib.ExtensionFileFinder(entry)
def test_success(self): def test_success(self):
# Path hook should handle a directory where a known extension module # Path hook should handle a directory where a known extension module
......
...@@ -87,16 +87,16 @@ class DefaultPathFinderTests(unittest.TestCase): ...@@ -87,16 +87,16 @@ class DefaultPathFinderTests(unittest.TestCase):
importer = util.mock_modules(module) importer = util.mock_modules(module)
path = '<test path>' path = '<test path>'
# XXX Not blackbox. # XXX Not blackbox.
original_hook = _bootstrap._DefaultPathFinder._default_hook original_hook = _bootstrap._DEFAULT_PATH_HOOK
mock_hook = import_util.mock_path_hook(path, importer=importer) mock_hook = import_util.mock_path_hook(path, importer=importer)
_bootstrap._DefaultPathFinder._default_hook = staticmethod(mock_hook) _bootstrap._DEFAULT_PATH_HOOK = mock_hook
try: try:
with util.import_state(path_importer_cache={path: None}): with util.import_state(path_importer_cache={path: None}):
loader = _bootstrap._DefaultPathFinder.find_module(module, loader = _bootstrap._DefaultPathFinder.find_module(module,
path=[path]) path=[path])
self.assert_(loader is importer) self.assert_(loader is importer)
finally: finally:
_bootstrap._DefaultPathFinder._default_hook = original_hook _bootstrap._DEFAULT_PATH_HOOK = original_hook
def test_main(): def test_main():
......
...@@ -19,7 +19,7 @@ class CaseSensitivityTest(unittest.TestCase): ...@@ -19,7 +19,7 @@ class CaseSensitivityTest(unittest.TestCase):
assert name != name.lower() assert name != name.lower()
def find(self, path): def find(self, path):
finder = importlib.PyFileImporter(path) finder = importlib.PyFileFinder(path)
return finder.find_module(self.name) return finder.find_module(self.name)
def sensitivity_test(self): def sensitivity_test(self):
......
...@@ -32,7 +32,7 @@ class FinderTests(abc.FinderTests): ...@@ -32,7 +32,7 @@ class FinderTests(abc.FinderTests):
""" """
def import_(self, root, module): def import_(self, root, module):
finder = importlib.PyFileImporter(root) finder = importlib.PyFileFinder(root)
return finder.find_module(module) return finder.find_module(module)
def run_test(self, test, create=None, *, compile_=None, unlink=None): def run_test(self, test, create=None, *, compile_=None, unlink=None):
......
...@@ -10,7 +10,7 @@ class PathHookTest(unittest.TestCase): ...@@ -10,7 +10,7 @@ class PathHookTest(unittest.TestCase):
def test_success(self): def test_success(self):
# XXX Only work on existing directories? # XXX Only work on existing directories?
with source_util.create_modules('dummy') as mapping: with source_util.create_modules('dummy') as mapping:
self.assert_(hasattr(importlib.FileImporter(mapping['.root']), self.assert_(hasattr(importlib.FileFinder(mapping['.root']),
'find_module')) 'find_module'))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment