Skip to content

gh-94619: Remove long deprecated methods module_repr() and load_module() #94624

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

Merged
merged 2 commits into from
Aug 5, 2022
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
14 changes: 0 additions & 14 deletions Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -493,20 +493,6 @@ ABC hierarchy::
other responsibilities of :meth:`load_module` when
:meth:`exec_module` is implemented.

.. method:: module_repr(module)

A legacy method which when implemented calculates and returns the given
module's representation, as a string. The module type's default
:meth:`__repr__` will use the result of this method as appropriate.

.. versionadded:: 3.3

.. versionchanged:: 3.4
Made optional instead of an abstractmethod.

.. deprecated:: 3.4
The import machinery now takes care of this automatically.


.. include:: importlib.resources.abc.rst

Expand Down
20 changes: 4 additions & 16 deletions Doc/reference/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -676,22 +676,10 @@ Here are the exact rules used:

* Otherwise, just use the module's ``__name__`` in the repr.

.. versionchanged:: 3.4
Use of :meth:`loader.module_repr() <importlib.abc.Loader.module_repr>`
has been deprecated and the module spec is now used by the import
machinery to generate a module repr.

For backward compatibility with Python 3.3, the module repr will be
generated by calling the loader's
:meth:`~importlib.abc.Loader.module_repr` method, if defined, before
trying either approach described above. However, the method is deprecated.

.. versionchanged:: 3.10

Calling :meth:`~importlib.abc.Loader.module_repr` now occurs after trying to
use a module's ``__spec__`` attribute but before falling back on
``__file__``. Use of :meth:`~importlib.abc.Loader.module_repr` is slated to
stop in Python 3.12.
.. versionchanged:: 3.12
Use of :meth:`module_repr`, having been deprecated since Python 3.4, was
removed in Python 3.12 and is no longer called during the resolution of a
module's repr.

.. _pyc-invalidation:

Expand Down
14 changes: 0 additions & 14 deletions Lib/importlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,3 @@ def load_module(self, fullname):
raise ImportError
# Warning implemented in _load_module_shim().
return _bootstrap._load_module_shim(self, fullname)

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

Used by the module type when the method does not raise
NotImplementedError.

This method is deprecated.

"""
warnings.warn("importlib.abc.Loader.module_repr() is deprecated and "
"slated for removal in Python 3.12", DeprecationWarning)
# The exception will cause ModuleType.__repr__ to ignore this method.
raise NotImplementedError
6 changes: 0 additions & 6 deletions Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,6 @@ def _module_repr(module):
loader = getattr(module, '__loader__', None)
if spec := getattr(module, "__spec__", None):
return _module_repr_from_spec(spec)
elif hasattr(loader, 'module_repr'):
try:
return loader.module_repr(module)
except Exception:
pass
# Fall through to a catch-all which always succeeds.
try:
name = module.__name__
Expand Down Expand Up @@ -582,7 +577,6 @@ def module_from_spec(spec):

def _module_repr_from_spec(spec):
"""Return the repr to use for the module."""
# We mostly replicate _module_repr() using the spec attributes.
name = '?' if spec.name is None else spec.name
if spec.origin is None:
if spec.loader is None:
Expand Down
13 changes: 1 addition & 12 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -1339,22 +1339,11 @@ def append(self, item):

# This class is actually exposed publicly in a namespace package's __loader__
# attribute, so it should be available through a non-private name.
# https://bugs.python.org/issue35673
# https://github.com/python/cpython/issues/92054
class NamespaceLoader:
def __init__(self, name, path, path_finder):
self._path = _NamespacePath(name, path, path_finder)

@staticmethod
def module_repr(module):
"""Return repr for the module.

The method is deprecated. The import machinery does the job itself.

"""
_warnings.warn("NamespaceLoader.module_repr() is deprecated and "
"slated for removal in Python 3.12", DeprecationWarning)
return '<module {!r} (namespace)>'.format(module.__name__)

def is_package(self, fullname):
return True

Expand Down
8 changes: 0 additions & 8 deletions Lib/test/test_importlib/frozen/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,6 @@ def test_lacking_parent(self):
expected=value))
self.assertEqual(output, 'Hello world!\n')

def test_module_repr(self):
name = '__hello__'
module, output = self.exec_module(name)
with deprecated():
repr_str = self.machinery.FrozenImporter.module_repr(module)
self.assertEqual(repr_str,
"<module '__hello__' (frozen)>")

def test_module_repr_indirect(self):
name = '__hello__'
module, output = self.exec_module(name)
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_importlib/source/test_file_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ class Tester(self.abc.FileLoader):
def get_code(self, _): pass
def get_source(self, _): pass
def is_package(self, _): pass
def module_repr(self, _): pass

path = 'some_path'
name = 'some_name'
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_importlib/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,6 @@ def test_module_repr(self):
mod = types.ModuleType('blah')
with warnings.catch_warnings():
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.
Expand Down
7 changes: 0 additions & 7 deletions Lib/test/test_importlib/test_namespace_pkgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,6 @@ def test_cant_import_other(self):
with self.assertRaises(ImportError):
import foo.two

def test_module_repr(self):
import foo.one
with warnings.catch_warnings():
warnings.simplefilter("ignore")
self.assertEqual(foo.__spec__.loader.module_repr(foo),
"<module 'foo' (namespace)>")


class DynamicPathNamespacePackage(NamespacePackageTest):
paths = ['portion1']
Expand Down
95 changes: 0 additions & 95 deletions Lib/test/test_importlib/test_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,101 +407,6 @@ def test_reload_legacy(self):
machinery=machinery)


class ModuleReprTests:

@property
def bootstrap(self):
return self.init._bootstrap

def setUp(self):
self.module = type(os)('spam')
self.spec = self.machinery.ModuleSpec('spam', TestLoader())

def test_module___loader___module_repr(self):
class Loader:
def module_repr(self, module):
return '<delicious {}>'.format(module.__name__)
self.module.__loader__ = Loader()
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr, '<delicious spam>')

def test_module___loader___module_repr_bad(self):
class Loader(TestLoader):
def module_repr(self, module):
raise Exception
self.module.__loader__ = Loader()
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr,
'<module {!r} (<TestLoader object>)>'.format('spam'))

def test_module___spec__(self):
origin = 'in a hole, in the ground'
self.spec.origin = origin
self.module.__spec__ = self.spec
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr, '<module {!r} ({})>'.format('spam', origin))

def test_module___spec___location(self):
location = 'in_a_galaxy_far_far_away.py'
self.spec.origin = location
self.spec._set_fileattr = True
self.module.__spec__ = self.spec
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr,
'<module {!r} from {!r}>'.format('spam', location))

def test_module___spec___no_origin(self):
self.spec.loader = TestLoader()
self.module.__spec__ = self.spec
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr,
'<module {!r} (<TestLoader object>)>'.format('spam'))

def test_module___spec___no_origin_no_loader(self):
self.spec.loader = None
self.module.__spec__ = self.spec
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr, '<module {!r}>'.format('spam'))

def test_module_no_name(self):
del self.module.__name__
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr, '<module {!r}>'.format('?'))

def test_module_with_file(self):
filename = 'e/i/e/i/o/spam.py'
self.module.__file__ = filename
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr,
'<module {!r} from {!r}>'.format('spam', filename))

def test_module_no_file(self):
self.module.__loader__ = TestLoader()
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr,
'<module {!r} (<TestLoader object>)>'.format('spam'))

def test_module_no_file_no_loader(self):
modrepr = self.bootstrap._module_repr(self.module)

self.assertEqual(modrepr, '<module {!r}>'.format('spam'))


(Frozen_ModuleReprTests,
Source_ModuleReprTests
) = test_util.test_both(ModuleReprTests, init=init, util=util,
machinery=machinery)


class FactoryTests:

def setUp(self):
Expand Down
10 changes: 5 additions & 5 deletions Lib/test/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import sys
ModuleType = type(sys)


class FullLoader:
@classmethod
def module_repr(cls, m):
return "<module '{}' (crafted)>".format(m.__name__)
pass


class BareLoader:
pass
Expand Down Expand Up @@ -236,7 +236,7 @@ def test_module_repr_with_full_loader(self):
# Yes, a class not an instance.
m.__loader__ = FullLoader
self.assertEqual(
repr(m), "<module 'foo' (crafted)>")
repr(m), "<module 'foo' (<class 'test.test_module.FullLoader'>)>")

def test_module_repr_with_bare_loader_and_filename(self):
# Because the loader has no module_repr(), use the file name.
Expand All @@ -252,7 +252,7 @@ def test_module_repr_with_full_loader_and_filename(self):
# Yes, a class not an instance.
m.__loader__ = FullLoader
m.__file__ = '/tmp/foo.py'
self.assertEqual(repr(m), "<module 'foo' (crafted)>")
self.assertEqual(repr(m), "<module 'foo' from '/tmp/foo.py'>")

def test_module_repr_builtin(self):
self.assertEqual(repr(sys), "<module 'sys' (built-in)>")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove the long-deprecated `module_repr()` from `importlib`.