Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Doc/reference/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,8 @@ a list containing the portion.
``find_loader()`` in preference to ``find_module()``.

.. versionchanged:: 3.10
Calls to :meth:`~importlib.abc.PathEntryFinder.find_module` by the import
Calls to :meth:`~importlib.abc.PathEntryFinder.find_module` and
:meth:`~importlib.abc.PathEntryFinder.find_loader` by the import
system will raise :exc:`ImportWarning`.


Expand Down
8 changes: 7 additions & 1 deletion Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,13 @@ Deprecated
:meth:`importlib.abc.PathEntryFinder.find_spec`
are preferred, respectively. You can use
:func:`importlib.util.spec_from_loader` to help in porting.
(Contributed by Brett Cannon in :issue:`42134`.)
(Contributed by Brett Cannon in :issue:`42134`.)

* The use of :meth:`importlib.abc.PathEntryFinder.find_loader` by the import
system now triggers an :exc:`ImportWarning` as
:meth:`importlib.abc.PathEntryFinder.find_spec` is preferred. You can use
:func:`importlib.util.spec_from_loader` to help in porting.
(Contributed by Brett Cannon in :issue:`43672`.)

* The import system now uses the ``__spec__`` attribute on modules before
falling back on :meth:`~importlib.abc.Loader.module_repr` for a module's
Expand Down
5 changes: 4 additions & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -1323,10 +1323,13 @@ def _legacy_get_spec(cls, fullname, finder):
# This would be a good place for a DeprecationWarning if
# we ended up going that route.
if hasattr(finder, 'find_loader'):
msg = (f"{_bootstrap._object_name(finder)}.find_spec() not found; "
"falling back to find_loader()")
_warnings.warn(msg, ImportWarning)
loader, portions = finder.find_loader(fullname)
else:
msg = (f"{_bootstrap._object_name(finder)}.find_spec() not found; "
"falling back to find_module()")
"falling back to find_module()")
_warnings.warn(msg, ImportWarning)
loader = finder.find_module(fullname)
portions = []
Expand Down
37 changes: 18 additions & 19 deletions Lib/test/test_importlib/extension/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ def test_is_package(self):
) = util.test_both(LoaderTests, machinery=machinery)

class MultiPhaseExtensionModuleTests(abc.LoaderTests):
"""Test loading extension modules with multi-phase initialization (PEP 489)
"""
# Test loading extension modules with multi-phase initialization (PEP 489).

def setUp(self):
self.name = '_testmultiphase'
Expand All @@ -101,13 +100,13 @@ def setUp(self):
self.name, self.spec.origin)

def load_module(self):
'''Load the module from the test extension'''
# Load the module from the test extension.
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return self.loader.load_module(self.name)

def load_module_by_name(self, fullname):
'''Load a module from the test extension by name'''
# Load a module from the test extension by name.
origin = self.spec.origin
loader = self.machinery.ExtensionFileLoader(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
Expand All @@ -125,7 +124,7 @@ def load_module_by_name(self, fullname):
test_state_after_failure = None

def test_module(self):
'''Test loading an extension module'''
# Test loading an extension module.
with util.uncache(self.name):
module = self.load_module()
for attr, value in [('__name__', self.name),
Expand All @@ -139,7 +138,7 @@ def test_module(self):
self.machinery.ExtensionFileLoader)

def test_functionality(self):
'''Test basic functionality of stuff defined in an extension module'''
# Test basic functionality of stuff defined in an extension module.
with util.uncache(self.name):
module = self.load_module()
self.assertIsInstance(module, types.ModuleType)
Expand All @@ -159,15 +158,15 @@ def test_functionality(self):
self.assertEqual(module.str_const, 'something different')

def test_reload(self):
'''Test that reload didn't re-set the module's attributes'''
# Test that reload didn't re-set the module's attributes.
with util.uncache(self.name):
module = self.load_module()
ex_class = module.Example
importlib.reload(module)
self.assertIs(ex_class, module.Example)

def test_try_registration(self):
'''Assert that the PyState_{Find,Add,Remove}Module C API doesn't work'''
# Assert that the PyState_{Find,Add,Remove}Module C API doesn't work.
module = self.load_module()
with self.subTest('PyState_FindModule'):
self.assertEqual(module.call_state_registration_func(0), None)
Expand All @@ -179,65 +178,65 @@ def test_try_registration(self):
module.call_state_registration_func(2)

def test_load_submodule(self):
'''Test loading a simulated submodule'''
# Test loading a simulated submodule.
module = self.load_module_by_name('pkg.' + self.name)
self.assertIsInstance(module, types.ModuleType)
self.assertEqual(module.__name__, 'pkg.' + self.name)
self.assertEqual(module.str_const, 'something different')

def test_load_short_name(self):
'''Test loading module with a one-character name'''
# Test loading module with a one-character name.
module = self.load_module_by_name('x')
self.assertIsInstance(module, types.ModuleType)
self.assertEqual(module.__name__, 'x')
self.assertEqual(module.str_const, 'something different')
self.assertNotIn('x', sys.modules)

def test_load_twice(self):
'''Test that 2 loads result in 2 module objects'''
# Test that 2 loads result in 2 module objects.
module1 = self.load_module_by_name(self.name)
module2 = self.load_module_by_name(self.name)
self.assertIsNot(module1, module2)

def test_unloadable(self):
'''Test nonexistent module'''
# Test nonexistent module.
name = 'asdfjkl;'
with self.assertRaises(ImportError) as cm:
self.load_module_by_name(name)
self.assertEqual(cm.exception.name, name)

def test_unloadable_nonascii(self):
'''Test behavior with nonexistent module with non-ASCII name'''
# Test behavior with nonexistent module with non-ASCII name.
name = 'fo\xf3'
with self.assertRaises(ImportError) as cm:
self.load_module_by_name(name)
self.assertEqual(cm.exception.name, name)

def test_nonmodule(self):
'''Test returning a non-module object from create works'''
# Test returning a non-module object from create works.
name = self.name + '_nonmodule'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)

# issue 27782
def test_nonmodule_with_methods(self):
'''Test creating a non-module object with methods defined'''
# Test creating a non-module object with methods defined.
name = self.name + '_nonmodule_with_methods'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
self.assertEqual(mod.bar(10, 1), 9)

def test_null_slots(self):
'''Test that NULL slots aren't a problem'''
# Test that NULL slots aren't a problem.
name = self.name + '_null_slots'
module = self.load_module_by_name(name)
self.assertIsInstance(module, types.ModuleType)
self.assertEqual(module.__name__, name)

def test_bad_modules(self):
'''Test SystemError is raised for misbehaving extensions'''
# Test SystemError is raised for misbehaving extensions.
for name_base in [
'bad_slot_large',
'bad_slot_negative',
Expand All @@ -261,9 +260,9 @@ def test_bad_modules(self):
self.load_module_by_name(name)

def test_nonascii(self):
'''Test that modules with non-ASCII names can be loaded'''
# Test that modules with non-ASCII names can be loaded.
# punycode behaves slightly differently in some-ASCII and no-ASCII
# cases, so test both
# cases, so test both.
cases = [
(self.name + '_zkou\u0161ka_na\u010dten\xed', 'Czech'),
('\uff3f\u30a4\u30f3\u30dd\u30fc\u30c8\u30c6\u30b9\u30c8',
Expand Down
8 changes: 6 additions & 2 deletions Lib/test/test_importlib/import_/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,16 @@ def find_loader(self, fullname):
return self.loader, self.portions
path = 'testing path'
with util.import_state(path_importer_cache={path: TestFinder()}):
self.assertIsNone(
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
self.assertIsNone(
self.machinery.PathFinder.find_spec('whatever', [path]))
success_finder = TestFinder()
success_finder.loader = __loader__
with util.import_state(path_importer_cache={path: success_finder}):
spec = self.machinery.PathFinder.find_spec('whatever', [path])
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
spec = self.machinery.PathFinder.find_spec('whatever', [path])
self.assertEqual(spec.loader, __loader__)

def test_finder_with_find_spec(self):
Expand Down
10 changes: 5 additions & 5 deletions Lib/test/test_importlib/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,13 @@ def test_load_module(self):
def test_module_repr(self):
mod = types.ModuleType('blah')
with warnings.catch_warnings():
warnings.simplefilter("ignore")
warnings.simplefilter("ignore", DeprecationWarning)
with self.assertRaises(NotImplementedError):
self.ins.module_repr(mod)
original_repr = repr(mod)
mod.__loader__ = self.ins
# Should still return a proper repr.
self.assertTrue(repr(mod))
original_repr = repr(mod)
mod.__loader__ = self.ins
# Should still return a proper repr.
self.assertTrue(repr(mod))


(Frozen_LDefaultTests,
Expand Down
36 changes: 12 additions & 24 deletions Lib/test/test_importlib/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,10 @@ def test_for_name_does_not_exist(self):
Distribution.from_name('does-not-exist')

def test_package_not_found_mentions_metadata(self):
"""
When a package is not found, that could indicate that the
packgae is not installed or that it is installed without
metadata. Ensure the exception mentions metadata to help
guide users toward the cause. See #124.
"""
# When a package is not found, that could indicate that the
# packgae is not installed or that it is installed without
# metadata. Ensure the exception mentions metadata to help
# guide users toward the cause. See #124.
with self.assertRaises(PackageNotFoundError) as ctx:
Distribution.from_name('does-not-exist')

Expand Down Expand Up @@ -90,10 +88,8 @@ def pkg_with_dashes(site_dir):
return 'my-pkg'

def test_dashes_in_dist_name_found_as_underscores(self):
"""
For a package with a dash in the name, the dist-info metadata
uses underscores in the name. Ensure the metadata loads.
"""
# For a package with a dash in the name, the dist-info metadata
# uses underscores in the name. Ensure the metadata loads.
pkg_name = self.pkg_with_dashes(self.site_dir)
assert version(pkg_name) == '1.0'

Expand All @@ -111,9 +107,7 @@ def pkg_with_mixed_case(site_dir):
return 'CherryPy'

def test_dist_name_found_as_any_case(self):
"""
Ensure the metadata loads when queried with any case.
"""
# Ensure the metadata loads when queried with any case.
pkg_name = self.pkg_with_mixed_case(self.site_dir)
assert version(pkg_name) == '1.0'
assert version(pkg_name.lower()) == '1.0'
Expand Down Expand Up @@ -241,13 +235,11 @@ def test_repr(self):
assert "'name'" in repr(self.ep)

def test_hashable(self):
"""EntryPoints should be hashable"""
# EntryPoints should be hashable.
hash(self.ep)

def test_json_dump(self):
"""
json should not expect to be able to dump an EntryPoint
"""
# json should not expect to be able to dump an EntryPoint.
with self.assertRaises(Exception):
with warnings.catch_warnings(record=True):
json.dumps(self.ep)
Expand All @@ -259,9 +251,7 @@ def test_attr(self):
assert self.ep.attr is None

def test_sortable(self):
"""
EntryPoint objects are sortable, but result is undefined.
"""
# EntryPoint objects are sortable, but result is undefined.
sorted(
[
EntryPoint('b', 'val', 'group'),
Expand All @@ -274,10 +264,8 @@ class FileSystem(
fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder, unittest.TestCase
):
def test_unicode_dir_on_sys_path(self):
"""
Ensure a Unicode subdirectory of a directory on sys.path
does not crash.
"""
# Ensure a Unicode subdirectory of a directory on sys.path
# does not crash.
fixtures.build_files(
{self.unicode_filename(): {}},
prefix=self.site_dir,
Expand Down
32 changes: 12 additions & 20 deletions Lib/test/test_importlib/test_metadata_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,8 @@ def test_entry_points_distribution(self):
self.assertEqual(ep.dist.version, "1.0.0")

def test_entry_points_unique_packages(self):
"""
Entry points should only be exposed for the first package
on sys.path with a given name.
"""
# Entry points should only be exposed for the first package
# on sys.path with a given name.
alt_site_dir = self.fixtures.enter_context(fixtures.tempdir())
self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
alt_pkg = {
Expand Down Expand Up @@ -116,11 +114,9 @@ def test_entry_points_missing_group(self):
assert entry_points(group='missing') == ()

def test_entry_points_dict_construction(self):
"""
Prior versions of entry_points() returned simple lists and
allowed casting those lists into maps by name using ``dict()``.
Capture this now deprecated use-case.
"""
# Prior versions of entry_points() returned simple lists and
# allowed casting those lists into maps by name using ``dict()``.
# Capture this now deprecated use-case.
with warnings.catch_warnings(record=True) as caught:
warnings.filterwarnings("default", category=DeprecationWarning)
eps = dict(entry_points(group='entries'))
Expand All @@ -134,23 +130,19 @@ def test_entry_points_dict_construction(self):
assert "Construction of dict of EntryPoints is deprecated" in str(expected)

def test_entry_points_groups_getitem(self):
"""
Prior versions of entry_points() returned a dict. Ensure
that callers using '.__getitem__()' are supported but warned to
migrate.
"""
# Prior versions of entry_points() returned a dict. Ensure
# that callers using '.__getitem__()' are supported but warned to
# migrate.
with warnings.catch_warnings(record=True):
entry_points()['entries'] == entry_points(group='entries')

with self.assertRaises(KeyError):
entry_points()['missing']

def test_entry_points_groups_get(self):
"""
Prior versions of entry_points() returned a dict. Ensure
that callers using '.get()' are supported but warned to
migrate.
"""
# Prior versions of entry_points() returned a dict. Ensure
# that callers using '.get()' are supported but warned to
# migrate.
with warnings.catch_warnings(record=True):
entry_points().get('missing', 'default') == 'default'
entry_points().get('entries', 'default') == entry_points()['entries']
Expand Down Expand Up @@ -259,7 +251,7 @@ def test_find_distributions_specified_path(self):
assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists)

def test_distribution_at_pathlib(self):
"""Demonstrate how to load metadata direct from a directory."""
# Demonstrate how to load metadata direct from a directory.
dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info'
dist = Distribution.at(dist_info_path)
assert dist.version == '1.0.0'
Expand Down
8 changes: 3 additions & 5 deletions Lib/test/test_importlib/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ class PathDiskTests(PathTests, unittest.TestCase):
data = data01

def test_natural_path(self):
"""
Guarantee the internal implementation detail that
file-system-backed resources do not get the tempdir
treatment.
"""
# Guarantee the internal implementation detail that
# file-system-backed resources do not get the tempdir
# treatment.
with resources.path(self.data, 'utf-8.file') as path:
assert 'data' in str(path)

Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_importlib/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def test_open_file(self):
path.open()

def test_join_path(self):
print('test_join_path')
prefix = os.path.abspath(os.path.join(__file__, '..'))
data01 = os.path.join(prefix, 'data01')
path = MultiplexedPath(self.folder, data01)
Expand Down
Loading