Skip to content
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

gh-97850: Remove deprecated functions from importlib.utils #97898

Merged
merged 2 commits into from Oct 7, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 1 addition & 53 deletions Doc/library/importlib.rst
Expand Up @@ -443,7 +443,7 @@ ABC hierarchy::
from the import. If the loader inserted a module and the load fails, it
must be removed by the loader from :data:`sys.modules`; modules already
in :data:`sys.modules` before the loader began execution should be left
alone (see :func:`importlib.util.module_for_loader`).
alone.

The loader should set several attributes on the module
(note that some of these attributes can change when a module is
Expand Down Expand Up @@ -1326,58 +1326,6 @@ an :term:`importer`.

.. versionadded:: 3.5

.. decorator:: module_for_loader

A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`
to handle selecting the proper
module object to load with. The decorated method is expected to have a call
signature taking two positional arguments
(e.g. ``load_module(self, module)``) for which the second argument
will be the module **object** to be used by the loader.
Note that the decorator will not work on static methods because of the
assumption of two arguments.

The decorated method will take in the **name** of the module to be loaded
as expected for a :term:`loader`. If the module is not found in
:data:`sys.modules` then a new one is constructed. Regardless of where the
module came from, :attr:`__loader__` set to **self** and :attr:`__package__`
is set based on what :meth:`importlib.abc.InspectLoader.is_package` returns
(if available). These attributes are set unconditionally to support
reloading.

If an exception is raised by the decorated method and a module was added to
:data:`sys.modules`, then the module will be removed to prevent a partially
initialized module from being in left in :data:`sys.modules`. If the module
was already in :data:`sys.modules` then it is left alone.

.. versionchanged:: 3.3
:attr:`__loader__` and :attr:`__package__` are automatically set
(when possible).

.. versionchanged:: 3.4
Set :attr:`__name__`, :attr:`__loader__` :attr:`__package__`
unconditionally to support reloading.

.. deprecated:: 3.4
The import machinery now directly performs all the functionality
provided by this function.

.. decorator:: set_loader

A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`
to set the :attr:`__loader__`
attribute on the returned module. If the attribute is already set the
decorator does nothing. It is assumed that the first positional argument to
the wrapped method (i.e. ``self``) is what :attr:`__loader__` should be set
to.

.. versionchanged:: 3.4
Set ``__loader__`` if set to ``None``, as if the attribute does not
exist.

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

.. function:: spec_from_loader(name, loader, *, origin=None, is_package=None)

A factory function for creating a :class:`~importlib.machinery.ModuleSpec`
Expand Down
87 changes: 0 additions & 87 deletions Lib/importlib/util.py
Expand Up @@ -11,12 +11,9 @@
from ._bootstrap_external import source_from_cache
from ._bootstrap_external import spec_from_file_location

from contextlib import contextmanager
import _imp
import functools
import sys
import types
import warnings


def source_hash(source_bytes):
Expand Down Expand Up @@ -115,90 +112,6 @@ def find_spec(name, package=None):
return spec


@contextmanager
def _module_to_load(name):
is_reload = name in sys.modules

module = sys.modules.get(name)
if not is_reload:
# This must be done before open() is called as the 'io' module
# implicitly imports 'locale' and would otherwise trigger an
# infinite loop.
module = type(sys)(name)
# This must be done before putting the module in sys.modules
# (otherwise an optimization shortcut in import.c becomes wrong)
module.__initializing__ = True
sys.modules[name] = module
try:
yield module
except Exception:
if not is_reload:
try:
del sys.modules[name]
except KeyError:
pass
finally:
module.__initializing__ = False


def set_loader(fxn):
"""Set __loader__ on the returned module.

This function is deprecated.

"""
@functools.wraps(fxn)
def set_loader_wrapper(self, *args, **kwargs):
warnings.warn('The import system now takes care of this automatically; '
'this decorator is slated for removal in Python 3.12',
DeprecationWarning, stacklevel=2)
module = fxn(self, *args, **kwargs)
if getattr(module, '__loader__', None) is None:
module.__loader__ = self
return module
return set_loader_wrapper


def module_for_loader(fxn):
"""Decorator to handle selecting the proper module for loaders.

The decorated function is passed the module to use instead of the module
name. The module passed in to the function is either from sys.modules if
it already exists or is a new module. If the module is new, then __name__
is set the first argument to the method, __loader__ is set to self, and
__package__ is set accordingly (if self.is_package() is defined) will be set
before it is passed to the decorated function (if self.is_package() does
not work for the module it will be set post-load).

If an exception is raised and the decorator created the module it is
subsequently removed from sys.modules.

The decorator assumes that the decorated function takes the module name as
the second argument.

"""
warnings.warn('The import system now takes care of this automatically; '
'this decorator is slated for removal in Python 3.12',
DeprecationWarning, stacklevel=2)
@functools.wraps(fxn)
def module_for_loader_wrapper(self, fullname, *args, **kwargs):
with _module_to_load(fullname) as module:
module.__loader__ = self
try:
is_package = self.is_package(fullname)
except (ImportError, AttributeError):
pass
else:
if is_package:
module.__package__ = fullname
else:
module.__package__ = fullname.rpartition('.')[0]
# If __package__ was not set above, __import__() will do it later.
return fxn(self, module, *args, **kwargs)

return module_for_loader_wrapper


class _LazyModule(types.ModuleType):

"""A subclass of the module type which triggers loading upon attribute access."""
Expand Down
8 changes: 1 addition & 7 deletions Lib/test/test_importlib/test_abc.py
Expand Up @@ -771,13 +771,7 @@ def verify_code(self, code_object):


class SourceOnlyLoaderTests(SourceLoaderTestHarness):

"""Test importlib.abc.SourceLoader for source-only loading.

Reload testing is subsumed by the tests for
importlib.util.module_for_loader.

"""
"""Test importlib.abc.SourceLoader for source-only loading."""

def test_get_source(self):
# Verify the source code is returned as a string.
Expand Down
48 changes: 0 additions & 48 deletions Lib/test/test_importlib/test_spec.py
Expand Up @@ -47,21 +47,6 @@ def exec_module(self, module):
module.eggs = self.EGGS


class LegacyLoader(TestLoader):

HAM = -1

with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)

frozen_util = util['Frozen']

@frozen_util.module_for_loader
def load_module(self, module):
module.ham = self.HAM
return module


class ModuleSpecTests:

def setUp(self):
Expand Down Expand Up @@ -302,26 +287,6 @@ def exec_module(self, module):
loaded = self.bootstrap._load(self.spec)
self.assertNotIn(self.spec.name, sys.modules)

def test_load_legacy(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
self.spec.loader = LegacyLoader()
with CleanImport(self.spec.name):
loaded = self.bootstrap._load(self.spec)

self.assertEqual(loaded.ham, -1)

def test_load_legacy_attributes(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
self.spec.loader = LegacyLoader()
with CleanImport(self.spec.name):
loaded = self.bootstrap._load(self.spec)

self.assertIs(loaded.__loader__, self.spec.loader)
self.assertEqual(loaded.__package__, self.spec.parent)
self.assertIs(loaded.__spec__, self.spec)

def test_load_legacy_attributes_immutable(self):
module = object()
with warnings.catch_warnings():
Expand Down Expand Up @@ -387,19 +352,6 @@ def test_reload_init_module_attrs(self):
self.assertFalse(hasattr(loaded, '__file__'))
self.assertFalse(hasattr(loaded, '__cached__'))

def test_reload_legacy(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", ImportWarning)
self.spec.loader = LegacyLoader()
with CleanImport(self.spec.name):
loaded = self.bootstrap._load(self.spec)
reloaded = self.bootstrap._exec(self.spec, loaded)
installed = sys.modules[self.spec.name]

self.assertEqual(loaded.ham, -1)
self.assertIs(reloaded, loaded)
self.assertIs(installed, loaded)


(Frozen_ModuleSpecMethodsTests,
Source_ModuleSpecMethodsTests
Expand Down