Issue #14605: Expose importlib.abc.FileLoader and
importlib.machinery.(FileFinder, SourceFileLoader,
_SourcelessFileLoader, ExtensionFileLoader).
This exposes all of importlib's mechanisms that will become public on
the sys module.
diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
index e5cc27f..de29e4f 100644
--- a/Doc/library/importlib.rst
+++ b/Doc/library/importlib.rst
@@ -237,6 +237,34 @@
module.
+.. class:: FileLoader(fullname, path)
+
+ An abstract base class which inherits from :class:`ResourceLoader` and
+ :class:`ExecutionLoader`, providing concreate implementations of
+ :meth:`ResourceLoader.get_data` and :meth:`ExecutionLoader.get_filename`.
+
+ The *fullname* argument is a fully resolved name of the module the loader is
+ to handle. The *path* argument is the path to the file for the module.
+
+ .. versionadded:: 3.3
+
+ .. attribute:: name
+
+ The name of the module the loader can handle.
+
+ .. attribute:: path
+
+ Path to the file of the module.
+
+ .. method:: get_filename(fullname)
+
+ Returns :attr:`path`.
+
+ .. method:: get_data(path)
+
+ Returns the open, binary file for *path*.
+
+
.. class:: SourceLoader
An abstract base class for implementing source (and optionally bytecode)
@@ -498,6 +526,163 @@
module. If no finder is ever found then ``None`` is returned.
+.. class:: FileFinder(path, \*loader_details)
+
+ A concrete implementation of :class:`importlib.abc.Finder` which caches
+ results from the file system.
+
+ The *path* argument is the directory for which the finder is in charge of
+ searching.
+
+ The *loader_details* argument is a variable number of 3-item tuples each
+ containing a loader, file suffixes the loader recognizes, and a boolean
+ representing whether the loader handles packages.
+
+ The finder will cache the directory contents as necessary, making stat calls
+ for each module search to verify the cache is not outdated. Because cache
+ staleness relies upon the granularity of the operating system's state
+ information of the file system, there is a potential race condition of
+ searching for a module, creating a new file, and then searching for the
+ module the new file represents. If the operations happen fast enough to fit
+ within the granularity of stat calls, then the module search will fail. To
+ prevent this from happening, when you create a module dynamically, make sure
+ to call :func:`importlib.invalidate_caches`.
+
+ .. versionadded:: 3.3
+
+ .. attribute:: path
+
+ The path the finder will search in.
+
+ .. method:: find_module(fullname)
+
+ Attempt to find the loader to handle *fullname* within :attr:`path`.
+
+ .. method:: invalidate_caches()
+
+ Clear out the internal cache.
+
+ .. classmethod:: path_hook(\*loader_details)
+
+ A class method which returns a closure for use on :attr:`sys.path_hooks`.
+ An instance of :class:`FileFinder` is returned by the closure using the
+ path argument given to the closure directly and *loader_details*
+ indirectly.
+
+ If the argument to the closure is not an existing directory,
+ :exc:`ImportError` is raised.
+
+
+.. class:: SourceFileLoader(fullname, path)
+
+ A concrete implementation of :class:`importlib.abc.SourceLoader` by
+ subclassing :class:`importlib.abc.FileLoader` and providing some concrete
+ implementations of other methods.
+
+ .. versionadded:: 3.3
+
+ .. attribute:: name
+
+ The name of the module that this loader will handle.
+
+ .. attribute:: path
+
+ The path to the source file.
+
+ .. method:: is_package(fullname)
+
+ Return true if :attr:`path` appears to be for a package.
+
+ .. method:: path_stats(path)
+
+ Concrete implementation of :meth:`importlib.abc.SourceLoader.path_stats`.
+
+ .. method:: set_data(path, data)
+
+ Concrete implementation of :meth:`importlib.abc.SourceLoader.set_data`.
+
+ .. method:: load_module(fullname)
+
+ Load the specified module if it is the same as :attr:`name`.
+
+
+.. class:: _SourcelessFileLoader(fullname, path)
+
+ A concrete implementation of :class:`importlib.abc.FileLoader` which can
+ import bytecode files (i.e. no source code files exist).
+
+ It is **strongly** suggested you do not rely on this loader (hence the
+ leading underscore of the class). Direct use of bytecode files (and thus not
+ source code files) inhibits your modules from being usable by all Python
+ implementations. It also runs the risk of your bytecode files not being
+ usable by new versions of Python which change the bytecode format. This
+ class is only documented as it is directly used by import and thus can
+ potentially have instances show up as a module's ``__loader__`` attribute.
+
+ .. versionadded:: 3.3
+
+ .. attribute:: name
+
+ The name of the module the loader will handle.
+
+ .. attribute:: path
+
+ The path to the bytecode file.
+
+ .. method:: is_package(fullname)
+
+ Determines if the module is a package based on :attr:`path`.
+
+ .. method:: get_code(fullname)
+
+ Returns the code object for :attr:`name` created from :attr:`path`.
+
+ .. method:: get_source(fullname)
+
+ Returns ``None`` as bytecode files have no source when this loader is
+ used.
+
+ .. method:: load_module(fullname)
+
+ Loads the specified module if it is the same as :attr:`name`.
+
+
+.. class:: ExtensionFileLoader(fullname, path)
+
+ A concrete implementation of :class:`importlib.abc.InspectLoader` for
+ extension modules.
+
+ The *fullname* argument specifies the name of the module the loader is to
+ support. The *path* argument is the path to the extension module's file.
+
+ .. versionadded:: 3.3
+
+ .. attribute:: name
+
+ Name of the module the loader supports.
+
+ .. attribute:: path
+
+ Path to the extension module.
+
+ .. method:: load_module(fullname)
+
+ Loads the extension module if and only if *fullname** is the same as
+ :attr:`name`.
+
+ .. method:: is_package(fullname)
+
+ Returns ``False`` as extension modules can never be packages.
+
+ .. method:: get_code(fullname)
+
+ Returns ``None`` as extension modules lack a code object.
+
+ .. method:: get_source(fullname)
+
+ Returns ``None`` as extension modules do not have source code.
+
+
:mod:`importlib.util` -- Utility code for importers
---------------------------------------------------
diff --git a/Lib/imp.py b/Lib/imp.py
index 3ab69e5..f35247c 100644
--- a/Lib/imp.py
+++ b/Lib/imp.py
@@ -71,7 +71,7 @@
def get_data(self, path):
"""Gross hack to contort loader to deal w/ load_*()'s bad API."""
- if self.file and path == self._path:
+ if self.file and path == self.path:
with self.file:
# Technically should be returning bytes, but
# SourceLoader.get_code() just passed what is returned to
@@ -83,7 +83,7 @@
return super().get_data(path)
-class _LoadSourceCompatibility(_HackedGetData, _bootstrap._SourceFileLoader):
+class _LoadSourceCompatibility(_HackedGetData, _bootstrap.SourceFileLoader):
"""Compatibility support for implementing load_source()."""
@@ -115,7 +115,7 @@
break
else:
raise ValueError('{!r} is not a package'.format(path))
- return _bootstrap._SourceFileLoader(name, path).load_module(name)
+ return _bootstrap.SourceFileLoader(name, path).load_module(name)
# XXX deprecate
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index 04ceb56..d9df2b7 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -13,6 +13,9 @@
# reference any injected objects! This includes not only global code but also
# anything specified at the class level.
+# XXX Make sure all public names have no single leading underscore and all
+# others do.
+
# Bootstrap-related code ######################################################
@@ -283,7 +286,7 @@
"""
def _check_name_wrapper(self, name, *args, **kwargs):
- if self._name != name:
+ if self.name != name:
raise ImportError("loader cannot handle %s" % name, name=name)
return method(self, name, *args, **kwargs)
_wrap(_check_name_wrapper, method)
@@ -423,7 +426,7 @@
class _LoaderBasics:
"""Base class of common code needed by both SourceLoader and
- _SourcelessFileLoader."""
+ SourcelessFileLoader."""
def is_package(self, fullname):
"""Concrete implementation of InspectLoader.is_package by checking if
@@ -608,7 +611,7 @@
return self._load_module(fullname)
-class _FileLoader:
+class FileLoader:
"""Base file loader class which implements the loader protocol methods that
require file system usage."""
@@ -616,13 +619,13 @@
def __init__(self, fullname, path):
"""Cache the module name and the path to the file found by the
finder."""
- self._name = fullname
- self._path = path
+ self.name = fullname
+ self.path = path
@_check_name
def get_filename(self, fullname):
"""Return the path to the source file as found by the finder."""
- return self._path
+ return self.path
def get_data(self, path):
"""Return the data from path as raw bytes."""
@@ -630,7 +633,7 @@
return file.read()
-class _SourceFileLoader(_FileLoader, SourceLoader):
+class SourceFileLoader(FileLoader, SourceLoader):
"""Concrete implementation of SourceLoader using the file system."""
@@ -668,7 +671,7 @@
pass
-class _SourcelessFileLoader(_FileLoader, _LoaderBasics):
+class _SourcelessFileLoader(FileLoader, _LoaderBasics):
"""Loader which handles sourceless file imports."""
@@ -692,7 +695,7 @@
return None
-class _ExtensionFileLoader:
+class ExtensionFileLoader:
"""Loader for extension modules.
@@ -701,8 +704,8 @@
"""
def __init__(self, name, path):
- self._name = name
- self._path = path
+ self.name = name
+ self.path = path
@_check_name
@set_package
@@ -711,8 +714,8 @@
"""Load an extension module."""
is_reload = fullname in sys.modules
try:
- module = _imp.load_dynamic(fullname, self._path)
- verbose_message('extension module loaded from {!r}', self._path)
+ module = _imp.load_dynamic(fullname, self.path)
+ verbose_message('extension module loaded from {!r}', self.path)
return module
except:
if not is_reload and fullname in sys.modules:
@@ -805,24 +808,25 @@
return None
-class _FileFinder:
+class FileFinder:
"""File-based finder.
- Constructor takes a list of objects detailing what file extensions their
- loader supports along with whether it can be used for a package.
+ Interactions with the file system are cached for performance, being
+ refreshed when the directory the finder is handling has been modified.
"""
def __init__(self, path, *details):
- """Initialize with finder details."""
+ """Initialize with the path to search on and a variable number of
+ 3-tuples containing the loader, file suffixes the loader recognizes, and
+ a boolean of whether the loader handles packages."""
packages = []
modules = []
- for detail in details:
- modules.extend((suffix, detail.loader) for suffix in detail.suffixes)
- if detail.supports_packages:
- packages.extend((suffix, detail.loader)
- for suffix in detail.suffixes)
+ for loader, suffixes, supports_packages in details:
+ modules.extend((suffix, loader) for suffix in suffixes)
+ if supports_packages:
+ packages.extend((suffix, loader) for suffix in suffixes)
self.packages = packages
self.modules = modules
# Base (directory) path
@@ -898,46 +902,29 @@
if sys.platform.startswith(CASE_INSENSITIVE_PLATFORMS):
self._relaxed_path_cache = set(fn.lower() for fn in contents)
+ @classmethod
+ def path_hook(cls, *loader_details):
+ """A class method which returns a closure to use on sys.path_hook
+ which will return an instance using the specified loaders and the path
+ called on the closure.
-class _SourceFinderDetails:
+ If the path called on the closure is not a directory, ImportError is
+ raised.
- loader = _SourceFileLoader
- supports_packages = True
+ """
+ def path_hook_for_FileFinder(path):
+ """Path hook for importlib.machinery.FileFinder."""
+ if not _path_isdir(path):
+ raise ImportError("only directories are supported", path=path)
+ return cls(path, *loader_details)
- def __init__(self):
- self.suffixes = _suffix_list(_imp.PY_SOURCE)
+ return path_hook_for_FileFinder
-class _SourcelessFinderDetails:
-
- loader = _SourcelessFileLoader
- supports_packages = True
-
- def __init__(self):
- self.suffixes = _suffix_list(_imp.PY_COMPILED)
-
-
-class _ExtensionFinderDetails:
-
- loader = _ExtensionFileLoader
- supports_packages = False
-
- def __init__(self):
- self.suffixes = _suffix_list(_imp.C_EXTENSION)
# Import itself ###############################################################
-def _file_path_hook(path):
- """If the path is a directory, return a file-based finder."""
- if _path_isdir(path):
- return _FileFinder(path, _ExtensionFinderDetails(),
- _SourceFinderDetails(),
- _SourcelessFinderDetails())
- else:
- raise ImportError("only directories are supported", path=path)
-
-
-_DEFAULT_PATH_HOOK = _file_path_hook
+_DEFAULT_PATH_HOOK = None # Set in _setup()
class _DefaultPathFinder(PathFinder):
@@ -1209,6 +1196,12 @@
if builtin_os == 'nt':
SOURCE_SUFFIXES.append('.pyw')
+ supported_loaders = [(ExtensionFileLoader, _suffix_list(3), False),
+ (SourceFileLoader, _suffix_list(1), True),
+ (_SourcelessFileLoader, _suffix_list(2), True)]
+ setattr(self_module, '_DEFAULT_PATH_HOOK',
+ FileFinder.path_hook(*supported_loaders))
+
def _install(sys_module, _imp_module):
"""Install importlib as the implementation of import.
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
index 43e4866..baa09fd 100644
--- a/Lib/importlib/abc.py
+++ b/Lib/importlib/abc.py
@@ -1,6 +1,12 @@
"""Abstract base classes related to import."""
from . import _bootstrap
from . import machinery
+try:
+ import _frozen_importlib
+except ImportError as exc:
+ if exc.name != '_frozen_importlib':
+ raise
+ _frozen_importlib = None
import abc
import imp
import marshal
@@ -9,6 +15,14 @@
import warnings
+def _register(abstract_cls, *classes):
+ for cls in classes:
+ abstract_cls.register(cls)
+ if _frozen_importlib is not None:
+ frozen_cls = getattr(_frozen_importlib, cls.__name__)
+ abstract_cls.register(frozen_cls)
+
+
class Loader(metaclass=abc.ABCMeta):
"""Abstract base class for import loaders."""
@@ -32,9 +46,8 @@
"""
raise NotImplementedError
-Finder.register(machinery.BuiltinImporter)
-Finder.register(machinery.FrozenImporter)
-Finder.register(machinery.PathFinder)
+_register(Finder, machinery.BuiltinImporter, machinery.FrozenImporter,
+ machinery.PathFinder, machinery.FileFinder)
class ResourceLoader(Loader):
@@ -80,8 +93,8 @@
module. The fullname is a str. Returns a str."""
raise NotImplementedError
-InspectLoader.register(machinery.BuiltinImporter)
-InspectLoader.register(machinery.FrozenImporter)
+_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter,
+ machinery.ExtensionFileLoader)
class ExecutionLoader(InspectLoader):
@@ -100,6 +113,15 @@
raise NotImplementedError
+class FileLoader(_bootstrap.FileLoader, ResourceLoader, ExecutionLoader):
+
+ """Abstract base class partially implementing the ResourceLoader and
+ ExecutionLoader ABCs."""
+
+_register(FileLoader, machinery.SourceFileLoader,
+ machinery._SourcelessFileLoader)
+
+
class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
"""Abstract base class for loading source code (and optionally any
@@ -146,6 +168,7 @@
"""
raise NotImplementedError
+_register(SourceLoader, machinery.SourceFileLoader)
class PyLoader(SourceLoader):
diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py
index 5197744..c9906c7 100644
--- a/Lib/importlib/machinery.py
+++ b/Lib/importlib/machinery.py
@@ -3,3 +3,7 @@
from ._bootstrap import BuiltinImporter
from ._bootstrap import FrozenImporter
from ._bootstrap import PathFinder
+from ._bootstrap import FileFinder
+from ._bootstrap import SourceFileLoader
+from ._bootstrap import _SourcelessFileLoader
+from ._bootstrap import ExtensionFileLoader
diff --git a/Lib/importlib/test/extension/test_case_sensitivity.py b/Lib/importlib/test/extension/test_case_sensitivity.py
index add830d..1ba2a33 100644
--- a/Lib/importlib/test/extension/test_case_sensitivity.py
+++ b/Lib/importlib/test/extension/test_case_sensitivity.py
@@ -1,3 +1,4 @@
+import imp
import sys
from test import support
import unittest
@@ -13,8 +14,10 @@
good_name = ext_util.NAME
bad_name = good_name.upper()
assert good_name != bad_name
- finder = _bootstrap._FileFinder(ext_util.PATH,
- _bootstrap._ExtensionFinderDetails())
+ finder = _bootstrap.FileFinder(ext_util.PATH,
+ (_bootstrap.ExtensionFileLoader,
+ _bootstrap._suffix_list(imp.C_EXTENSION),
+ False))
return finder.find_module(bad_name)
def test_case_sensitive(self):
diff --git a/Lib/importlib/test/extension/test_finder.py b/Lib/importlib/test/extension/test_finder.py
index ea97483..a28cd07 100644
--- a/Lib/importlib/test/extension/test_finder.py
+++ b/Lib/importlib/test/extension/test_finder.py
@@ -2,6 +2,7 @@
from .. import abc
from . import util
+import imp
import unittest
class FinderTests(abc.FinderTests):
@@ -9,8 +10,10 @@
"""Test the finder for extension modules."""
def find_module(self, fullname):
- importer = _bootstrap._FileFinder(util.PATH,
- _bootstrap._ExtensionFinderDetails())
+ importer = _bootstrap.FileFinder(util.PATH,
+ (_bootstrap.ExtensionFileLoader,
+ _bootstrap._suffix_list(imp.C_EXTENSION),
+ False))
return importer.find_module(fullname)
def test_module(self):
diff --git a/Lib/importlib/test/extension/test_loader.py b/Lib/importlib/test/extension/test_loader.py
index 9e5edad..ab2b686 100644
--- a/Lib/importlib/test/extension/test_loader.py
+++ b/Lib/importlib/test/extension/test_loader.py
@@ -12,7 +12,7 @@
"""Test load_module() for extension modules."""
def load_module(self, fullname):
- loader = _bootstrap._ExtensionFileLoader(ext_util.NAME,
+ loader = _bootstrap.ExtensionFileLoader(ext_util.NAME,
ext_util.FILEPATH)
return loader.load_module(fullname)
@@ -25,7 +25,7 @@
self.assertEqual(getattr(module, attr), value)
self.assertTrue(ext_util.NAME in sys.modules)
self.assertTrue(isinstance(module.__loader__,
- _bootstrap._ExtensionFileLoader))
+ _bootstrap.ExtensionFileLoader))
def test_package(self):
# Extensions are not found in packages.
diff --git a/Lib/importlib/test/extension/test_path_hook.py b/Lib/importlib/test/extension/test_path_hook.py
index 4610420..673c300 100644
--- a/Lib/importlib/test/extension/test_path_hook.py
+++ b/Lib/importlib/test/extension/test_path_hook.py
@@ -14,7 +14,8 @@
# XXX Should it only work for directories containing an extension module?
def hook(self, entry):
- return _bootstrap._file_path_hook(entry)
+ return _bootstrap.FileFinder.path_hook((_bootstrap.ExtensionFileLoader,
+ _bootstrap._suffix_list(imp.C_EXTENSION), False))(entry)
def test_success(self):
# Path hook should handle a directory where a known extension module
diff --git a/Lib/importlib/test/source/test_case_sensitivity.py b/Lib/importlib/test/source/test_case_sensitivity.py
index 569f516..d4bae8d 100644
--- a/Lib/importlib/test/source/test_case_sensitivity.py
+++ b/Lib/importlib/test/source/test_case_sensitivity.py
@@ -2,6 +2,7 @@
from importlib import _bootstrap
from .. import util
from . import util as source_util
+import imp
import os
import sys
from test import support as test_support
@@ -19,9 +20,13 @@
assert name != name.lower()
def find(self, path):
- finder = _bootstrap._FileFinder(path,
- _bootstrap._SourceFinderDetails(),
- _bootstrap._SourcelessFinderDetails())
+ finder = _bootstrap.FileFinder(path,
+ (_bootstrap.SourceFileLoader,
+ _bootstrap._suffix_list(imp.PY_SOURCE),
+ True),
+ (_bootstrap._SourcelessFileLoader,
+ _bootstrap._suffix_list(imp.PY_COMPILED),
+ True))
return finder.find_module(self.name)
def sensitivity_test(self):
diff --git a/Lib/importlib/test/source/test_file_loader.py b/Lib/importlib/test/source/test_file_loader.py
index 710339c..764dcff 100644
--- a/Lib/importlib/test/source/test_file_loader.py
+++ b/Lib/importlib/test/source/test_file_loader.py
@@ -27,7 +27,7 @@
# [basic]
def test_module(self):
with source_util.create_modules('_temp') as mapping:
- loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp'])
module = loader.load_module('_temp')
self.assertTrue('_temp' in sys.modules)
check = {'__name__': '_temp', '__file__': mapping['_temp'],
@@ -37,7 +37,7 @@
def test_package(self):
with source_util.create_modules('_pkg.__init__') as mapping:
- loader = _bootstrap._SourceFileLoader('_pkg',
+ loader = _bootstrap.SourceFileLoader('_pkg',
mapping['_pkg.__init__'])
module = loader.load_module('_pkg')
self.assertTrue('_pkg' in sys.modules)
@@ -50,7 +50,7 @@
def test_lacking_parent(self):
with source_util.create_modules('_pkg.__init__', '_pkg.mod')as mapping:
- loader = _bootstrap._SourceFileLoader('_pkg.mod',
+ loader = _bootstrap.SourceFileLoader('_pkg.mod',
mapping['_pkg.mod'])
module = loader.load_module('_pkg.mod')
self.assertTrue('_pkg.mod' in sys.modules)
@@ -65,7 +65,7 @@
def test_module_reuse(self):
with source_util.create_modules('_temp') as mapping:
- loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp'])
module = loader.load_module('_temp')
module_id = id(module)
module_dict_id = id(module.__dict__)
@@ -90,7 +90,7 @@
setattr(orig_module, attr, value)
with open(mapping[name], 'w') as file:
file.write('+++ bad syntax +++')
- loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp'])
with self.assertRaises(SyntaxError):
loader.load_module(name)
for attr in attributes:
@@ -101,7 +101,7 @@
with source_util.create_modules('_temp') as mapping:
with open(mapping['_temp'], 'w') as file:
file.write('=')
- loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp'])
with self.assertRaises(SyntaxError):
loader.load_module('_temp')
self.assertTrue('_temp' not in sys.modules)
@@ -114,7 +114,7 @@
file.write("# test file for importlib")
try:
with util.uncache('_temp'):
- loader = _bootstrap._SourceFileLoader('_temp', file_path)
+ loader = _bootstrap.SourceFileLoader('_temp', file_path)
mod = loader.load_module('_temp')
self.assertEqual(file_path, mod.__file__)
self.assertEqual(imp.cache_from_source(file_path),
@@ -140,7 +140,7 @@
if e.errno != getattr(errno, 'EOVERFLOW', None):
raise
self.skipTest("cannot set modification time to large integer ({})".format(e))
- loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp'])
mod = loader.load_module('_temp')
# Sanity checks.
self.assertEqual(mod.__cached__, compiled)
@@ -255,7 +255,7 @@
class SourceLoaderBadBytecodeTest(BadBytecodeTest):
- loader = _bootstrap._SourceFileLoader
+ loader = _bootstrap.SourceFileLoader
@source_util.writes_bytecode_files
def test_empty_file(self):
diff --git a/Lib/importlib/test/source/test_finder.py b/Lib/importlib/test/source/test_finder.py
index 315aa77..f5de58a 100644
--- a/Lib/importlib/test/source/test_finder.py
+++ b/Lib/importlib/test/source/test_finder.py
@@ -3,6 +3,7 @@
from importlib import _bootstrap
import errno
+import imp
import os
import py_compile
from test.support import make_legacy_pyc
@@ -35,9 +36,11 @@
"""
def import_(self, root, module):
- finder = _bootstrap._FileFinder(root,
- _bootstrap._SourceFinderDetails(),
- _bootstrap._SourcelessFinderDetails())
+ loader_details = [(_bootstrap.SourceFileLoader,
+ _bootstrap._suffix_list(imp.PY_SOURCE), True),
+ (_bootstrap._SourcelessFileLoader,
+ _bootstrap._suffix_list(imp.PY_COMPILED), True)]
+ finder = _bootstrap.FileFinder(root, *loader_details)
return finder.find_module(module)
def run_test(self, test, create=None, *, compile_=None, unlink=None):
@@ -135,7 +138,8 @@
def test_empty_string_for_dir(self):
# The empty string from sys.path means to search in the cwd.
- finder = _bootstrap._FileFinder('', _bootstrap._SourceFinderDetails())
+ finder = _bootstrap.FileFinder('', (_bootstrap.SourceFileLoader,
+ _bootstrap._suffix_list(imp.PY_SOURCE), True))
with open('mod.py', 'w') as file:
file.write("# test file for importlib")
try:
@@ -146,7 +150,8 @@
def test_invalidate_caches(self):
# invalidate_caches() should reset the mtime.
- finder = _bootstrap._FileFinder('', _bootstrap._SourceFinderDetails())
+ finder = _bootstrap.FileFinder('', (_bootstrap.SourceFileLoader,
+ _bootstrap._suffix_list(imp.PY_SOURCE), True))
finder._path_mtime = 42
finder.invalidate_caches()
self.assertEqual(finder._path_mtime, -1)
diff --git a/Lib/importlib/test/source/test_path_hook.py b/Lib/importlib/test/source/test_path_hook.py
index 3de822c..663a128 100644
--- a/Lib/importlib/test/source/test_path_hook.py
+++ b/Lib/importlib/test/source/test_path_hook.py
@@ -1,6 +1,7 @@
from . import util as source_util
from importlib import _bootstrap
+import imp
import unittest
@@ -8,14 +9,18 @@
"""Test the path hook for source."""
+ def path_hook(self):
+ return _bootstrap.FileFinder.path_hook((_bootstrap.SourceFileLoader,
+ _bootstrap._suffix_list(imp.PY_SOURCE), True))
+
def test_success(self):
with source_util.create_modules('dummy') as mapping:
- self.assertTrue(hasattr(_bootstrap._file_path_hook(mapping['.root']),
+ self.assertTrue(hasattr(self.path_hook()(mapping['.root']),
'find_module'))
def test_empty_string(self):
# The empty string represents the cwd.
- self.assertTrue(hasattr(_bootstrap._file_path_hook(''), 'find_module'))
+ self.assertTrue(hasattr(self.path_hook()(''), 'find_module'))
def test_main():
diff --git a/Lib/importlib/test/source/test_source_encoding.py b/Lib/importlib/test/source/test_source_encoding.py
index 72a1360..0ca5195 100644
--- a/Lib/importlib/test/source/test_source_encoding.py
+++ b/Lib/importlib/test/source/test_source_encoding.py
@@ -35,7 +35,7 @@
with source_util.create_modules(self.module_name) as mapping:
with open(mapping[self.module_name], 'wb') as file:
file.write(source)
- loader = _bootstrap._SourceFileLoader(self.module_name,
+ loader = _bootstrap.SourceFileLoader(self.module_name,
mapping[self.module_name])
return loader.load_module(self.module_name)
@@ -97,7 +97,7 @@
with source_util.create_modules(module_name) as mapping:
with open(mapping[module_name], 'wb') as file:
file.write(source)
- loader = _bootstrap._SourceFileLoader(module_name,
+ loader = _bootstrap.SourceFileLoader(module_name,
mapping[module_name])
return loader.load_module(module_name)
diff --git a/Lib/importlib/test/test_abc.py b/Lib/importlib/test/test_abc.py
index 0ecbe39..e9eec60 100644
--- a/Lib/importlib/test/test_abc.py
+++ b/Lib/importlib/test/test_abc.py
@@ -50,7 +50,7 @@
superclasses = [abc.Loader]
subclasses = [abc.PyLoader, machinery.BuiltinImporter,
- machinery.FrozenImporter]
+ machinery.FrozenImporter, machinery.ExtensionFileLoader]
class ExecutionLoader(InheritanceTests, unittest.TestCase):
@@ -59,9 +59,16 @@
subclasses = [abc.PyLoader]
+class FileLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.ResourceLoader, abc.ExecutionLoader]
+ subclasses = [machinery.SourceFileLoader, machinery._SourcelessFileLoader]
+
+
class SourceLoader(InheritanceTests, unittest.TestCase):
superclasses = [abc.ResourceLoader, abc.ExecutionLoader]
+ subclasses = [machinery.SourceFileLoader]
class PyLoader(InheritanceTests, unittest.TestCase):
diff --git a/Misc/NEWS b/Misc/NEWS
index 975e684..5bb9b92 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -61,6 +61,9 @@
Library
-------
+- Issue #14605: Add importlib.abc.FileLoader, importlib.machinery.(FileFinder,
+ SourceFileLoader, _SourcelessFileLoader, ExtensionFileLoader).
+
- Issue #13959: imp.cache_from_source()/source_from_cache() now follow
os.path.join()/split() semantics for path manipulation instead of its prior,
custom semantics of caring the right-most path separator forward in path
diff --git a/Python/importlib.h b/Python/importlib.h
index 4b1001d..bc4c0d9 100644
--- a/Python/importlib.h
+++ b/Python/importlib.h
Binary files differ