mirror of
https://github.com/python/cpython.git
synced 2025-09-27 10:50:04 +00:00
Issues #18088, 18089: Introduce
importlib.abc.Loader.init_module_attrs() and implement importlib.abc.InspectLoader.load_module(). The importlib.abc.Loader.init_module_attrs() method sets the various attributes on the module being loaded. It is done unconditionally to support reloading. Typically people used importlib.util.module_for_loader, but since that's a decorator there was no way to override it's actions, so init_module_attrs() came into existence to allow for overriding. This is also why module_for_loader is now pending deprecation (having its other use replaced by importlib.util.module_to_load). All of this allowed for importlib.abc.InspectLoader.load_module() to be implemented. At this point you can now implement a loader with nothing more than get_code() (which only requires get_source(); package support requires is_package()). Thanks to init_module_attrs() the implementation of load_module() is basically a context manager containing 2 methods calls, a call to exec(), and a return statement.
This commit is contained in:
parent
f1d7b11db9
commit
0dbb4c7f13
10 changed files with 3934 additions and 3637 deletions
|
@ -246,7 +246,7 @@ ABC hierarchy::
|
||||||
|
|
||||||
The loader should set several attributes on the module.
|
The loader should set several attributes on the module.
|
||||||
(Note that some of these attributes can change when a module is
|
(Note that some of these attributes can change when a module is
|
||||||
reloaded.)
|
reloaded; see :meth:`init_module_attrs`):
|
||||||
|
|
||||||
- :attr:`__name__`
|
- :attr:`__name__`
|
||||||
The name of the module.
|
The name of the module.
|
||||||
|
@ -289,6 +289,17 @@ ABC hierarchy::
|
||||||
.. versionchanged:: 3.4
|
.. versionchanged:: 3.4
|
||||||
Made optional instead of an abstractmethod.
|
Made optional instead of an abstractmethod.
|
||||||
|
|
||||||
|
.. method:: init_module_attrs(module)
|
||||||
|
|
||||||
|
Set the :attr:`__loader__` attribute on the module.
|
||||||
|
|
||||||
|
Subclasses overriding this method should set whatever appropriate
|
||||||
|
attributes it can, getting the module's name from :attr:`__name__` when
|
||||||
|
needed. All values should also be overridden so that reloading works as
|
||||||
|
expected.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
|
||||||
.. class:: ResourceLoader
|
.. class:: ResourceLoader
|
||||||
|
|
||||||
|
@ -363,6 +374,18 @@ ABC hierarchy::
|
||||||
|
|
||||||
.. versionadded:: 3.4
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
.. method:: init_module_attrs(module)
|
||||||
|
|
||||||
|
Set the :attr:`__package__` attribute and :attr:`__path__` attribute to
|
||||||
|
the empty list if appropriate along with what
|
||||||
|
:meth:`importlib.abc.Loader.init_module_attrs` sets.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
.. method:: load_module(fullname)
|
||||||
|
|
||||||
|
Implementation of :meth:`Loader.load_module`.
|
||||||
|
|
||||||
|
|
||||||
.. class:: ExecutionLoader
|
.. class:: ExecutionLoader
|
||||||
|
|
||||||
|
@ -383,6 +406,15 @@ ABC hierarchy::
|
||||||
.. versionchanged:: 3.4
|
.. versionchanged:: 3.4
|
||||||
Raises :exc:`ImportError` instead of :exc:`NotImplementedError`.
|
Raises :exc:`ImportError` instead of :exc:`NotImplementedError`.
|
||||||
|
|
||||||
|
.. method:: init_module_attrs(module)
|
||||||
|
|
||||||
|
Set :attr:`__file__` and if initializing a package then set
|
||||||
|
:attr:`__path__` to ``[os.path.dirname(__file__)]`` along with
|
||||||
|
all attributes set by
|
||||||
|
:meth:`importlib.abc.InspectLoader.init_module_attrs`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
|
||||||
.. class:: FileLoader(fullname, path)
|
.. class:: FileLoader(fullname, path)
|
||||||
|
|
||||||
|
@ -500,6 +532,14 @@ ABC hierarchy::
|
||||||
``__init__`` when the file extension is removed **and** the module name
|
``__init__`` when the file extension is removed **and** the module name
|
||||||
itself does not end in ``__init__``.
|
itself does not end in ``__init__``.
|
||||||
|
|
||||||
|
.. method:: init_module_attr(module)
|
||||||
|
|
||||||
|
Set :attr:`__cached__` using :func:`imp.cache_from_source`. Other
|
||||||
|
attributes set by
|
||||||
|
:meth:`importlib.abc.ExecutionLoader.init_module_attrs`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
|
||||||
:mod:`importlib.machinery` -- Importers and path hooks
|
:mod:`importlib.machinery` -- Importers and path hooks
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
@ -826,17 +866,18 @@ an :term:`importer`.
|
||||||
module from being in left in :data:`sys.modules`. If the module was already
|
module from being in left in :data:`sys.modules`. If the module was already
|
||||||
in :data:`sys.modules` then it is left alone.
|
in :data:`sys.modules` then it is left alone.
|
||||||
|
|
||||||
.. note::
|
|
||||||
:func:`module_to_load` subsumes the module management aspect of this
|
|
||||||
decorator.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
:attr:`__loader__` and :attr:`__package__` are automatically set
|
:attr:`__loader__` and :attr:`__package__` are automatically set
|
||||||
(when possible).
|
(when possible).
|
||||||
|
|
||||||
.. versionchanged:: 3.4
|
.. versionchanged:: 3.4
|
||||||
Set :attr:`__loader__` :attr:`__package__` unconditionally to support
|
Set :attr:`__name__`, :attr:`__loader__` :attr:`__package__`
|
||||||
reloading.
|
unconditionally to support reloading.
|
||||||
|
|
||||||
|
.. deprecated:: 3.4
|
||||||
|
For the benefit of :term:`loader` subclasses, please use
|
||||||
|
:func:`module_to_load` and
|
||||||
|
:meth:`importlib.abc.Loader.init_module_attrs` instead.
|
||||||
|
|
||||||
.. decorator:: set_loader
|
.. decorator:: set_loader
|
||||||
|
|
||||||
|
@ -849,7 +890,8 @@ an :term:`importer`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
As this decorator sets :attr:`__loader__` after loading the module, it is
|
As this decorator sets :attr:`__loader__` after loading the module, it is
|
||||||
recommended to use :func:`module_for_loader` instead when appropriate.
|
recommended to use :meth:`importlib.abc.Loader.init_module_attrs` instead
|
||||||
|
when appropriate.
|
||||||
|
|
||||||
.. versionchanged:: 3.4
|
.. versionchanged:: 3.4
|
||||||
Set ``__loader__`` if set to ``None``, as if the attribute does not
|
Set ``__loader__`` if set to ``None``, as if the attribute does not
|
||||||
|
@ -862,4 +904,5 @@ an :term:`importer`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
As this decorator sets :attr:`__package__` after loading the module, it is
|
As this decorator sets :attr:`__package__` after loading the module, it is
|
||||||
recommended to use :func:`module_for_loader` instead when appropriate.
|
recommended to use :meth:`importlib.abc.Loader.init_module_attrs` instead
|
||||||
|
when appropriate.
|
||||||
|
|
|
@ -232,7 +232,10 @@ Deprecated functions and types of the C API
|
||||||
Deprecated features
|
Deprecated features
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
* None yet.
|
* :func:`importlib.util.module_for_loader` is pending deprecation. Using
|
||||||
|
:func:`importlib.util.module_to_load` and
|
||||||
|
:meth:`importlib.abc.Loader.init_module_attrs` allows subclasses of a loader
|
||||||
|
to more easily customize module loading.
|
||||||
|
|
||||||
|
|
||||||
Porting to Python 3.4
|
Porting to Python 3.4
|
||||||
|
|
|
@ -538,6 +538,32 @@ def module_to_load(name, *, reset_name=True):
|
||||||
return _ModuleManager(name, reset_name=reset_name)
|
return _ModuleManager(name, reset_name=reset_name)
|
||||||
|
|
||||||
|
|
||||||
|
def _init_package_attrs(loader, module):
|
||||||
|
"""Set __package__ and __path__ based on what loader.is_package() says."""
|
||||||
|
name = module.__name__
|
||||||
|
try:
|
||||||
|
is_package = loader.is_package(name)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if is_package:
|
||||||
|
module.__package__ = name
|
||||||
|
module.__path__ = []
|
||||||
|
else:
|
||||||
|
module.__package__ = name.rpartition('.')[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _init_file_attrs(loader, module):
|
||||||
|
"""Set __file__ and __path__ based on loader.get_filename()."""
|
||||||
|
try:
|
||||||
|
module.__file__ = loader.get_filename(module.__name__)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if module.__name__ == module.__package__:
|
||||||
|
module.__path__.append(_path_split(module.__file__)[0])
|
||||||
|
|
||||||
|
|
||||||
def set_package(fxn):
|
def set_package(fxn):
|
||||||
"""Set __package__ on the returned module."""
|
"""Set __package__ on the returned module."""
|
||||||
def set_package_wrapper(*args, **kwargs):
|
def set_package_wrapper(*args, **kwargs):
|
||||||
|
@ -562,42 +588,6 @@ def set_loader(fxn):
|
||||||
return set_loader_wrapper
|
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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
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)
|
|
||||||
_wrap(module_for_loader_wrapper, fxn)
|
|
||||||
return module_for_loader_wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def _check_name(method):
|
def _check_name(method):
|
||||||
"""Decorator to verify that the module being requested matches the one the
|
"""Decorator to verify that the module being requested matches the one the
|
||||||
loader can handle.
|
loader can handle.
|
||||||
|
@ -904,24 +894,31 @@ class _LoaderBasics:
|
||||||
tail_name = fullname.rpartition('.')[2]
|
tail_name = fullname.rpartition('.')[2]
|
||||||
return filename_base == '__init__' and tail_name != '__init__'
|
return filename_base == '__init__' and tail_name != '__init__'
|
||||||
|
|
||||||
@module_for_loader
|
def init_module_attrs(self, module):
|
||||||
def _load_module(self, module, *, sourceless=False):
|
"""Set various attributes on the module.
|
||||||
"""Helper for load_module able to handle either source or sourceless
|
|
||||||
loading."""
|
ExecutionLoader.init_module_attrs() is used to set __loader__,
|
||||||
name = module.__name__
|
__package__, __file__, and optionally __path__. The __cached__ attribute
|
||||||
code_object = self.get_code(name)
|
is set using imp.cache_from_source() and __file__.
|
||||||
module.__file__ = self.get_filename(name)
|
"""
|
||||||
if not sourceless:
|
module.__loader__ = self # Loader
|
||||||
|
_init_package_attrs(self, module) # InspectLoader
|
||||||
|
_init_file_attrs(self, module) # ExecutionLoader
|
||||||
|
if hasattr(module, '__file__'): # SourceLoader
|
||||||
try:
|
try:
|
||||||
module.__cached__ = cache_from_source(module.__file__)
|
module.__cached__ = cache_from_source(module.__file__)
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
module.__cached__ = module.__file__
|
pass
|
||||||
else:
|
|
||||||
module.__cached__ = module.__file__
|
def load_module(self, fullname):
|
||||||
if self.is_package(name):
|
"""Load the specified module into sys.modules and return it."""
|
||||||
module.__path__ = [_path_split(module.__file__)[0]]
|
with module_to_load(fullname) as module:
|
||||||
# __package__ and __loader set by @module_for_loader.
|
self.init_module_attrs(module)
|
||||||
_call_with_frames_removed(exec, code_object, module.__dict__)
|
code = self.get_code(fullname)
|
||||||
|
if code is None:
|
||||||
|
raise ImportError('cannot load module {!r} when get_code() '
|
||||||
|
'returns None'.format(fullname))
|
||||||
|
_call_with_frames_removed(exec, code, module.__dict__)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
@ -1046,16 +1043,6 @@ class SourceLoader(_LoaderBasics):
|
||||||
pass
|
pass
|
||||||
return code_object
|
return code_object
|
||||||
|
|
||||||
def load_module(self, fullname):
|
|
||||||
"""Concrete implementation of Loader.load_module.
|
|
||||||
|
|
||||||
Requires ExecutionLoader.get_filename and ResourceLoader.get_data to be
|
|
||||||
implemented to load source code. Use of bytecode is dictated by whether
|
|
||||||
get_code uses/writes bytecode.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._load_module(fullname)
|
|
||||||
|
|
||||||
|
|
||||||
class FileLoader:
|
class FileLoader:
|
||||||
|
|
||||||
|
@ -1133,8 +1120,9 @@ class SourcelessFileLoader(FileLoader, _LoaderBasics):
|
||||||
|
|
||||||
"""Loader which handles sourceless file imports."""
|
"""Loader which handles sourceless file imports."""
|
||||||
|
|
||||||
def load_module(self, fullname):
|
def init_module_attrs(self, module):
|
||||||
return self._load_module(fullname, sourceless=True)
|
super().init_module_attrs(module)
|
||||||
|
module.__cached__ = module.__file__
|
||||||
|
|
||||||
def get_code(self, fullname):
|
def get_code(self, fullname):
|
||||||
path = self.get_filename(fullname)
|
path = self.get_filename(fullname)
|
||||||
|
@ -1259,11 +1247,12 @@ class NamespaceLoader:
|
||||||
def module_repr(cls, module):
|
def module_repr(cls, module):
|
||||||
return "<module '{}' (namespace)>".format(module.__name__)
|
return "<module '{}' (namespace)>".format(module.__name__)
|
||||||
|
|
||||||
@module_for_loader
|
def load_module(self, fullname):
|
||||||
def load_module(self, module):
|
|
||||||
"""Load a namespace module."""
|
"""Load a namespace module."""
|
||||||
_verbose_message('namespace module loaded with path {!r}', self._path)
|
_verbose_message('namespace module loaded with path {!r}', self._path)
|
||||||
|
with module_to_load(fullname) as module:
|
||||||
module.__path__ = self._path
|
module.__path__ = self._path
|
||||||
|
module.__package__ = fullname
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,6 @@ except ImportError as exc:
|
||||||
raise
|
raise
|
||||||
_frozen_importlib = None
|
_frozen_importlib = None
|
||||||
import abc
|
import abc
|
||||||
import imp
|
|
||||||
import marshal
|
|
||||||
import sys
|
|
||||||
import tokenize
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
def _register(abstract_cls, *classes):
|
def _register(abstract_cls, *classes):
|
||||||
|
@ -113,6 +108,10 @@ class Loader(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def init_module_attrs(self, module):
|
||||||
|
"""Set the module's __loader__ attribute."""
|
||||||
|
module.__loader__ = self
|
||||||
|
|
||||||
|
|
||||||
class ResourceLoader(Loader):
|
class ResourceLoader(Loader):
|
||||||
|
|
||||||
|
@ -177,6 +176,17 @@ class InspectLoader(Loader):
|
||||||
argument should be where the data was retrieved (when applicable)."""
|
argument should be where the data was retrieved (when applicable)."""
|
||||||
return compile(data, path, 'exec', dont_inherit=True)
|
return compile(data, path, 'exec', dont_inherit=True)
|
||||||
|
|
||||||
|
def init_module_attrs(self, module):
|
||||||
|
"""Initialize the __loader__ and __package__ attributes of the module.
|
||||||
|
|
||||||
|
The name of the module is gleaned from module.__name__. The __package__
|
||||||
|
attribute is set based on self.is_package().
|
||||||
|
"""
|
||||||
|
super().init_module_attrs(module)
|
||||||
|
_bootstrap._init_package_attrs(self, module)
|
||||||
|
|
||||||
|
load_module = _bootstrap._LoaderBasics.load_module
|
||||||
|
|
||||||
_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter,
|
_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter,
|
||||||
machinery.ExtensionFileLoader)
|
machinery.ExtensionFileLoader)
|
||||||
|
|
||||||
|
@ -215,6 +225,18 @@ class ExecutionLoader(InspectLoader):
|
||||||
else:
|
else:
|
||||||
return self.source_to_code(source, path)
|
return self.source_to_code(source, path)
|
||||||
|
|
||||||
|
def init_module_attrs(self, module):
|
||||||
|
"""Initialize the module's attributes.
|
||||||
|
|
||||||
|
It is assumed that the module's name has been set on module.__name__.
|
||||||
|
It is also assumed that any path returned by self.get_filename() uses
|
||||||
|
(one of) the operating system's path separator(s) to separate filenames
|
||||||
|
from directories in order to set __path__ intelligently.
|
||||||
|
InspectLoader.init_module_attrs() sets __loader__ and __package__.
|
||||||
|
"""
|
||||||
|
super().init_module_attrs(module)
|
||||||
|
_bootstrap._init_file_attrs(self, module)
|
||||||
|
|
||||||
|
|
||||||
class FileLoader(_bootstrap.FileLoader, ResourceLoader, ExecutionLoader):
|
class FileLoader(_bootstrap.FileLoader, ResourceLoader, ExecutionLoader):
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
"""Utility code for constructing importers, etc."""
|
"""Utility code for constructing importers, etc."""
|
||||||
|
|
||||||
from ._bootstrap import module_to_load
|
from ._bootstrap import module_to_load
|
||||||
from ._bootstrap import module_for_loader
|
|
||||||
from ._bootstrap import set_loader
|
from ._bootstrap import set_loader
|
||||||
from ._bootstrap import set_package
|
from ._bootstrap import set_package
|
||||||
from ._bootstrap import _resolve_name
|
from ._bootstrap import _resolve_name
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
def resolve_name(name, package):
|
def resolve_name(name, package):
|
||||||
"""Resolve a relative module name to an absolute one."""
|
"""Resolve a relative module name to an absolute one."""
|
||||||
|
@ -20,3 +22,44 @@ def resolve_name(name, package):
|
||||||
break
|
break
|
||||||
level += 1
|
level += 1
|
||||||
return _resolve_name(name[level:], package, level)
|
return _resolve_name(name[level:], package, level)
|
||||||
|
|
||||||
|
|
||||||
|
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('To make it easier for subclasses, please use '
|
||||||
|
'importlib.util.module_to_load() and '
|
||||||
|
'importlib.abc.Loader.init_module_attrs()',
|
||||||
|
PendingDeprecationWarning, 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
|
|
@ -15,7 +15,7 @@ import stat
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from test.support import make_legacy_pyc
|
from test.support import make_legacy_pyc, unload
|
||||||
|
|
||||||
|
|
||||||
class SimpleTest(unittest.TestCase):
|
class SimpleTest(unittest.TestCase):
|
||||||
|
@ -26,23 +26,13 @@ class SimpleTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_load_module_API(self):
|
def test_load_module_API(self):
|
||||||
# If fullname is not specified that assume self.name is desired.
|
class Tester(importlib.abc.FileLoader):
|
||||||
class TesterMixin(importlib.abc.Loader):
|
def get_source(self, _): return 'attr = 42'
|
||||||
def load_module(self, fullname): return fullname
|
def is_package(self, _): return False
|
||||||
def module_repr(self, module): return '<module>'
|
|
||||||
|
|
||||||
class Tester(importlib.abc.FileLoader, TesterMixin):
|
loader = Tester('blah', 'blah.py')
|
||||||
def get_code(self, _): pass
|
self.addCleanup(unload, 'blah')
|
||||||
def get_source(self, _): pass
|
module = loader.load_module() # Should not raise an exception.
|
||||||
def is_package(self, _): pass
|
|
||||||
|
|
||||||
name = 'mod_name'
|
|
||||||
loader = Tester(name, 'some_path')
|
|
||||||
self.assertEqual(name, loader.load_module())
|
|
||||||
self.assertEqual(name, loader.load_module(None))
|
|
||||||
self.assertEqual(name, loader.load_module(name))
|
|
||||||
with self.assertRaises(ImportError):
|
|
||||||
loader.load_module(loader.name + 'XXX')
|
|
||||||
|
|
||||||
def test_get_filename_API(self):
|
def test_get_filename_API(self):
|
||||||
# If fullname is not set then assume self.path is desired.
|
# If fullname is not set then assume self.path is desired.
|
||||||
|
@ -473,13 +463,6 @@ class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
|
||||||
self._test_non_code_marshal(del_source=True)
|
self._test_non_code_marshal(del_source=True)
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
|
||||||
from test.support import run_unittest
|
|
||||||
run_unittest(SimpleTest,
|
|
||||||
SourceLoaderBadBytecodeTest,
|
|
||||||
SourcelessLoaderBadBytecodeTest
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
test_main()
|
unittest.main()
|
||||||
|
|
|
@ -2,12 +2,14 @@ import importlib
|
||||||
from importlib import abc
|
from importlib import abc
|
||||||
from importlib import machinery
|
from importlib import machinery
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import imp
|
import imp
|
||||||
import inspect
|
import inspect
|
||||||
import io
|
import io
|
||||||
import marshal
|
import marshal
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from test import support
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
@ -198,6 +200,15 @@ class ExecutionLoaderDefaultsTests(unittest.TestCase):
|
||||||
with self.assertRaises(ImportError):
|
with self.assertRaises(ImportError):
|
||||||
self.ins.get_filename('blah')
|
self.ins.get_filename('blah')
|
||||||
|
|
||||||
|
##### Loader concrete methods ##################################################
|
||||||
|
class LoaderConcreteMethodTests(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_init_module_attrs(self):
|
||||||
|
loader = LoaderSubclass()
|
||||||
|
module = imp.new_module('blah')
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertEqual(module.__loader__, loader)
|
||||||
|
|
||||||
|
|
||||||
##### InspectLoader concrete methods ###########################################
|
##### InspectLoader concrete methods ###########################################
|
||||||
class InspectLoaderSourceToCodeTests(unittest.TestCase):
|
class InspectLoaderSourceToCodeTests(unittest.TestCase):
|
||||||
|
@ -269,6 +280,93 @@ class InspectLoaderGetCodeTests(unittest.TestCase):
|
||||||
loader.get_code('blah')
|
loader.get_code('blah')
|
||||||
|
|
||||||
|
|
||||||
|
class InspectLoaderInitModuleTests(unittest.TestCase):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mock_is_package(return_value):
|
||||||
|
return mock.patch.object(InspectLoaderSubclass, 'is_package',
|
||||||
|
return_value=return_value)
|
||||||
|
|
||||||
|
def init_module_attrs(self, name):
|
||||||
|
loader = InspectLoaderSubclass()
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertEqual(module.__loader__, loader)
|
||||||
|
return module
|
||||||
|
|
||||||
|
def test_package(self):
|
||||||
|
# If a package, then __package__ == __name__, __path__ == []
|
||||||
|
with self.mock_is_package(True):
|
||||||
|
name = 'blah'
|
||||||
|
module = self.init_module_attrs(name)
|
||||||
|
self.assertEqual(module.__package__, name)
|
||||||
|
self.assertEqual(module.__path__, [])
|
||||||
|
|
||||||
|
def test_toplevel(self):
|
||||||
|
# If a module is top-level, __package__ == ''
|
||||||
|
with self.mock_is_package(False):
|
||||||
|
name = 'blah'
|
||||||
|
module = self.init_module_attrs(name)
|
||||||
|
self.assertEqual(module.__package__, '')
|
||||||
|
|
||||||
|
def test_submodule(self):
|
||||||
|
# If a module is contained within a package then set __package__ to the
|
||||||
|
# package name.
|
||||||
|
with self.mock_is_package(False):
|
||||||
|
name = 'pkg.mod'
|
||||||
|
module = self.init_module_attrs(name)
|
||||||
|
self.assertEqual(module.__package__, 'pkg')
|
||||||
|
|
||||||
|
def test_is_package_ImportError(self):
|
||||||
|
# If is_package() raises ImportError, __package__ should be None and
|
||||||
|
# __path__ should not be set.
|
||||||
|
with self.mock_is_package(False) as mocked_method:
|
||||||
|
mocked_method.side_effect = ImportError
|
||||||
|
name = 'mod'
|
||||||
|
module = self.init_module_attrs(name)
|
||||||
|
self.assertIsNone(module.__package__)
|
||||||
|
self.assertFalse(hasattr(module, '__path__'))
|
||||||
|
|
||||||
|
|
||||||
|
class InspectLoaderLoadModuleTests(unittest.TestCase):
|
||||||
|
|
||||||
|
"""Test InspectLoader.load_module()."""
|
||||||
|
|
||||||
|
module_name = 'blah'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
support.unload(self.module_name)
|
||||||
|
self.addCleanup(support.unload, self.module_name)
|
||||||
|
|
||||||
|
def mock_get_code(self):
|
||||||
|
return mock.patch.object(InspectLoaderSubclass, 'get_code')
|
||||||
|
|
||||||
|
def test_get_code_ImportError(self):
|
||||||
|
# If get_code() raises ImportError, it should propagate.
|
||||||
|
with self.mock_get_code() as mocked_get_code:
|
||||||
|
mocked_get_code.side_effect = ImportError
|
||||||
|
with self.assertRaises(ImportError):
|
||||||
|
loader = InspectLoaderSubclass()
|
||||||
|
loader.load_module(self.module_name)
|
||||||
|
|
||||||
|
def test_get_code_None(self):
|
||||||
|
# If get_code() returns None, raise ImportError.
|
||||||
|
with self.mock_get_code() as mocked_get_code:
|
||||||
|
mocked_get_code.return_value = None
|
||||||
|
with self.assertRaises(ImportError):
|
||||||
|
loader = InspectLoaderSubclass()
|
||||||
|
loader.load_module(self.module_name)
|
||||||
|
|
||||||
|
def test_module_returned(self):
|
||||||
|
# The loaded module should be returned.
|
||||||
|
code = compile('attr = 42', '<string>', 'exec')
|
||||||
|
with self.mock_get_code() as mocked_get_code:
|
||||||
|
mocked_get_code.return_value = code
|
||||||
|
loader = InspectLoaderSubclass()
|
||||||
|
module = loader.load_module(self.module_name)
|
||||||
|
self.assertEqual(module, sys.modules[self.module_name])
|
||||||
|
|
||||||
|
|
||||||
##### ExecutionLoader concrete methods #########################################
|
##### ExecutionLoader concrete methods #########################################
|
||||||
class ExecutionLoaderGetCodeTests(unittest.TestCase):
|
class ExecutionLoaderGetCodeTests(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -327,6 +425,69 @@ class ExecutionLoaderGetCodeTests(unittest.TestCase):
|
||||||
self.assertEqual(module.attr, 42)
|
self.assertEqual(module.attr, 42)
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionLoaderInitModuleTests(unittest.TestCase):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def mock_methods(is_package, filename):
|
||||||
|
is_package_manager = InspectLoaderInitModuleTests.mock_is_package(is_package)
|
||||||
|
get_filename_manager = mock.patch.object(ExecutionLoaderSubclass,
|
||||||
|
'get_filename', return_value=filename)
|
||||||
|
with is_package_manager as mock_is_package:
|
||||||
|
with get_filename_manager as mock_get_filename:
|
||||||
|
yield {'is_package': mock_is_package,
|
||||||
|
'get_filename': mock_get_filename}
|
||||||
|
|
||||||
|
def test_toplevel(self):
|
||||||
|
# Verify __loader__, __file__, and __package__; no __path__.
|
||||||
|
name = 'blah'
|
||||||
|
path = os.path.join('some', 'path', '{}.py'.format(name))
|
||||||
|
with self.mock_methods(False, path):
|
||||||
|
loader = ExecutionLoaderSubclass()
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertIs(module.__loader__, loader)
|
||||||
|
self.assertEqual(module.__file__, path)
|
||||||
|
self.assertEqual(module.__package__, '')
|
||||||
|
self.assertFalse(hasattr(module, '__path__'))
|
||||||
|
|
||||||
|
def test_package(self):
|
||||||
|
# Verify __loader__, __file__, __package__, and __path__.
|
||||||
|
name = 'pkg'
|
||||||
|
path = os.path.join('some', 'pkg', '__init__.py')
|
||||||
|
with self.mock_methods(True, path):
|
||||||
|
loader = ExecutionLoaderSubclass()
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertIs(module.__loader__, loader)
|
||||||
|
self.assertEqual(module.__file__, path)
|
||||||
|
self.assertEqual(module.__package__, 'pkg')
|
||||||
|
self.assertEqual(module.__path__, [os.path.dirname(path)])
|
||||||
|
|
||||||
|
def test_submodule(self):
|
||||||
|
# Verify __package__ and not __path__; test_toplevel() takes care of
|
||||||
|
# other attributes.
|
||||||
|
name = 'pkg.submodule'
|
||||||
|
path = os.path.join('some', 'pkg', 'submodule.py')
|
||||||
|
with self.mock_methods(False, path):
|
||||||
|
loader = ExecutionLoaderSubclass()
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertEqual(module.__package__, 'pkg')
|
||||||
|
self.assertEqual(module.__file__, path)
|
||||||
|
self.assertFalse(hasattr(module, '__path__'))
|
||||||
|
|
||||||
|
def test_get_filename_ImportError(self):
|
||||||
|
# If get_filename() raises ImportError, don't set __file__.
|
||||||
|
name = 'blah'
|
||||||
|
path = 'blah.py'
|
||||||
|
with self.mock_methods(False, path) as mocked_methods:
|
||||||
|
mocked_methods['get_filename'].side_effect = ImportError
|
||||||
|
loader = ExecutionLoaderSubclass()
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertFalse(hasattr(module, '__file__'))
|
||||||
|
|
||||||
|
|
||||||
##### SourceLoader concrete methods ############################################
|
##### SourceLoader concrete methods ############################################
|
||||||
class SourceOnlyLoaderMock(abc.SourceLoader):
|
class SourceOnlyLoaderMock(abc.SourceLoader):
|
||||||
|
@ -621,6 +782,47 @@ class SourceLoaderGetSourceTests(unittest.TestCase):
|
||||||
self.assertEqual(mock.get_source(name), expect)
|
self.assertEqual(mock.get_source(name), expect)
|
||||||
|
|
||||||
|
|
||||||
|
class SourceLoaderInitModuleAttrTests(unittest.TestCase):
|
||||||
|
|
||||||
|
"""Tests for importlib.abc.SourceLoader.init_module_attrs()."""
|
||||||
|
|
||||||
|
def test_init_module_attrs(self):
|
||||||
|
# If __file__ set, __cached__ == imp.cached_from_source(__file__).
|
||||||
|
name = 'blah'
|
||||||
|
path = 'blah.py'
|
||||||
|
loader = SourceOnlyLoaderMock(path)
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertEqual(module.__loader__, loader)
|
||||||
|
self.assertEqual(module.__package__, '')
|
||||||
|
self.assertEqual(module.__file__, path)
|
||||||
|
self.assertEqual(module.__cached__, imp.cache_from_source(path))
|
||||||
|
|
||||||
|
@mock.patch('importlib._bootstrap.cache_from_source')
|
||||||
|
def test_cache_from_source_NotImplementedError(self, mock_cache_from_source):
|
||||||
|
# If imp.cache_from_source() raises NotImplementedError don't set
|
||||||
|
# __cached__.
|
||||||
|
mock_cache_from_source.side_effect = NotImplementedError
|
||||||
|
name = 'blah'
|
||||||
|
path = 'blah.py'
|
||||||
|
loader = SourceOnlyLoaderMock(path)
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertEqual(module.__file__, path)
|
||||||
|
self.assertFalse(hasattr(module, '__cached__'))
|
||||||
|
|
||||||
|
def test_no_get_filename(self):
|
||||||
|
# No __file__, no __cached__.
|
||||||
|
with mock.patch.object(SourceOnlyLoaderMock, 'get_filename') as mocked:
|
||||||
|
mocked.side_effect = ImportError
|
||||||
|
name = 'blah'
|
||||||
|
loader = SourceOnlyLoaderMock('blah.py')
|
||||||
|
module = imp.new_module(name)
|
||||||
|
loader.init_module_attrs(module)
|
||||||
|
self.assertFalse(hasattr(module, '__file__'))
|
||||||
|
self.assertFalse(hasattr(module, '__cached__'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import sys
|
||||||
from test import support
|
from test import support
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
class ModuleToLoadTests(unittest.TestCase):
|
class ModuleToLoadTests(unittest.TestCase):
|
||||||
|
@ -72,14 +73,27 @@ class ModuleForLoaderTests(unittest.TestCase):
|
||||||
|
|
||||||
"""Tests for importlib.util.module_for_loader."""
|
"""Tests for importlib.util.module_for_loader."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def module_for_loader(func):
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', PendingDeprecationWarning)
|
||||||
|
return util.module_for_loader(func)
|
||||||
|
|
||||||
|
def test_warning(self):
|
||||||
|
# Should raise a PendingDeprecationWarning when used.
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('error', PendingDeprecationWarning)
|
||||||
|
with self.assertRaises(PendingDeprecationWarning):
|
||||||
|
func = util.module_for_loader(lambda x: x)
|
||||||
|
|
||||||
def return_module(self, name):
|
def return_module(self, name):
|
||||||
fxn = util.module_for_loader(lambda self, module: module)
|
fxn = self.module_for_loader(lambda self, module: module)
|
||||||
return fxn(self, name)
|
return fxn(self, name)
|
||||||
|
|
||||||
def raise_exception(self, name):
|
def raise_exception(self, name):
|
||||||
def to_wrap(self, module):
|
def to_wrap(self, module):
|
||||||
raise ImportError
|
raise ImportError
|
||||||
fxn = util.module_for_loader(to_wrap)
|
fxn = self.module_for_loader(to_wrap)
|
||||||
try:
|
try:
|
||||||
fxn(self, name)
|
fxn(self, name)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -100,7 +114,7 @@ class ModuleForLoaderTests(unittest.TestCase):
|
||||||
class FakeLoader:
|
class FakeLoader:
|
||||||
def is_package(self, name):
|
def is_package(self, name):
|
||||||
return True
|
return True
|
||||||
@util.module_for_loader
|
@self.module_for_loader
|
||||||
def load_module(self, module):
|
def load_module(self, module):
|
||||||
return module
|
return module
|
||||||
name = 'a.b.c'
|
name = 'a.b.c'
|
||||||
|
@ -134,7 +148,7 @@ class ModuleForLoaderTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_decorator_attrs(self):
|
def test_decorator_attrs(self):
|
||||||
def fxn(self, module): pass
|
def fxn(self, module): pass
|
||||||
wrapped = util.module_for_loader(fxn)
|
wrapped = self.module_for_loader(fxn)
|
||||||
self.assertEqual(wrapped.__name__, fxn.__name__)
|
self.assertEqual(wrapped.__name__, fxn.__name__)
|
||||||
self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
|
self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
|
||||||
|
|
||||||
|
@ -160,7 +174,7 @@ class ModuleForLoaderTests(unittest.TestCase):
|
||||||
self._pkg = is_package
|
self._pkg = is_package
|
||||||
def is_package(self, name):
|
def is_package(self, name):
|
||||||
return self._pkg
|
return self._pkg
|
||||||
@util.module_for_loader
|
@self.module_for_loader
|
||||||
def load_module(self, module):
|
def load_module(self, module):
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,12 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #18089: Implement importlib.abc.InspectLoader.load_module.
|
||||||
|
|
||||||
|
- Issue #18088: Introduce importlib.abc.Loader.init_module_attrs for setting
|
||||||
|
module attributes. Leads to the pending deprecation of
|
||||||
|
importlib.util.module_for_loader.
|
||||||
|
|
||||||
- Issue #17403: urllib.parse.robotparser normalizes the urls before adding to
|
- Issue #17403: urllib.parse.robotparser normalizes the urls before adding to
|
||||||
ruleline. This helps in handling certain types invalid urls in a conservative
|
ruleline. This helps in handling certain types invalid urls in a conservative
|
||||||
manner. Patch contributed by Mher Movsisyan.
|
manner. Patch contributed by Mher Movsisyan.
|
||||||
|
@ -104,7 +110,7 @@ Library
|
||||||
- Issue #18070: Have importlib.util.module_for_loader() set attributes
|
- Issue #18070: Have importlib.util.module_for_loader() set attributes
|
||||||
unconditionally in order to properly support reloading.
|
unconditionally in order to properly support reloading.
|
||||||
|
|
||||||
- Add importlib.util.module_to_load to return a context manager to provide the
|
- Added importlib.util.module_to_load to return a context manager to provide the
|
||||||
proper module object to load.
|
proper module object to load.
|
||||||
|
|
||||||
- Issue #18025: Fixed a segfault in io.BufferedIOBase.readinto() when raw
|
- Issue #18025: Fixed a segfault in io.BufferedIOBase.readinto() when raw
|
||||||
|
|
7036
Python/importlib.h
7036
Python/importlib.h
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue