Skip to content

bpo-26131: deprecate the use of load_module() #22905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
14 changes: 13 additions & 1 deletion Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ Optimizations
with ``gcc`` by up to 30%. See `this article
<https://developers.redhat.com/blog/2020/06/25/red-hat-enterprise-linux-8-2-brings-faster-python-3-8-run-speeds/>`_
for more details. (Contributed by Victor Stinner and Pablo Galindo in
:issue:`38980`)
:issue:`38980`.)


Deprecated
==========
Expand All @@ -284,6 +285,17 @@ Deprecated
as appropriate to help identify code which needs updating during
this transition.

* The various ``load_module()`` methods of :mod:`importlib` have been
documented as deprecated since Python 3.6, but will now also trigger
a :exc:`DeprecationWarning`. Use
:meth:`~importlib.abc.Loader.exec_module` instead.
(Contributed by Brett Cannon in :issue:`26131`.)

* The use of :meth:`~importlib.abc.Loader.load_module` by the import
system now triggers an :exc:`ImportWarning` as
:meth:`~importlib.abc.Loader.exec_module` is preferred.
(Contributed by Brett Cannon in :issue:`26131`.)


Removed
=======
Expand Down
1 change: 1 addition & 0 deletions Lib/importlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def load_module(self, fullname):
"""
if not hasattr(self, 'exec_module'):
raise ImportError
# Warning implemented in _load_module_shim().
return _bootstrap._load_module_shim(self, fullname)

def module_repr(self, module):
Expand Down
24 changes: 18 additions & 6 deletions Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
# reference any injected objects! This includes not only global code but also
# anything specified at the class level.

def _object_name(obj):
try:
return obj.__qualname__
except AttributeError:
return type(obj).__qualname__

# Bootstrap-related code ######################################################

_bootstrap_external = None
Expand Down Expand Up @@ -265,6 +271,9 @@ def _load_module_shim(self, fullname):
This method is deprecated. Use loader.exec_module instead.

"""
msg = ("the load_module() method is deprecated and slated for removal in "
"Python 3.12; use exec_module() instead")
_warnings.warn(msg, DeprecationWarning)
spec = spec_from_loader(fullname, self)
if fullname in sys.modules:
module = sys.modules[fullname]
Expand Down Expand Up @@ -605,9 +614,9 @@ def _exec(spec, module):
else:
_init_module_attrs(spec, module, override=True)
if not hasattr(spec.loader, 'exec_module'):
# (issue19713) Once BuiltinImporter and ExtensionFileLoader
# have exec_module() implemented, we can add a deprecation
# warning here.
msg = (f"{_object_name(spec.loader)}.exec_module() not found; "
"falling back to load_module()")
_warnings.warn(msg, ImportWarning)
spec.loader.load_module(name)
else:
spec.loader.exec_module(module)
Expand All @@ -620,9 +629,8 @@ def _exec(spec, module):


def _load_backward_compatible(spec):
# (issue19713) Once BuiltinImporter and ExtensionFileLoader
# have exec_module() implemented, we can add a deprecation
# warning here.
# It is assumed that all callers have been warned about using load_module()
# appropriately before calling this function.
try:
spec.loader.load_module(spec.name)
except:
Expand Down Expand Up @@ -661,6 +669,9 @@ def _load_unlocked(spec):
if spec.loader is not None:
# Not a namespace package.
if not hasattr(spec.loader, 'exec_module'):
msg = (f"{_object_name(spec.loader)}.exec_module() not found; "
"falling back to load_module()")
_warnings.warn(msg, ImportWarning)
return _load_backward_compatible(spec)

module = module_from_spec(spec)
Expand Down Expand Up @@ -844,6 +855,7 @@ def load_module(cls, fullname):
This method is deprecated. Use exec_module() instead.

"""
# Warning about deprecation implemented in _load_module_shim().
return _load_module_shim(cls, fullname)

@classmethod
Expand Down
6 changes: 4 additions & 2 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,8 @@ def exec_module(self, module):
_bootstrap._call_with_frames_removed(exec, code, module.__dict__)

def load_module(self, fullname):
"""This module is deprecated."""
"""This method is deprecated."""
# Warning implemented in _load_module_shim().
return _bootstrap._load_module_shim(self, fullname)


Expand Down Expand Up @@ -966,7 +967,7 @@ def load_module(self, fullname):
"""
# The only reason for this method is for the name check.
# Issue #14857: Avoid the zero-argument form of super so the implementation
# of that form can be updated without breaking the frozen module
# of that form can be updated without breaking the frozen module.
return super(FileLoader, self).load_module(fullname)

@_check_name
Expand Down Expand Up @@ -1216,6 +1217,7 @@ def load_module(self, fullname):
# The import system never calls this method.
_bootstrap._verbose_message('namespace module loaded with path {!r}',
self._path)
# Warning implemented in _load_module_shim().
return _bootstrap._load_module_shim(self, fullname)


Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_importlib/builtin/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sys
import types
import unittest
import warnings

@unittest.skipIf(util.BUILTINS.good_name is None, 'no reasonable builtin module')
class LoaderTests(abc.LoaderTests):
Expand All @@ -24,7 +25,9 @@ def verify(self, module):
self.assertIn(module.__name__, sys.modules)

def load_module(self, name):
return self.machinery.BuiltinImporter.load_module(name)
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return self.machinery.BuiltinImporter.load_module(name)

def test_module(self):
# Common case.
Expand Down
44 changes: 26 additions & 18 deletions Lib/test/test_importlib/extension/test_loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from warnings import catch_warnings
from .. import abc
from .. import util

Expand All @@ -7,6 +8,7 @@
import sys
import types
import unittest
import warnings
import importlib.util
import importlib
from test.support.script_helper import assert_python_failure
Expand All @@ -20,14 +22,18 @@ def setUp(self):
util.EXTENSIONS.file_path)

def load_module(self, fullname):
return self.loader.load_module(fullname)
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return self.loader.load_module(fullname)

def test_load_module_API(self):
# Test the default argument for load_module().
self.loader.load_module()
self.loader.load_module(None)
with self.assertRaises(ImportError):
self.load_module('XXX')
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
self.loader.load_module()
self.loader.load_module(None)
with self.assertRaises(ImportError):
self.load_module('XXX')

def test_equality(self):
other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
Expand Down Expand Up @@ -94,6 +100,21 @@ def setUp(self):
self.loader = self.machinery.ExtensionFileLoader(
self.name, self.spec.origin)

def load_module(self):
'''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'''
origin = self.spec.origin
loader = self.machinery.ExtensionFileLoader(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module

# No extension module as __init__ available for testing.
test_package = None

Expand Down Expand Up @@ -157,19 +178,6 @@ def test_try_registration(self):
with self.assertRaises(SystemError):
module.call_state_registration_func(2)

def load_module(self):
'''Load the module from the test extension'''
return self.loader.load_module(self.name)

def load_module_by_name(self, fullname):
'''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)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module

def test_load_submodule(self):
'''Test loading a simulated submodule'''
module = self.load_module_by_name('pkg.' + self.name)
Expand Down
20 changes: 12 additions & 8 deletions Lib/test/test_importlib/frozen/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,23 @@ def test_module_repr(self):
"<module '__hello__' (frozen)>")

def test_module_repr_indirect(self):
with util.uncache('__hello__'), captured_stdout():
module = self.machinery.FrozenImporter.load_module('__hello__')
self.assertEqual(repr(module),
"<module '__hello__' (frozen)>")
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
with util.uncache('__hello__'), captured_stdout():
module = self.machinery.FrozenImporter.load_module('__hello__')
self.assertEqual(repr(module),
"<module '__hello__' (frozen)>")

# No way to trigger an error in a frozen module.
test_state_after_failure = None

def test_unloadable(self):
assert self.machinery.FrozenImporter.find_module('_not_real') is None
with self.assertRaises(ImportError) as cm:
self.machinery.FrozenImporter.load_module('_not_real')
self.assertEqual(cm.exception.name, '_not_real')
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
assert self.machinery.FrozenImporter.find_module('_not_real') is None
with self.assertRaises(ImportError) as cm:
self.machinery.FrozenImporter.load_module('_not_real')
self.assertEqual(cm.exception.name, '_not_real')


(Frozen_LoaderTests,
Expand Down
39 changes: 22 additions & 17 deletions Lib/test/test_importlib/import_/test___loader__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import types
import unittest
import warnings

from .. import util

Expand Down Expand Up @@ -45,25 +46,29 @@ def load_module(self, fullname):
class LoaderAttributeTests:

def test___loader___missing(self):
module = types.ModuleType('blah')
try:
del module.__loader__
except AttributeError:
pass
loader = LoaderMock()
loader.module = module
with util.uncache('blah'), util.import_state(meta_path=[loader]):
module = self.__import__('blah')
self.assertEqual(loader, module.__loader__)
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
module = types.ModuleType('blah')
try:
del module.__loader__
except AttributeError:
pass
loader = LoaderMock()
loader.module = module
with util.uncache('blah'), util.import_state(meta_path=[loader]):
module = self.__import__('blah')
self.assertEqual(loader, module.__loader__)

def test___loader___is_None(self):
module = types.ModuleType('blah')
module.__loader__ = None
loader = LoaderMock()
loader.module = module
with util.uncache('blah'), util.import_state(meta_path=[loader]):
returned_module = self.__import__('blah')
self.assertEqual(loader, module.__loader__)
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
module = types.ModuleType('blah')
module.__loader__ = None
loader = LoaderMock()
loader.module = module
with util.uncache('blah'), util.import_state(meta_path=[loader]):
returned_module = self.__import__('blah')
self.assertEqual(loader, module.__loader__)


(Frozen_Tests,
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_importlib/import_/test___package__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ def __init__(self, parent):
class Using__package__PEP302(Using__package__):
mock_modules = util.mock_modules

def test_using___package__(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_using___package__()

def test_spec_fallback(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_spec_fallback()


(Frozen_UsingPackagePEP302,
Source_UsingPackagePEP302
Expand Down Expand Up @@ -155,6 +165,21 @@ def test_submodule(self):
class Setting__package__PEP302(Setting__package__, unittest.TestCase):
mock_modules = util.mock_modules

def test_top_level(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_top_level()

def test_package(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_package()

def test_submodule(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_submodule()

class Setting__package__PEP451(Setting__package__, unittest.TestCase):
mock_modules = util.mock_spec

Expand Down
31 changes: 31 additions & 0 deletions Lib/test/test_importlib/import_/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import types
import unittest
import warnings

PKG_NAME = 'fine'
SUBMOD_NAME = 'fine.bogus'
Expand Down Expand Up @@ -100,6 +101,36 @@ def test_blocked_fromlist(self):
class OldAPITests(APITest):
bad_finder_loader = BadLoaderFinder

def test_raises_ModuleNotFoundError(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_raises_ModuleNotFoundError()

def test_name_requires_rparition(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_name_requires_rparition()

def test_negative_level(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_negative_level()

def test_nonexistent_fromlist_entry(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_nonexistent_fromlist_entry()

def test_fromlist_load_error_propagates(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_fromlist_load_error_propagates

def test_blocked_fromlist(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
super().test_blocked_fromlist()


(Frozen_OldAPITests,
Source_OldAPITests
Expand Down
Loading