Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8d94229

Browse files
committed
Issue #19719: Update various finder and loader ABCs such that their
old methods now provide implementations when PEP 451 APIs are present. This should help with backwards-compatibility with code which has not been updated to work with PEP 451.
1 parent 61272b7 commit 8d94229

4 files changed

Lines changed: 176 additions & 8 deletions

File tree

Doc/library/importlib.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,13 @@ ABC hierarchy::
275275
will be the value of :attr:`__path__` from the parent
276276
package. If a loader cannot be found, ``None`` is returned.
277277

278+
If :meth:`find_spec` is defined, backwards-compatible functionality is
279+
provided.
280+
278281
.. versionchanged:: 3.4
279282
Returns ``None`` when called instead of raising
280-
:exc:`NotImplementedError`.
283+
:exc:`NotImplementedError`. Can use :meth:`find_spec` to provide
284+
functionality.
281285

282286
.. deprecated:: 3.4
283287
Use :meth:`find_spec` instead.
@@ -325,8 +329,12 @@ ABC hierarchy::
325329
``portion`` is the empty list then no loader or location for a namespace
326330
package were found (i.e. failure to find anything for the module).
327331

332+
If :meth:`find_spec` is defined then backwards-compatible functionality is
333+
provided.
334+
328335
.. versionchanged:: 3.4
329336
Returns ``(None, [])`` instead of raising :exc:`NotImplementedError`.
337+
Uses :meth:`find_spec` when available to provide functionality.
330338

331339
.. deprecated:: 3.4
332340
Use :meth:`find_spec` instead.
@@ -413,9 +421,13 @@ ABC hierarchy::
413421
:func:`importlib.util.module_for_loader` decorator can handle the
414422
details for :attr:`__package__`.
415423

424+
When :meth:`exec_module` is available then backwards-compatible
425+
functionality is provided.
426+
416427
.. versionchanged:: 3.4
417428
Raise :exc:`ImportError` when called instead of
418-
:exc:`NotImplementedError`.
429+
:exc:`NotImplementedError`. Functionality provided when
430+
:meth:`exec_module` is available.
419431

420432
.. deprecated:: 3.4
421433
The recommended API for loading a module is :meth:`exec_module`

Lib/importlib/abc.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,15 @@ def find_module(self, fullname, path):
4949
If no module is found, return None. The fullname is a str and
5050
the path is a list of strings or None.
5151
52-
This method is deprecated in favor of finder.find_spec().
52+
This method is deprecated in favor of finder.find_spec(). If find_spec()
53+
exists then backwards-compatible functionality is provided for this
54+
method.
5355
5456
"""
55-
return None
57+
if not hasattr(self, 'find_spec'):
58+
return None
59+
found = self.find_spec(fullname, path)
60+
return found.loader if found is not None else None
5661

5762
def invalidate_caches(self):
5863
"""An optional method for clearing the finder's cache, if any.
@@ -81,10 +86,21 @@ def find_loader(self, fullname):
8186
The portion will be discarded if another path entry finder
8287
locates the module as a normal module or package.
8388
84-
This method is deprecated in favor of finder.find_spec().
89+
This method is deprecated in favor of finder.find_spec(). If find_spec()
90+
is provided than backwards-compatible functionality is provided.
8591
8692
"""
87-
return None, []
93+
if not hasattr(self, 'find_spec'):
94+
return None, []
95+
found = self.find_spec(fullname)
96+
if found is not None:
97+
if not found.submodule_search_locations:
98+
portions = []
99+
else:
100+
portions = found.submodule_search_locations
101+
return found.loader, portions
102+
else:
103+
return None, []
88104

89105
find_module = _bootstrap._find_module_shim
90106

@@ -124,10 +140,14 @@ def load_module(self, fullname):
124140
125141
ImportError is raised on failure.
126142
127-
This method is deprecated in favor of loader.exec_module().
143+
This method is deprecated in favor of loader.exec_module(). If
144+
exec_module() exists then it is used to provide a backwards-compatible
145+
functionality for this method.
128146
129147
"""
130-
raise ImportError
148+
if not hasattr(self, 'exec_module'):
149+
raise ImportError
150+
return _bootstrap._load_module_shim(self, fullname)
131151

132152
def module_repr(self, module):
133153
"""Return a module's repr.

Lib/test/test_importlib/test_abc.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
frozen_init, source_init = util.import_importlib('importlib')
1616
frozen_abc, source_abc = util.import_importlib('importlib.abc')
17+
machinery = util.import_importlib('importlib.machinery')
1718
frozen_util, source_util = util.import_importlib('importlib.util')
1819

1920
##### Inheritance ##############################################################
@@ -285,6 +286,137 @@ def test_get_filename(self):
285286
tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests)
286287
Frozen_ELDefaultTests, Source_ELDefaultsTests = tests
287288

289+
##### MetaPathFinder concrete methods ##########################################
290+
291+
class MetaPathFinderFindModuleTests:
292+
293+
@classmethod
294+
def finder(cls, spec):
295+
class MetaPathSpecFinder(cls.abc.MetaPathFinder):
296+
297+
def find_spec(self, fullname, path, target=None):
298+
self.called_for = fullname, path
299+
return spec
300+
301+
return MetaPathSpecFinder()
302+
303+
def test_no_spec(self):
304+
finder = self.finder(None)
305+
path = ['a', 'b', 'c']
306+
name = 'blah'
307+
found = finder.find_module(name, path)
308+
self.assertIsNone(found)
309+
self.assertEqual(name, finder.called_for[0])
310+
self.assertEqual(path, finder.called_for[1])
311+
312+
def test_spec(self):
313+
loader = object()
314+
spec = self.util.spec_from_loader('blah', loader)
315+
finder = self.finder(spec)
316+
found = finder.find_module('blah', None)
317+
self.assertIs(found, spec.loader)
318+
319+
320+
Frozen_MPFFindModuleTests, Source_MPFFindModuleTests = util.test_both(
321+
MetaPathFinderFindModuleTests,
322+
abc=(frozen_abc, source_abc),
323+
util=(frozen_util, source_util))
324+
325+
##### PathEntryFinder concrete methods #########################################
326+
327+
class PathEntryFinderFindLoaderTests:
328+
329+
@classmethod
330+
def finder(cls, spec):
331+
class PathEntrySpecFinder(cls.abc.PathEntryFinder):
332+
333+
def find_spec(self, fullname, target=None):
334+
self.called_for = fullname
335+
return spec
336+
337+
return PathEntrySpecFinder()
338+
339+
def test_no_spec(self):
340+
finder = self.finder(None)
341+
name = 'blah'
342+
found = finder.find_loader(name)
343+
self.assertIsNone(found[0])
344+
self.assertEqual([], found[1])
345+
self.assertEqual(name, finder.called_for)
346+
347+
def test_spec_with_loader(self):
348+
loader = object()
349+
spec = self.util.spec_from_loader('blah', loader)
350+
finder = self.finder(spec)
351+
found = finder.find_loader('blah')
352+
self.assertIs(found[0], spec.loader)
353+
354+
def test_spec_with_portions(self):
355+
spec = self.machinery.ModuleSpec('blah', None)
356+
paths = ['a', 'b', 'c']
357+
spec.submodule_search_locations = paths
358+
finder = self.finder(spec)
359+
found = finder.find_loader('blah')
360+
self.assertIsNone(found[0])
361+
self.assertEqual(paths, found[1])
362+
363+
364+
Frozen_PEFFindLoaderTests, Source_PEFFindLoaderTests = util.test_both(
365+
PathEntryFinderFindLoaderTests,
366+
abc=(frozen_abc, source_abc),
367+
machinery=machinery,
368+
util=(frozen_util, source_util))
369+
370+
371+
##### Loader concrete methods ##################################################
372+
class LoaderLoadModuleTests:
373+
374+
def loader(self):
375+
class SpecLoader(self.abc.Loader):
376+
found = None
377+
def exec_module(self, module):
378+
self.found = module
379+
380+
def is_package(self, fullname):
381+
"""Force some non-default module state to be set."""
382+
return True
383+
384+
return SpecLoader()
385+
386+
def test_fresh(self):
387+
loader = self.loader()
388+
name = 'blah'
389+
with util.uncache(name):
390+
loader.load_module(name)
391+
module = loader.found
392+
self.assertIs(sys.modules[name], module)
393+
self.assertEqual(loader, module.__loader__)
394+
self.assertEqual(loader, module.__spec__.loader)
395+
self.assertEqual(name, module.__name__)
396+
self.assertEqual(name, module.__spec__.name)
397+
self.assertIsNotNone(module.__path__)
398+
self.assertIsNotNone(module.__path__,
399+
module.__spec__.submodule_search_locations)
400+
401+
def test_reload(self):
402+
name = 'blah'
403+
loader = self.loader()
404+
module = types.ModuleType(name)
405+
module.__spec__ = self.util.spec_from_loader(name, loader)
406+
module.__loader__ = loader
407+
with util.uncache(name):
408+
sys.modules[name] = module
409+
loader.load_module(name)
410+
found = loader.found
411+
self.assertIs(found, sys.modules[name])
412+
self.assertIs(module, sys.modules[name])
413+
414+
415+
Frozen_LoaderLoadModuleTests, Source_LoaderLoadModuleTests = util.test_both(
416+
LoaderLoadModuleTests,
417+
abc=(frozen_abc, source_abc),
418+
util=(frozen_util, source_util))
419+
288420

289421
##### InspectLoader concrete methods ###########################################
290422
class InspectLoaderSourceToCodeTests:

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Core and Builtins
1313
Library
1414
-------
1515

16+
- Issue #19719: Make importlib.abc.MetaPathFinder.find_module(),
17+
PathEntryFinder.find_loader(), and Loader.load_module() use PEP 451 APIs to
18+
help with backwards-compatibility.
19+
1620
- Issue #20144: inspect.Signature now supports parsing simple symbolic
1721
constants as parameter default values in __text_signature__.
1822

0 commit comments

Comments
 (0)