(original) (raw)
changeset: 76478:1da623513b26 user: Brett Cannon brett@python.org date: Sun Apr 22 19:58:33 2012 -0400 files: Doc/library/importlib.rst Lib/imp.py Lib/importlib/_bootstrap.py Lib/importlib/abc.py Lib/importlib/machinery.py Lib/importlib/test/extension/test_case_sensitivity.py Lib/importlib/test/extension/test_finder.py Lib/importlib/test/extension/test_loader.py Lib/importlib/test/extension/test_path_hook.py Lib/importlib/test/source/test_case_sensitivity.py Lib/importlib/test/source/test_file_loader.py Lib/importlib/test/source/test_finder.py Lib/importlib/test/source/test_path_hook.py Lib/importlib/test/source/test_source_encoding.py Lib/importlib/test/test_abc.py Misc/NEWS Python/importlib.h description: 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 -r 0a79a2add552 -r 1da623513b26 Doc/library/importlib.rst --- a/Doc/library/importlib.rst Sun Apr 22 13:30:07 2012 -0400 +++ b/Doc/library/importlib.rst Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/imp.py --- a/Lib/imp.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/imp.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/_bootstrap.py Sun Apr 22 19:58:33 2012 -0400 @@ -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) - -class _SourceFinderDetails: + @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. - loader = _SourceFileLoader - supports_packages = True - - def __init__(self): - self.suffixes = _suffix_list(_imp.PY_SOURCE) - -class _SourcelessFinderDetails: + If the path called on the closure is not a directory, ImportError is + raised. - loader = _SourcelessFileLoader - supports_packages = True - - def __init__(self): - self.suffixes = _suffix_list(_imp.PY_COMPILED) - + """ + 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) -class _ExtensionFinderDetails: + return path_hook_for_FileFinder - 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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/abc.py --- a/Lib/importlib/abc.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/abc.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/machinery.py --- a/Lib/importlib/machinery.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/machinery.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/extension/test_case_sensitivity.py --- a/Lib/importlib/test/extension/test_case_sensitivity.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/extension/test_case_sensitivity.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/extension/test_finder.py --- a/Lib/importlib/test/extension/test_finder.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/extension/test_finder.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/extension/test_loader.py --- a/Lib/importlib/test/extension/test_loader.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/extension/test_loader.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/extension/test_path_hook.py --- a/Lib/importlib/test/extension/test_path_hook.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/extension/test_path_hook.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/source/test_case_sensitivity.py --- a/Lib/importlib/test/source/test_case_sensitivity.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/source/test_case_sensitivity.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/source/test_file_loader.py --- a/Lib/importlib/test/source/test_file_loader.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/source/test_file_loader.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/source/test_finder.py --- a/Lib/importlib/test/source/test_finder.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/source/test_finder.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/source/test_path_hook.py --- a/Lib/importlib/test/source/test_path_hook.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/source/test_path_hook.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/source/test_source_encoding.py --- a/Lib/importlib/test/source/test_source_encoding.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/source/test_source_encoding.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Lib/importlib/test/test_abc.py --- a/Lib/importlib/test/test_abc.py Sun Apr 22 13:30:07 2012 -0400 +++ b/Lib/importlib/test/test_abc.py Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Misc/NEWS --- a/Misc/NEWS Sun Apr 22 13:30:07 2012 -0400 +++ b/Misc/NEWS Sun Apr 22 19:58:33 2012 -0400 @@ -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 -r 0a79a2add552 -r 1da623513b26 Python/importlib.h Binary file Python/importlib.h has changed /brett@python.org