Skip to content

Commit bb54b33

Browse files
committed
Merge
2 parents eafae02 + ee78a2b commit bb54b33

File tree

6 files changed

+105
-18
lines changed

6 files changed

+105
-18
lines changed

Doc/library/importlib.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ Functions
8686
that was imported (e.g. ``pkg.mod``), while :func:`__import__` returns the
8787
top-level package or module (e.g. ``pkg``).
8888

89+
.. function:: find_loader(name, path=None)
90+
91+
Find the loader for a module, optionally within the specified *path*. If the
92+
module is in :attr:`sys.modules`, then ``sys.modules[name].__loader__`` is
93+
returned (unless the loader would be ``None``, in which case
94+
:exc:`ValueError` is raised). Otherwise a search using :attr:`sys.meta_path`
95+
is done. ``None`` is returned if no loader is found.
96+
97+
A dotted name does not have its parent's implicitly imported. If that is
98+
desired (although not nessarily required to find the loader, it will most
99+
likely be needed if the loader actually is used to load the module), then
100+
you will have to import the packages containing the module prior to calling
101+
this function.
102+
89103
.. function:: invalidate_caches()
90104

91105
Invalidate the internal caches of the finders stored at

Lib/importlib/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,30 @@ def invalidate_caches():
2929
finder.invalidate_caches()
3030

3131

32+
def find_loader(name, path=None):
33+
"""Find the loader for the specified module.
34+
35+
First, sys.modules is checked to see if the module was already imported. If
36+
so, then sys.modules[name].__loader__ is returned. If that happens to be
37+
set to None, then ValueError is raised. If the module is not in
38+
sys.modules, then sys.meta_path is searched for a suitable loader with the
39+
value of 'path' given to the finders. None is returned if no loader could
40+
be found.
41+
42+
Dotted names do not have their parent packages implicitly imported.
43+
44+
"""
45+
try:
46+
loader = sys.modules[name].__loader__
47+
if loader is None:
48+
raise ValueError('{}.__loader__ is None'.format(name))
49+
else:
50+
return loader
51+
except KeyError:
52+
pass
53+
return _bootstrap._find_module(name, path)
54+
55+
3256
def import_module(name, package=None):
3357
"""Import a module.
3458

Lib/importlib/test/test_api.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,54 @@ def load_b():
8585
self.assertEqual(b_load_count, 1)
8686

8787

88+
class FindLoaderTests(unittest.TestCase):
89+
90+
class FakeMetaFinder:
91+
@staticmethod
92+
def find_module(name, path=None): return name, path
93+
94+
def test_sys_modules(self):
95+
# If a module with __loader__ is in sys.modules, then return it.
96+
name = 'some_mod'
97+
with util.uncache(name):
98+
module = imp.new_module(name)
99+
loader = 'a loader!'
100+
module.__loader__ = loader
101+
sys.modules[name] = module
102+
found = importlib.find_loader(name)
103+
self.assertEqual(loader, found)
104+
105+
def test_sys_modules_loader_is_None(self):
106+
# If sys.modules[name].__loader__ is None, raise ValueError.
107+
name = 'some_mod'
108+
with util.uncache(name):
109+
module = imp.new_module(name)
110+
module.__loader__ = None
111+
sys.modules[name] = module
112+
with self.assertRaises(ValueError):
113+
importlib.find_loader(name)
114+
115+
def test_success(self):
116+
# Return the loader found on sys.meta_path.
117+
name = 'some_mod'
118+
with util.uncache(name):
119+
with util.import_state(meta_path=[self.FakeMetaFinder]):
120+
self.assertEqual((name, None), importlib.find_loader(name))
121+
122+
def test_success_path(self):
123+
# Searching on a path should work.
124+
name = 'some_mod'
125+
path = 'path to some place'
126+
with util.uncache(name):
127+
with util.import_state(meta_path=[self.FakeMetaFinder]):
128+
self.assertEqual((name, path),
129+
importlib.find_loader(name, path))
130+
131+
def test_nothing(self):
132+
# None is returned upon failure to find a loader.
133+
self.assertIsNone(importlib.find_loader('nevergoingtofindthismodule'))
134+
135+
88136
class InvalidateCacheTests(unittest.TestCase):
89137

90138
def test_method_called(self):
@@ -114,7 +162,7 @@ def test_method_lacking(self):
114162

115163
def test_main():
116164
from test.support import run_unittest
117-
run_unittest(ImportModuleTests)
165+
run_unittest(ImportModuleTests, FindLoaderTests, InvalidateCacheTests)
118166

119167

120168
if __name__ == '__main__':

Lib/pyclbr.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
lineno -- the line in the file on which the class statement occurred
4040
"""
4141

42+
import io
43+
import os
4244
import sys
43-
import imp
45+
import importlib
4446
import tokenize
4547
from token import NAME, DEDENT, OP
4648
from operator import itemgetter
@@ -133,19 +135,24 @@ def _readmodule(module, path, inpackage=None):
133135
# Search the path for the module
134136
f = None
135137
if inpackage is not None:
136-
f, fname, (_s, _m, ty) = imp.find_module(module, path)
138+
search_path = path
137139
else:
138-
f, fname, (_s, _m, ty) = imp.find_module(module, path + sys.path)
139-
if ty == imp.PKG_DIRECTORY:
140-
dict['__path__'] = [fname]
141-
path = [fname] + path
142-
f, fname, (_s, _m, ty) = imp.find_module('__init__', [fname])
140+
search_path = path + sys.path
141+
loader = importlib.find_loader(fullmodule, search_path)
142+
fname = loader.get_filename(fullmodule)
143143
_modules[fullmodule] = dict
144-
if ty != imp.PY_SOURCE:
144+
if loader.is_package(fullmodule):
145+
dict['__path__'] = [os.path.dirname(fname)]
146+
try:
147+
source = loader.get_source(fullmodule)
148+
if source is None:
149+
return dict
150+
except (AttributeError, ImportError):
145151
# not Python source, can't do anything with this module
146-
f.close()
147152
return dict
148153

154+
f = io.StringIO(source)
155+
149156
stack = [] # stack of (class, indent) pairs
150157

151158
g = tokenize.generate_tokens(f.readline)

Lib/test/test_unicode.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,14 +1422,6 @@ def test_codecs_errors(self):
14221422
self.assertRaises(TypeError, str, b"hello", "test.unicode2")
14231423
self.assertRaises(TypeError, "hello".encode, "test.unicode1")
14241424
self.assertRaises(TypeError, "hello".encode, "test.unicode2")
1425-
# executes PyUnicode_Encode()
1426-
import imp
1427-
self.assertRaises(
1428-
ImportError,
1429-
imp.find_module,
1430-
"non-existing module",
1431-
["non-existing dir"]
1432-
)
14331425

14341426
# Error handling (wrong arguments)
14351427
self.assertRaises(TypeError, "hello".encode, 42, 42, 42)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Core and Builtins
2323
Library
2424
-------
2525

26+
- Issue #13959: Introduce importlib.find_loader().
27+
2628
- Issue #14082: shutil.copy2() now copies extended attributes, if possible.
2729
Patch by Hynek Schlawack.
2830

0 commit comments

Comments
 (0)