Issue #13959: Introduce importlib.find_loader().

The long-term goal is to deprecate imp.find_module() in favour of this
API, but it will take some time as some APIs explicitly return/use what
imp.find_module() returns.
This commit is contained in:
Brett Cannon 2012-05-12 17:43:17 -04:00
parent acc0c181a8
commit ee78a2b51c
5 changed files with 105 additions and 10 deletions

View File

@ -86,6 +86,20 @@ Functions
that was imported (e.g. ``pkg.mod``), while :func:`__import__` returns the
top-level package or module (e.g. ``pkg``).
.. function:: find_loader(name, path=None)
Find the loader for a module, optionally within the specified *path*. If the
module is in :attr:`sys.modules`, then ``sys.modules[name].__loader__`` is
returned (unless the loader would be ``None``, in which case
:exc:`ValueError` is raised). Otherwise a search using :attr:`sys.meta_path`
is done. ``None`` is returned if no loader is found.
A dotted name does not have its parent's implicitly imported. If that is
desired (although not nessarily required to find the loader, it will most
likely be needed if the loader actually is used to load the module), then
you will have to import the packages containing the module prior to calling
this function.
.. function:: invalidate_caches()
Invalidate the internal caches of the finders stored at

View File

@ -29,6 +29,30 @@ def invalidate_caches():
finder.invalidate_caches()
def find_loader(name, path=None):
"""Find the loader for the specified module.
First, sys.modules is checked to see if the module was already imported. If
so, then sys.modules[name].__loader__ is returned. If that happens to be
set to None, then ValueError is raised. If the module is not in
sys.modules, then sys.meta_path is searched for a suitable loader with the
value of 'path' given to the finders. None is returned if no loader could
be found.
Dotted names do not have their parent packages implicitly imported.
"""
try:
loader = sys.modules[name].__loader__
if loader is None:
raise ValueError('{}.__loader__ is None'.format(name))
else:
return loader
except KeyError:
pass
return _bootstrap._find_module(name, path)
def import_module(name, package=None):
"""Import a module.

View File

@ -85,6 +85,54 @@ class ImportModuleTests(unittest.TestCase):
self.assertEqual(b_load_count, 1)
class FindLoaderTests(unittest.TestCase):
class FakeMetaFinder:
@staticmethod
def find_module(name, path=None): return name, path
def test_sys_modules(self):
# If a module with __loader__ is in sys.modules, then return it.
name = 'some_mod'
with util.uncache(name):
module = imp.new_module(name)
loader = 'a loader!'
module.__loader__ = loader
sys.modules[name] = module
found = importlib.find_loader(name)
self.assertEqual(loader, found)
def test_sys_modules_loader_is_None(self):
# If sys.modules[name].__loader__ is None, raise ValueError.
name = 'some_mod'
with util.uncache(name):
module = imp.new_module(name)
module.__loader__ = None
sys.modules[name] = module
with self.assertRaises(ValueError):
importlib.find_loader(name)
def test_success(self):
# Return the loader found on sys.meta_path.
name = 'some_mod'
with util.uncache(name):
with util.import_state(meta_path=[self.FakeMetaFinder]):
self.assertEqual((name, None), importlib.find_loader(name))
def test_success_path(self):
# Searching on a path should work.
name = 'some_mod'
path = 'path to some place'
with util.uncache(name):
with util.import_state(meta_path=[self.FakeMetaFinder]):
self.assertEqual((name, path),
importlib.find_loader(name, path))
def test_nothing(self):
# None is returned upon failure to find a loader.
self.assertIsNone(importlib.find_loader('nevergoingtofindthismodule'))
class InvalidateCacheTests(unittest.TestCase):
def test_method_called(self):
@ -114,7 +162,7 @@ class InvalidateCacheTests(unittest.TestCase):
def test_main():
from test.support import run_unittest
run_unittest(ImportModuleTests)
run_unittest(ImportModuleTests, FindLoaderTests, InvalidateCacheTests)
if __name__ == '__main__':

View File

@ -39,8 +39,10 @@ Instances of this class have the following instance variables:
lineno -- the line in the file on which the class statement occurred
"""
import io
import os
import sys
import imp
import importlib
import tokenize
from token import NAME, DEDENT, OP
from operator import itemgetter
@ -133,19 +135,24 @@ def _readmodule(module, path, inpackage=None):
# Search the path for the module
f = None
if inpackage is not None:
f, fname, (_s, _m, ty) = imp.find_module(module, path)
search_path = path
else:
f, fname, (_s, _m, ty) = imp.find_module(module, path + sys.path)
if ty == imp.PKG_DIRECTORY:
dict['__path__'] = [fname]
path = [fname] + path
f, fname, (_s, _m, ty) = imp.find_module('__init__', [fname])
search_path = path + sys.path
loader = importlib.find_loader(fullmodule, search_path)
fname = loader.get_filename(fullmodule)
_modules[fullmodule] = dict
if ty != imp.PY_SOURCE:
if loader.is_package(fullmodule):
dict['__path__'] = [os.path.dirname(fname)]
try:
source = loader.get_source(fullmodule)
if source is None:
return dict
except (AttributeError, ImportError):
# not Python source, can't do anything with this module
f.close()
return dict
f = io.StringIO(source)
stack = [] # stack of (class, indent) pairs
g = tokenize.generate_tokens(f.readline)

View File

@ -23,6 +23,8 @@ Core and Builtins
Library
-------
- Issue #13959: Introduce importlib.find_loader().
- Issue #14082: shutil.copy2() now copies extended attributes, if possible.
Patch by Hynek Schlawack.