mirror of https://github.com/python/cpython
Issue #15641: Clean up deprecated classes from importlib
Patch by Taras Lyapun.
This commit is contained in:
parent
bcbf4036c9
commit
90a654b1dd
|
@ -133,8 +133,6 @@ ABC hierarchy::
|
||||||
+-- ExecutionLoader --+
|
+-- ExecutionLoader --+
|
||||||
+-- FileLoader
|
+-- FileLoader
|
||||||
+-- SourceLoader
|
+-- SourceLoader
|
||||||
+-- PyLoader (deprecated)
|
|
||||||
+-- PyPycLoader (deprecated)
|
|
||||||
|
|
||||||
|
|
||||||
.. class:: Finder
|
.. class:: Finder
|
||||||
|
@ -431,142 +429,6 @@ ABC hierarchy::
|
||||||
itself does not end in ``__init__``.
|
itself does not end in ``__init__``.
|
||||||
|
|
||||||
|
|
||||||
.. class:: PyLoader
|
|
||||||
|
|
||||||
An abstract base class inheriting from
|
|
||||||
:class:`ExecutionLoader` and
|
|
||||||
:class:`ResourceLoader` designed to ease the loading of
|
|
||||||
Python source modules (bytecode is not handled; see
|
|
||||||
:class:`SourceLoader` for a source/bytecode ABC). A subclass
|
|
||||||
implementing this ABC will only need to worry about exposing how the source
|
|
||||||
code is stored; all other details for loading Python source code will be
|
|
||||||
handled by the concrete implementations of key methods.
|
|
||||||
|
|
||||||
.. deprecated:: 3.2
|
|
||||||
This class has been deprecated in favor of :class:`SourceLoader` and is
|
|
||||||
slated for removal in Python 3.4. See below for how to create a
|
|
||||||
subclass that is compatible with Python 3.1 onwards.
|
|
||||||
|
|
||||||
If compatibility with Python 3.1 is required, then use the following idiom
|
|
||||||
to implement a subclass that will work with Python 3.1 onwards (make sure
|
|
||||||
to implement :meth:`ExecutionLoader.get_filename`)::
|
|
||||||
|
|
||||||
try:
|
|
||||||
from importlib.abc import SourceLoader
|
|
||||||
except ImportError:
|
|
||||||
from importlib.abc import PyLoader as SourceLoader
|
|
||||||
|
|
||||||
|
|
||||||
class CustomLoader(SourceLoader):
|
|
||||||
def get_filename(self, fullname):
|
|
||||||
"""Return the path to the source file."""
|
|
||||||
# Implement ...
|
|
||||||
|
|
||||||
def source_path(self, fullname):
|
|
||||||
"""Implement source_path in terms of get_filename."""
|
|
||||||
try:
|
|
||||||
return self.get_filename(fullname)
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def is_package(self, fullname):
|
|
||||||
"""Implement is_package by looking for an __init__ file
|
|
||||||
name as returned by get_filename."""
|
|
||||||
filename = os.path.basename(self.get_filename(fullname))
|
|
||||||
return os.path.splitext(filename)[0] == '__init__'
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: source_path(fullname)
|
|
||||||
|
|
||||||
An abstract method that returns the path to the source code for a
|
|
||||||
module. Should return ``None`` if there is no source code.
|
|
||||||
Raises :exc:`ImportError` if the loader knows it cannot handle the
|
|
||||||
module.
|
|
||||||
|
|
||||||
.. method:: get_filename(fullname)
|
|
||||||
|
|
||||||
A concrete implementation of
|
|
||||||
:meth:`importlib.abc.ExecutionLoader.get_filename` that
|
|
||||||
relies on :meth:`source_path`. If :meth:`source_path` returns
|
|
||||||
``None``, then :exc:`ImportError` is raised.
|
|
||||||
|
|
||||||
.. method:: load_module(fullname)
|
|
||||||
|
|
||||||
A concrete implementation of :meth:`importlib.abc.Loader.load_module`
|
|
||||||
that loads Python source code. All needed information comes from the
|
|
||||||
abstract methods required by this ABC. The only pertinent assumption
|
|
||||||
made by this method is that when loading a package
|
|
||||||
:attr:`__path__` is set to ``[os.path.dirname(__file__)]``.
|
|
||||||
|
|
||||||
.. method:: get_code(fullname)
|
|
||||||
|
|
||||||
A concrete implementation of
|
|
||||||
:meth:`importlib.abc.InspectLoader.get_code` that creates code objects
|
|
||||||
from Python source code, by requesting the source code (using
|
|
||||||
:meth:`source_path` and :meth:`get_data`) and compiling it with the
|
|
||||||
built-in :func:`compile` function.
|
|
||||||
|
|
||||||
.. method:: get_source(fullname)
|
|
||||||
|
|
||||||
A concrete implementation of
|
|
||||||
:meth:`importlib.abc.InspectLoader.get_source`. Uses
|
|
||||||
:meth:`importlib.abc.ResourceLoader.get_data` and :meth:`source_path`
|
|
||||||
to get the source code. It tries to guess the source encoding using
|
|
||||||
:func:`tokenize.detect_encoding`.
|
|
||||||
|
|
||||||
|
|
||||||
.. class:: PyPycLoader
|
|
||||||
|
|
||||||
An abstract base class inheriting from :class:`PyLoader`.
|
|
||||||
This ABC is meant to help in creating loaders that support both Python
|
|
||||||
source and bytecode.
|
|
||||||
|
|
||||||
.. deprecated:: 3.2
|
|
||||||
This class has been deprecated in favor of :class:`SourceLoader` and to
|
|
||||||
properly support :pep:`3147`. If compatibility is required with
|
|
||||||
Python 3.1, implement both :class:`SourceLoader` and :class:`PyLoader`;
|
|
||||||
instructions on how to do so are included in the documentation for
|
|
||||||
:class:`PyLoader`. Do note that this solution will not support
|
|
||||||
sourceless/bytecode-only loading; only source *and* bytecode loading.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
|
||||||
Updated to parse (but not use) the new source size field in bytecode
|
|
||||||
files when reading and to write out the field properly when writing.
|
|
||||||
|
|
||||||
.. method:: source_mtime(fullname)
|
|
||||||
|
|
||||||
An abstract method which returns the modification time for the source
|
|
||||||
code of the specified module. The modification time should be an
|
|
||||||
integer. If there is no source code, return ``None``. If the
|
|
||||||
module cannot be found then :exc:`ImportError` is raised.
|
|
||||||
|
|
||||||
.. method:: bytecode_path(fullname)
|
|
||||||
|
|
||||||
An abstract method which returns the path to the bytecode for the
|
|
||||||
specified module, if it exists. It returns ``None``
|
|
||||||
if no bytecode exists (yet).
|
|
||||||
Raises :exc:`ImportError` if the loader knows it cannot handle the
|
|
||||||
module.
|
|
||||||
|
|
||||||
.. method:: get_filename(fullname)
|
|
||||||
|
|
||||||
A concrete implementation of
|
|
||||||
:meth:`ExecutionLoader.get_filename` that relies on
|
|
||||||
:meth:`PyLoader.source_path` and :meth:`bytecode_path`.
|
|
||||||
If :meth:`source_path` returns a path, then that value is returned.
|
|
||||||
Else if :meth:`bytecode_path` returns a path, that path will be
|
|
||||||
returned. If a path is not available from both methods,
|
|
||||||
:exc:`ImportError` is raised.
|
|
||||||
|
|
||||||
.. method:: write_bytecode(fullname, bytecode)
|
|
||||||
|
|
||||||
An abstract method which has the loader write *bytecode* for future
|
|
||||||
use. If the bytecode is written, return ``True``. Return
|
|
||||||
``False`` if the bytecode could not be written. This method
|
|
||||||
should not be called if :data:`sys.dont_write_bytecode` is true.
|
|
||||||
The *bytecode* argument should be a bytes string or bytes array.
|
|
||||||
|
|
||||||
|
|
||||||
:mod:`importlib.machinery` -- Importers and path hooks
|
:mod:`importlib.machinery` -- Importers and path hooks
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -225,180 +225,3 @@ class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
_register(SourceLoader, machinery.SourceFileLoader)
|
_register(SourceLoader, machinery.SourceFileLoader)
|
||||||
|
|
||||||
class PyLoader(SourceLoader):
|
|
||||||
|
|
||||||
"""Implement the deprecated PyLoader ABC in terms of SourceLoader.
|
|
||||||
|
|
||||||
This class has been deprecated! It is slated for removal in Python 3.4.
|
|
||||||
If compatibility with Python 3.1 is not needed then implement the
|
|
||||||
SourceLoader ABC instead of this class. If Python 3.1 compatibility is
|
|
||||||
needed, then use the following idiom to have a single class that is
|
|
||||||
compatible with Python 3.1 onwards::
|
|
||||||
|
|
||||||
try:
|
|
||||||
from importlib.abc import SourceLoader
|
|
||||||
except ImportError:
|
|
||||||
from importlib.abc import PyLoader as SourceLoader
|
|
||||||
|
|
||||||
|
|
||||||
class CustomLoader(SourceLoader):
|
|
||||||
def get_filename(self, fullname):
|
|
||||||
# Implement ...
|
|
||||||
|
|
||||||
def source_path(self, fullname):
|
|
||||||
'''Implement source_path in terms of get_filename.'''
|
|
||||||
try:
|
|
||||||
return self.get_filename(fullname)
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def is_package(self, fullname):
|
|
||||||
filename = os.path.basename(self.get_filename(fullname))
|
|
||||||
return os.path.splitext(filename)[0] == '__init__'
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def is_package(self, fullname):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def source_path(self, fullname):
|
|
||||||
"""Abstract method. Accepts a str module name and returns the path to
|
|
||||||
the source code for the module."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_filename(self, fullname):
|
|
||||||
"""Implement get_filename in terms of source_path.
|
|
||||||
|
|
||||||
As get_filename should only return a source file path there is no
|
|
||||||
chance of the path not existing but loading still being possible, so
|
|
||||||
ImportError should propagate instead of being turned into returning
|
|
||||||
None.
|
|
||||||
|
|
||||||
"""
|
|
||||||
warnings.warn("importlib.abc.PyLoader is deprecated and is "
|
|
||||||
"slated for removal in Python 3.4; "
|
|
||||||
"use SourceLoader instead. "
|
|
||||||
"See the importlib documentation on how to be "
|
|
||||||
"compatible with Python 3.1 onwards.",
|
|
||||||
DeprecationWarning)
|
|
||||||
path = self.source_path(fullname)
|
|
||||||
if path is None:
|
|
||||||
raise ImportError(name=fullname)
|
|
||||||
else:
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
class PyPycLoader(PyLoader):
|
|
||||||
|
|
||||||
"""Abstract base class to assist in loading source and bytecode by
|
|
||||||
requiring only back-end storage methods to be implemented.
|
|
||||||
|
|
||||||
This class has been deprecated! Removal is slated for Python 3.4. Implement
|
|
||||||
the SourceLoader ABC instead. If Python 3.1 compatibility is needed, see
|
|
||||||
PyLoader.
|
|
||||||
|
|
||||||
The methods get_code, get_source, and load_module are implemented for the
|
|
||||||
user.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_filename(self, fullname):
|
|
||||||
"""Return the source or bytecode file path."""
|
|
||||||
path = self.source_path(fullname)
|
|
||||||
if path is not None:
|
|
||||||
return path
|
|
||||||
path = self.bytecode_path(fullname)
|
|
||||||
if path is not None:
|
|
||||||
return path
|
|
||||||
raise ImportError("no source or bytecode path available for "
|
|
||||||
"{0!r}".format(fullname), name=fullname)
|
|
||||||
|
|
||||||
def get_code(self, fullname):
|
|
||||||
"""Get a code object from source or bytecode."""
|
|
||||||
warnings.warn("importlib.abc.PyPycLoader is deprecated and slated for "
|
|
||||||
"removal in Python 3.4; use SourceLoader instead. "
|
|
||||||
"If Python 3.1 compatibility is required, see the "
|
|
||||||
"latest documentation for PyLoader.",
|
|
||||||
DeprecationWarning)
|
|
||||||
source_timestamp = self.source_mtime(fullname)
|
|
||||||
# Try to use bytecode if it is available.
|
|
||||||
bytecode_path = self.bytecode_path(fullname)
|
|
||||||
if bytecode_path:
|
|
||||||
data = self.get_data(bytecode_path)
|
|
||||||
try:
|
|
||||||
magic = data[:4]
|
|
||||||
if len(magic) < 4:
|
|
||||||
raise ImportError(
|
|
||||||
"bad magic number in {}".format(fullname),
|
|
||||||
name=fullname, path=bytecode_path)
|
|
||||||
raw_timestamp = data[4:8]
|
|
||||||
if len(raw_timestamp) < 4:
|
|
||||||
raise EOFError("bad timestamp in {}".format(fullname))
|
|
||||||
pyc_timestamp = _bootstrap._r_long(raw_timestamp)
|
|
||||||
raw_source_size = data[8:12]
|
|
||||||
if len(raw_source_size) != 4:
|
|
||||||
raise EOFError("bad file size in {}".format(fullname))
|
|
||||||
# Source size is unused as the ABC does not provide a way to
|
|
||||||
# get the size of the source ahead of reading it.
|
|
||||||
bytecode = data[12:]
|
|
||||||
# Verify that the magic number is valid.
|
|
||||||
if imp.get_magic() != magic:
|
|
||||||
raise ImportError(
|
|
||||||
"bad magic number in {}".format(fullname),
|
|
||||||
name=fullname, path=bytecode_path)
|
|
||||||
# Verify that the bytecode is not stale (only matters when
|
|
||||||
# there is source to fall back on.
|
|
||||||
if source_timestamp:
|
|
||||||
if pyc_timestamp < source_timestamp:
|
|
||||||
raise ImportError("bytecode is stale", name=fullname,
|
|
||||||
path=bytecode_path)
|
|
||||||
except (ImportError, EOFError):
|
|
||||||
# If source is available give it a shot.
|
|
||||||
if source_timestamp is not None:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
# Bytecode seems fine, so try to use it.
|
|
||||||
return marshal.loads(bytecode)
|
|
||||||
elif source_timestamp is None:
|
|
||||||
raise ImportError("no source or bytecode available to create code "
|
|
||||||
"object for {0!r}".format(fullname),
|
|
||||||
name=fullname)
|
|
||||||
# Use the source.
|
|
||||||
source_path = self.source_path(fullname)
|
|
||||||
if source_path is None:
|
|
||||||
message = "a source path must exist to load {0}".format(fullname)
|
|
||||||
raise ImportError(message, name=fullname)
|
|
||||||
source = self.get_data(source_path)
|
|
||||||
code_object = compile(source, source_path, 'exec', dont_inherit=True)
|
|
||||||
# Generate bytecode and write it out.
|
|
||||||
if not sys.dont_write_bytecode:
|
|
||||||
data = bytearray(imp.get_magic())
|
|
||||||
data.extend(_bootstrap._w_long(source_timestamp))
|
|
||||||
data.extend(_bootstrap._w_long(len(source) & 0xFFFFFFFF))
|
|
||||||
data.extend(marshal.dumps(code_object))
|
|
||||||
self.write_bytecode(fullname, data)
|
|
||||||
return code_object
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def source_mtime(self, fullname):
|
|
||||||
"""Abstract method. Accepts a str filename and returns an int
|
|
||||||
modification time for the source of the module."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def bytecode_path(self, fullname):
|
|
||||||
"""Abstract method. Accepts a str filename and returns the str pathname
|
|
||||||
to the bytecode for the module."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def write_bytecode(self, fullname, bytecode):
|
|
||||||
"""Abstract method. Accepts a str filename and bytes object
|
|
||||||
representing the bytecode for the module. Returns a boolean
|
|
||||||
representing whether the bytecode was written or not."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
|
@ -70,483 +70,9 @@ class SourceLoaderMock(SourceOnlyLoaderMock):
|
||||||
return path == self.bytecode_path
|
return path == self.bytecode_path
|
||||||
|
|
||||||
|
|
||||||
class PyLoaderMock(abc.PyLoader):
|
|
||||||
|
|
||||||
# Globals that should be defined for all modules.
|
|
||||||
source = (b"_ = '::'.join([__name__, __file__, __package__, "
|
|
||||||
b"repr(__loader__)])")
|
|
||||||
|
|
||||||
def __init__(self, data):
|
|
||||||
"""Take a dict of 'module_name: path' pairings.
|
|
||||||
|
|
||||||
Paths should have no file extension, allowing packages to be denoted by
|
|
||||||
ending in '__init__'.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.module_paths = data
|
|
||||||
self.path_to_module = {val:key for key,val in data.items()}
|
|
||||||
|
|
||||||
def get_data(self, path):
|
|
||||||
if path not in self.path_to_module:
|
|
||||||
raise IOError
|
|
||||||
return self.source
|
|
||||||
|
|
||||||
def is_package(self, name):
|
|
||||||
filename = os.path.basename(self.get_filename(name))
|
|
||||||
return os.path.splitext(filename)[0] == '__init__'
|
|
||||||
|
|
||||||
def source_path(self, name):
|
|
||||||
try:
|
|
||||||
return self.module_paths[name]
|
|
||||||
except KeyError:
|
|
||||||
raise ImportError
|
|
||||||
|
|
||||||
def get_filename(self, name):
|
|
||||||
"""Silence deprecation warning."""
|
|
||||||
with warnings.catch_warnings(record=True) as w:
|
|
||||||
warnings.simplefilter("always")
|
|
||||||
path = super().get_filename(name)
|
|
||||||
assert len(w) == 1
|
|
||||||
assert issubclass(w[0].category, DeprecationWarning)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def module_repr(self):
|
|
||||||
return '<module>'
|
|
||||||
|
|
||||||
|
|
||||||
class PyLoaderCompatMock(PyLoaderMock):
|
|
||||||
|
|
||||||
"""Mock that matches what is suggested to have a loader that is compatible
|
|
||||||
from Python 3.1 onwards."""
|
|
||||||
|
|
||||||
def get_filename(self, fullname):
|
|
||||||
try:
|
|
||||||
return self.module_paths[fullname]
|
|
||||||
except KeyError:
|
|
||||||
raise ImportError
|
|
||||||
|
|
||||||
def source_path(self, fullname):
|
|
||||||
try:
|
|
||||||
return self.get_filename(fullname)
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
|
|
||||||
|
|
||||||
default_mtime = 1
|
|
||||||
|
|
||||||
def __init__(self, source, bc={}):
|
|
||||||
"""Initialize mock.
|
|
||||||
|
|
||||||
'bc' is a dict keyed on a module's name. The value is dict with
|
|
||||||
possible keys of 'path', 'mtime', 'magic', and 'bc'. Except for 'path',
|
|
||||||
each of those keys control if any part of created bytecode is to
|
|
||||||
deviate from default values.
|
|
||||||
|
|
||||||
"""
|
|
||||||
super().__init__(source)
|
|
||||||
self.module_bytecode = {}
|
|
||||||
self.path_to_bytecode = {}
|
|
||||||
self.bytecode_to_path = {}
|
|
||||||
for name, data in bc.items():
|
|
||||||
self.path_to_bytecode[data['path']] = name
|
|
||||||
self.bytecode_to_path[name] = data['path']
|
|
||||||
magic = data.get('magic', imp.get_magic())
|
|
||||||
mtime = importlib._w_long(data.get('mtime', self.default_mtime))
|
|
||||||
source_size = importlib._w_long(len(self.source) & 0xFFFFFFFF)
|
|
||||||
if 'bc' in data:
|
|
||||||
bc = data['bc']
|
|
||||||
else:
|
|
||||||
bc = self.compile_bc(name)
|
|
||||||
self.module_bytecode[name] = magic + mtime + source_size + bc
|
|
||||||
|
|
||||||
def compile_bc(self, name):
|
|
||||||
source_path = self.module_paths.get(name, '<test>') or '<test>'
|
|
||||||
code = compile(self.source, source_path, 'exec')
|
|
||||||
return marshal.dumps(code)
|
|
||||||
|
|
||||||
def source_mtime(self, name):
|
|
||||||
if name in self.module_paths:
|
|
||||||
return self.default_mtime
|
|
||||||
elif name in self.module_bytecode:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
raise ImportError
|
|
||||||
|
|
||||||
def bytecode_path(self, name):
|
|
||||||
try:
|
|
||||||
return self.bytecode_to_path[name]
|
|
||||||
except KeyError:
|
|
||||||
if name in self.module_paths:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
raise ImportError
|
|
||||||
|
|
||||||
def write_bytecode(self, name, bytecode):
|
|
||||||
self.module_bytecode[name] = bytecode
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_data(self, path):
|
|
||||||
if path in self.path_to_module:
|
|
||||||
return super().get_data(path)
|
|
||||||
elif path in self.path_to_bytecode:
|
|
||||||
name = self.path_to_bytecode[path]
|
|
||||||
return self.module_bytecode[name]
|
|
||||||
else:
|
|
||||||
raise IOError
|
|
||||||
|
|
||||||
def is_package(self, name):
|
|
||||||
try:
|
|
||||||
return super().is_package(name)
|
|
||||||
except TypeError:
|
|
||||||
return '__init__' in self.bytecode_to_path[name]
|
|
||||||
|
|
||||||
def get_code(self, name):
|
|
||||||
with warnings.catch_warnings(record=True) as w:
|
|
||||||
warnings.simplefilter("always")
|
|
||||||
code_object = super().get_code(name)
|
|
||||||
assert len(w) == 1
|
|
||||||
assert issubclass(w[0].category, DeprecationWarning)
|
|
||||||
return code_object
|
|
||||||
|
|
||||||
class PyLoaderTests(testing_abc.LoaderTests):
|
|
||||||
|
|
||||||
"""Tests for importlib.abc.PyLoader."""
|
|
||||||
|
|
||||||
mocker = PyLoaderMock
|
|
||||||
|
|
||||||
def eq_attrs(self, ob, **kwargs):
|
|
||||||
for attr, val in kwargs.items():
|
|
||||||
found = getattr(ob, attr)
|
|
||||||
self.assertEqual(found, val,
|
|
||||||
"{} attribute: {} != {}".format(attr, found, val))
|
|
||||||
|
|
||||||
def test_module(self):
|
|
||||||
name = '<module>'
|
|
||||||
path = os.path.join('', 'path', 'to', 'module')
|
|
||||||
mock = self.mocker({name: path})
|
|
||||||
with util.uncache(name):
|
|
||||||
module = mock.load_module(name)
|
|
||||||
self.assertIn(name, sys.modules)
|
|
||||||
self.eq_attrs(module, __name__=name, __file__=path, __package__='',
|
|
||||||
__loader__=mock)
|
|
||||||
self.assertTrue(not hasattr(module, '__path__'))
|
|
||||||
return mock, name
|
|
||||||
|
|
||||||
def test_package(self):
|
|
||||||
name = '<pkg>'
|
|
||||||
path = os.path.join('path', 'to', name, '__init__')
|
|
||||||
mock = self.mocker({name: path})
|
|
||||||
with util.uncache(name):
|
|
||||||
module = mock.load_module(name)
|
|
||||||
self.assertIn(name, sys.modules)
|
|
||||||
self.eq_attrs(module, __name__=name, __file__=path,
|
|
||||||
__path__=[os.path.dirname(path)], __package__=name,
|
|
||||||
__loader__=mock)
|
|
||||||
return mock, name
|
|
||||||
|
|
||||||
def test_lacking_parent(self):
|
|
||||||
name = 'pkg.mod'
|
|
||||||
path = os.path.join('path', 'to', 'pkg', 'mod')
|
|
||||||
mock = self.mocker({name: path})
|
|
||||||
with util.uncache(name):
|
|
||||||
module = mock.load_module(name)
|
|
||||||
self.assertIn(name, sys.modules)
|
|
||||||
self.eq_attrs(module, __name__=name, __file__=path, __package__='pkg',
|
|
||||||
__loader__=mock)
|
|
||||||
self.assertFalse(hasattr(module, '__path__'))
|
|
||||||
return mock, name
|
|
||||||
|
|
||||||
def test_module_reuse(self):
|
|
||||||
name = 'mod'
|
|
||||||
path = os.path.join('path', 'to', 'mod')
|
|
||||||
module = imp.new_module(name)
|
|
||||||
mock = self.mocker({name: path})
|
|
||||||
with util.uncache(name):
|
|
||||||
sys.modules[name] = module
|
|
||||||
loaded_module = mock.load_module(name)
|
|
||||||
self.assertIs(loaded_module, module)
|
|
||||||
self.assertIs(sys.modules[name], module)
|
|
||||||
return mock, name
|
|
||||||
|
|
||||||
def test_state_after_failure(self):
|
|
||||||
name = "mod"
|
|
||||||
module = imp.new_module(name)
|
|
||||||
module.blah = None
|
|
||||||
mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
|
|
||||||
mock.source = b"1/0"
|
|
||||||
with util.uncache(name):
|
|
||||||
sys.modules[name] = module
|
|
||||||
with self.assertRaises(ZeroDivisionError):
|
|
||||||
mock.load_module(name)
|
|
||||||
self.assertIs(sys.modules[name], module)
|
|
||||||
self.assertTrue(hasattr(module, 'blah'))
|
|
||||||
return mock
|
|
||||||
|
|
||||||
def test_unloadable(self):
|
|
||||||
name = "mod"
|
|
||||||
mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
|
|
||||||
mock.source = b"1/0"
|
|
||||||
with util.uncache(name):
|
|
||||||
with self.assertRaises(ZeroDivisionError):
|
|
||||||
mock.load_module(name)
|
|
||||||
self.assertNotIn(name, sys.modules)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
|
|
||||||
class PyLoaderCompatTests(PyLoaderTests):
|
|
||||||
|
|
||||||
"""Test that the suggested code to make a loader that is compatible from
|
|
||||||
Python 3.1 forward works."""
|
|
||||||
|
|
||||||
mocker = PyLoaderCompatMock
|
|
||||||
|
|
||||||
|
|
||||||
class PyLoaderInterfaceTests(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Tests for importlib.abc.PyLoader to make sure that when source_path()
|
|
||||||
doesn't return a path everything works as expected."""
|
|
||||||
|
|
||||||
def test_no_source_path(self):
|
|
||||||
# No source path should lead to ImportError.
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyLoaderMock({})
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError):
|
|
||||||
mock.load_module(name)
|
|
||||||
|
|
||||||
def test_source_path_is_None(self):
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyLoaderMock({name: None})
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError):
|
|
||||||
mock.load_module(name)
|
|
||||||
|
|
||||||
def test_get_filename_with_source_path(self):
|
|
||||||
# get_filename() should return what source_path() returns.
|
|
||||||
name = 'mod'
|
|
||||||
path = os.path.join('path', 'to', 'source')
|
|
||||||
mock = PyLoaderMock({name: path})
|
|
||||||
with util.uncache(name):
|
|
||||||
self.assertEqual(mock.get_filename(name), path)
|
|
||||||
|
|
||||||
def test_get_filename_no_source_path(self):
|
|
||||||
# get_filename() should raise ImportError if source_path returns None.
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyLoaderMock({name: None})
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError):
|
|
||||||
mock.get_filename(name)
|
|
||||||
|
|
||||||
|
|
||||||
class PyPycLoaderTests(PyLoaderTests):
|
|
||||||
|
|
||||||
"""Tests for importlib.abc.PyPycLoader."""
|
|
||||||
|
|
||||||
mocker = PyPycLoaderMock
|
|
||||||
|
|
||||||
@source_util.writes_bytecode_files
|
|
||||||
def verify_bytecode(self, mock, name):
|
|
||||||
assert name in mock.module_paths
|
|
||||||
self.assertIn(name, mock.module_bytecode)
|
|
||||||
magic = mock.module_bytecode[name][:4]
|
|
||||||
self.assertEqual(magic, imp.get_magic())
|
|
||||||
mtime = importlib._r_long(mock.module_bytecode[name][4:8])
|
|
||||||
self.assertEqual(mtime, 1)
|
|
||||||
source_size = mock.module_bytecode[name][8:12]
|
|
||||||
self.assertEqual(len(mock.source) & 0xFFFFFFFF,
|
|
||||||
importlib._r_long(source_size))
|
|
||||||
bc = mock.module_bytecode[name][12:]
|
|
||||||
self.assertEqual(bc, mock.compile_bc(name))
|
|
||||||
|
|
||||||
def test_module(self):
|
|
||||||
mock, name = super().test_module()
|
|
||||||
self.verify_bytecode(mock, name)
|
|
||||||
|
|
||||||
def test_package(self):
|
|
||||||
mock, name = super().test_package()
|
|
||||||
self.verify_bytecode(mock, name)
|
|
||||||
|
|
||||||
def test_lacking_parent(self):
|
|
||||||
mock, name = super().test_lacking_parent()
|
|
||||||
self.verify_bytecode(mock, name)
|
|
||||||
|
|
||||||
def test_module_reuse(self):
|
|
||||||
mock, name = super().test_module_reuse()
|
|
||||||
self.verify_bytecode(mock, name)
|
|
||||||
|
|
||||||
def test_state_after_failure(self):
|
|
||||||
super().test_state_after_failure()
|
|
||||||
|
|
||||||
def test_unloadable(self):
|
|
||||||
super().test_unloadable()
|
|
||||||
|
|
||||||
|
|
||||||
class PyPycLoaderInterfaceTests(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Test for the interface of importlib.abc.PyPycLoader."""
|
|
||||||
|
|
||||||
def get_filename_check(self, src_path, bc_path, expect):
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyPycLoaderMock({name: src_path}, {name: {'path': bc_path}})
|
|
||||||
with util.uncache(name):
|
|
||||||
assert mock.source_path(name) == src_path
|
|
||||||
assert mock.bytecode_path(name) == bc_path
|
|
||||||
self.assertEqual(mock.get_filename(name), expect)
|
|
||||||
|
|
||||||
def test_filename_with_source_bc(self):
|
|
||||||
# When source and bytecode paths present, return the source path.
|
|
||||||
self.get_filename_check('source_path', 'bc_path', 'source_path')
|
|
||||||
|
|
||||||
def test_filename_with_source_no_bc(self):
|
|
||||||
# With source but no bc, return source path.
|
|
||||||
self.get_filename_check('source_path', None, 'source_path')
|
|
||||||
|
|
||||||
def test_filename_with_no_source_bc(self):
|
|
||||||
# With not source but bc, return the bc path.
|
|
||||||
self.get_filename_check(None, 'bc_path', 'bc_path')
|
|
||||||
|
|
||||||
def test_filename_with_no_source_or_bc(self):
|
|
||||||
# With no source or bc, raise ImportError.
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyPycLoaderMock({name: None}, {name: {'path': None}})
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError):
|
|
||||||
mock.get_filename(name)
|
|
||||||
|
|
||||||
|
|
||||||
class SkipWritingBytecodeTests(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Test that bytecode is properly handled based on
|
|
||||||
sys.dont_write_bytecode."""
|
|
||||||
|
|
||||||
@source_util.writes_bytecode_files
|
|
||||||
def run_test(self, dont_write_bytecode):
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
|
|
||||||
sys.dont_write_bytecode = dont_write_bytecode
|
|
||||||
with util.uncache(name):
|
|
||||||
mock.load_module(name)
|
|
||||||
self.assertIsNot(name in mock.module_bytecode, dont_write_bytecode)
|
|
||||||
|
|
||||||
def test_no_bytecode_written(self):
|
|
||||||
self.run_test(True)
|
|
||||||
|
|
||||||
def test_bytecode_written(self):
|
|
||||||
self.run_test(False)
|
|
||||||
|
|
||||||
|
|
||||||
class RegeneratedBytecodeTests(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Test that bytecode is regenerated as expected."""
|
|
||||||
|
|
||||||
@source_util.writes_bytecode_files
|
|
||||||
def test_different_magic(self):
|
|
||||||
# A different magic number should lead to new bytecode.
|
|
||||||
name = 'mod'
|
|
||||||
bad_magic = b'\x00\x00\x00\x00'
|
|
||||||
assert bad_magic != imp.get_magic()
|
|
||||||
mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
|
|
||||||
{name: {'path': os.path.join('path', 'to',
|
|
||||||
'mod.bytecode'),
|
|
||||||
'magic': bad_magic}})
|
|
||||||
with util.uncache(name):
|
|
||||||
mock.load_module(name)
|
|
||||||
self.assertIn(name, mock.module_bytecode)
|
|
||||||
magic = mock.module_bytecode[name][:4]
|
|
||||||
self.assertEqual(magic, imp.get_magic())
|
|
||||||
|
|
||||||
@source_util.writes_bytecode_files
|
|
||||||
def test_old_mtime(self):
|
|
||||||
# Bytecode with an older mtime should be regenerated.
|
|
||||||
name = 'mod'
|
|
||||||
old_mtime = PyPycLoaderMock.default_mtime - 1
|
|
||||||
mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
|
|
||||||
{name: {'path': 'path/to/mod.bytecode', 'mtime': old_mtime}})
|
|
||||||
with util.uncache(name):
|
|
||||||
mock.load_module(name)
|
|
||||||
self.assertIn(name, mock.module_bytecode)
|
|
||||||
mtime = importlib._r_long(mock.module_bytecode[name][4:8])
|
|
||||||
self.assertEqual(mtime, PyPycLoaderMock.default_mtime)
|
|
||||||
|
|
||||||
|
|
||||||
class BadBytecodeFailureTests(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Test import failures when there is no source and parts of the bytecode
|
|
||||||
is bad."""
|
|
||||||
|
|
||||||
def test_bad_magic(self):
|
|
||||||
# A bad magic number should lead to an ImportError.
|
|
||||||
name = 'mod'
|
|
||||||
bad_magic = b'\x00\x00\x00\x00'
|
|
||||||
bc = {name:
|
|
||||||
{'path': os.path.join('path', 'to', 'mod'),
|
|
||||||
'magic': bad_magic}}
|
|
||||||
mock = PyPycLoaderMock({name: None}, bc)
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError) as cm:
|
|
||||||
mock.load_module(name)
|
|
||||||
self.assertEqual(cm.exception.name, name)
|
|
||||||
|
|
||||||
def test_no_bytecode(self):
|
|
||||||
# Missing code object bytecode should lead to an EOFError.
|
|
||||||
name = 'mod'
|
|
||||||
bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b''}}
|
|
||||||
mock = PyPycLoaderMock({name: None}, bc)
|
|
||||||
with util.uncache(name), self.assertRaises(EOFError):
|
|
||||||
mock.load_module(name)
|
|
||||||
|
|
||||||
def test_bad_bytecode(self):
|
|
||||||
# Malformed code object bytecode should lead to a ValueError.
|
|
||||||
name = 'mod'
|
|
||||||
bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b'1234'}}
|
|
||||||
mock = PyPycLoaderMock({name: None}, bc)
|
|
||||||
with util.uncache(name), self.assertRaises(ValueError):
|
|
||||||
mock.load_module(name)
|
|
||||||
|
|
||||||
|
|
||||||
def raise_ImportError(*args, **kwargs):
|
def raise_ImportError(*args, **kwargs):
|
||||||
raise ImportError
|
raise ImportError
|
||||||
|
|
||||||
class MissingPathsTests(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Test what happens when a source or bytecode path does not exist (either
|
|
||||||
from *_path returning None or raising ImportError)."""
|
|
||||||
|
|
||||||
def test_source_path_None(self):
|
|
||||||
# Bytecode should be used when source_path returns None, along with
|
|
||||||
# __file__ being set to the bytecode path.
|
|
||||||
name = 'mod'
|
|
||||||
bytecode_path = 'path/to/mod'
|
|
||||||
mock = PyPycLoaderMock({name: None}, {name: {'path': bytecode_path}})
|
|
||||||
with util.uncache(name):
|
|
||||||
module = mock.load_module(name)
|
|
||||||
self.assertEqual(module.__file__, bytecode_path)
|
|
||||||
|
|
||||||
# Testing for bytecode_path returning None handled by all tests where no
|
|
||||||
# bytecode initially exists.
|
|
||||||
|
|
||||||
def test_all_paths_None(self):
|
|
||||||
# If all *_path methods return None, raise ImportError.
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyPycLoaderMock({name: None})
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError) as cm:
|
|
||||||
mock.load_module(name)
|
|
||||||
self.assertEqual(cm.exception.name, name)
|
|
||||||
|
|
||||||
def test_source_path_ImportError(self):
|
|
||||||
# An ImportError from source_path should trigger an ImportError.
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyPycLoaderMock({}, {name: {'path': os.path.join('path', 'to',
|
|
||||||
'mod')}})
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError):
|
|
||||||
mock.load_module(name)
|
|
||||||
|
|
||||||
def test_bytecode_path_ImportError(self):
|
|
||||||
# An ImportError from bytecode_path should trigger an ImportError.
|
|
||||||
name = 'mod'
|
|
||||||
mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
|
|
||||||
bad_meth = types.MethodType(raise_ImportError, mock)
|
|
||||||
mock.bytecode_path = bad_meth
|
|
||||||
with util.uncache(name), self.assertRaises(ImportError) as cm:
|
|
||||||
mock.load_module(name)
|
|
||||||
|
|
||||||
|
|
||||||
class SourceLoaderTestHarness(unittest.TestCase):
|
class SourceLoaderTestHarness(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -801,6 +327,7 @@ class AbstractMethodImplTests(unittest.TestCase):
|
||||||
class Loader(abc.Loader):
|
class Loader(abc.Loader):
|
||||||
def load_module(self, fullname):
|
def load_module(self, fullname):
|
||||||
super().load_module(fullname)
|
super().load_module(fullname)
|
||||||
|
|
||||||
def module_repr(self, module):
|
def module_repr(self, module):
|
||||||
super().module_repr(module)
|
super().module_repr(module)
|
||||||
|
|
||||||
|
@ -825,20 +352,6 @@ class AbstractMethodImplTests(unittest.TestCase):
|
||||||
class SourceLoader(ResourceLoader, ExecutionLoader, abc.SourceLoader):
|
class SourceLoader(ResourceLoader, ExecutionLoader, abc.SourceLoader):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class PyLoader(ResourceLoader, InspectLoader, abc.PyLoader):
|
|
||||||
def source_path(self, _):
|
|
||||||
super().source_path(_)
|
|
||||||
|
|
||||||
class PyPycLoader(PyLoader, abc.PyPycLoader):
|
|
||||||
def bytecode_path(self, _):
|
|
||||||
super().bytecode_path(_)
|
|
||||||
|
|
||||||
def source_mtime(self, _):
|
|
||||||
super().source_mtime(_)
|
|
||||||
|
|
||||||
def write_bytecode(self, _, _2):
|
|
||||||
super().write_bytecode(_, _2)
|
|
||||||
|
|
||||||
def raises_NotImplementedError(self, ins, *args):
|
def raises_NotImplementedError(self, ins, *args):
|
||||||
for method_name in args:
|
for method_name in args:
|
||||||
method = getattr(ins, method_name)
|
method = getattr(ins, method_name)
|
||||||
|
@ -877,24 +390,12 @@ class AbstractMethodImplTests(unittest.TestCase):
|
||||||
# Required abstractmethods.
|
# Required abstractmethods.
|
||||||
self.raises_NotImplementedError(ins, 'get_filename', 'get_data')
|
self.raises_NotImplementedError(ins, 'get_filename', 'get_data')
|
||||||
# Optional abstractmethods.
|
# Optional abstractmethods.
|
||||||
self.raises_NotImplementedError(ins,'path_stats', 'set_data')
|
self.raises_NotImplementedError(ins, 'path_stats', 'set_data')
|
||||||
|
|
||||||
def test_PyLoader(self):
|
|
||||||
self.raises_NotImplementedError(self.PyLoader(), 'source_path',
|
|
||||||
'get_data', 'is_package')
|
|
||||||
|
|
||||||
def test_PyPycLoader(self):
|
|
||||||
self.raises_NotImplementedError(self.PyPycLoader(), 'source_path',
|
|
||||||
'source_mtime', 'bytecode_path',
|
|
||||||
'write_bytecode')
|
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
from test.support import run_unittest
|
from test.support import run_unittest
|
||||||
run_unittest(PyLoaderTests, PyLoaderCompatTests,
|
run_unittest(SkipWritingBytecodeTests, RegeneratedBytecodeTests,
|
||||||
PyLoaderInterfaceTests,
|
|
||||||
PyPycLoaderTests, PyPycLoaderInterfaceTests,
|
|
||||||
SkipWritingBytecodeTests, RegeneratedBytecodeTests,
|
|
||||||
BadBytecodeFailureTests, MissingPathsTests,
|
BadBytecodeFailureTests, MissingPathsTests,
|
||||||
SourceOnlyLoaderTests,
|
SourceOnlyLoaderTests,
|
||||||
SourceLoaderBytecodeTests,
|
SourceLoaderBytecodeTests,
|
||||||
|
|
|
@ -43,11 +43,6 @@ class PathEntryFinder(InheritanceTests, unittest.TestCase):
|
||||||
subclasses = [machinery.FileFinder]
|
subclasses = [machinery.FileFinder]
|
||||||
|
|
||||||
|
|
||||||
class Loader(InheritanceTests, unittest.TestCase):
|
|
||||||
|
|
||||||
subclasses = [abc.PyLoader]
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceLoader(InheritanceTests, unittest.TestCase):
|
class ResourceLoader(InheritanceTests, unittest.TestCase):
|
||||||
|
|
||||||
superclasses = [abc.Loader]
|
superclasses = [abc.Loader]
|
||||||
|
@ -56,14 +51,13 @@ class ResourceLoader(InheritanceTests, unittest.TestCase):
|
||||||
class InspectLoader(InheritanceTests, unittest.TestCase):
|
class InspectLoader(InheritanceTests, unittest.TestCase):
|
||||||
|
|
||||||
superclasses = [abc.Loader]
|
superclasses = [abc.Loader]
|
||||||
subclasses = [abc.PyLoader, machinery.BuiltinImporter,
|
subclasses = [machinery.BuiltinImporter,
|
||||||
machinery.FrozenImporter, machinery.ExtensionFileLoader]
|
machinery.FrozenImporter, machinery.ExtensionFileLoader]
|
||||||
|
|
||||||
|
|
||||||
class ExecutionLoader(InheritanceTests, unittest.TestCase):
|
class ExecutionLoader(InheritanceTests, unittest.TestCase):
|
||||||
|
|
||||||
superclasses = [abc.InspectLoader]
|
superclasses = [abc.InspectLoader]
|
||||||
subclasses = [abc.PyLoader]
|
|
||||||
|
|
||||||
|
|
||||||
class FileLoader(InheritanceTests, unittest.TestCase):
|
class FileLoader(InheritanceTests, unittest.TestCase):
|
||||||
|
@ -78,16 +72,6 @@ class SourceLoader(InheritanceTests, unittest.TestCase):
|
||||||
subclasses = [machinery.SourceFileLoader]
|
subclasses = [machinery.SourceFileLoader]
|
||||||
|
|
||||||
|
|
||||||
class PyLoader(InheritanceTests, unittest.TestCase):
|
|
||||||
|
|
||||||
superclasses = [abc.Loader, abc.ResourceLoader, abc.ExecutionLoader]
|
|
||||||
|
|
||||||
|
|
||||||
class PyPycLoader(InheritanceTests, unittest.TestCase):
|
|
||||||
|
|
||||||
superclasses = [abc.PyLoader]
|
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
from test.support import run_unittest
|
from test.support import run_unittest
|
||||||
classes = []
|
classes = []
|
||||||
|
|
|
@ -107,6 +107,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #15641: Clean up deprecated classes from importlib
|
||||||
|
Patch by Taras Lyapun.
|
||||||
|
|
||||||
- Issue #16350: zlib.Decompress.decompress() now accumulates data from
|
- Issue #16350: zlib.Decompress.decompress() now accumulates data from
|
||||||
successive calls after EOF in unused_data, instead of only saving the argument
|
successive calls after EOF in unused_data, instead of only saving the argument
|
||||||
to the last call. Patch by Serhiy Storchaka.
|
to the last call. Patch by Serhiy Storchaka.
|
||||||
|
|
Loading…
Reference in New Issue