summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/importlib.rst215
-rw-r--r--Lib/importlib/NOTES39
-rw-r--r--Lib/importlib/_bootstrap.py10
-rw-r--r--Lib/importlib/abc.py110
-rw-r--r--Lib/importlib/test/abc.py5
-rw-r--r--Lib/importlib/test/source/test_abc_loader.py390
-rw-r--r--Lib/importlib/test/source/test_file_loader.py (renamed from Lib/importlib/test/source/test_loader.py)96
-rw-r--r--Lib/importlib/test/source/util.py14
-rw-r--r--Lib/importlib/test/test_abc.py31
9 files changed, 739 insertions, 171 deletions
diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
index 1481302..eb9fd00 100644
--- a/Doc/library/importlib.rst
+++ b/Doc/library/importlib.rst
@@ -82,6 +82,175 @@ Functions
occuring from to already be imported (i.e., *package* must already be
imported).
+:mod:`importlib.abc` -- Abstract base classes related to import
+---------------------------------------------------------------
+
+.. module:: importlib.abc
+ :synopsis: Abstract base classes related to import
+
+The :mod:`importlib.abc` module contains all of the core abstract base classes
+used by :keyword:`import`. Some subclasses of the core abstract base classes
+are also provided to help in implementing the core ABCs.
+
+
+.. class:: Finder
+
+ An abstract base class representing a :term:`finder`.
+
+ ..method:: find_module(fullname, path=None)
+
+ An abstract method for finding a :term:`loader` for the specified
+ module. If the :term:`finder` is found on :data:`sys.meta_path` and the
+ module to be searched for is a subpackage or module then *path* is set
+ to the value of :attr:`__path__` from the parent package. If a loader
+ cannot be found, :keyword:`None` is returned.
+
+ The exact definition of a :term:`finder` can be found in :pep:`302`.
+
+
+.. class:: Loader
+
+ An abstract base class for a :term:`loader`.
+
+ ..method:: load_module(fullname)
+
+ An abstract method for loading a module. If the module cannot be
+ loaded, :exc:`ImportError` is raised, otherwise the loaded module is
+ returned.
+
+ If the requested module is already exists in :data:`sys.modules`, that
+ module should be used and reloaded.
+ Otherwise a new module is to be created by the loader and inserted into
+ :data:`sys.modules`before any loading begins to prevent recursion from
+ the import. If the loader inserted into 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. The :func:`importlib.util.module_for_loader` decorator handles
+ all of these details.
+
+ The loader is expected to set several attributes on the module when
+ adding a new module to :data:`sys.modules`.
+
+ - :attr:`__name__`
+ The name of the module.
+
+ - :attr:`__file__`
+ The path to where the module data is stored (not set for built-in
+ modules).
+
+ - :attr:`__path__`
+ Set to a list of strings specifying the search path within a
+ package. This attribute is not set on modules.
+
+ - :attr:`__package__`
+ The parent package for the module/package. If the module is
+ top-level then it has a value of the empty string. The
+ :func:`importlib.util.set_package` decorator can handle the details
+ for :attr:`__package__`.
+
+ - :attr:`__loader__`
+ Set to the loader used to load the module.
+
+ See :pep:`302` for the exact definition for a loader.
+
+
+.. class:: ResourceLoader
+
+ An abstract base class for a :term:`loader` which implements the optional
+ :pep:`302` protocol for loading arbitrary resources from the storage
+ back-end.
+
+ ..method:: get_data(path)
+
+ An abstract method to return the bytes for the data located at *path*.
+ Loaders that have a file-like storage back-end can implement this
+ abstract method to give direct access
+ to the data stored. :exc:`IOError` is to be raised if the *path* cannot
+ be found. The *path* is expected to be constructed using a module's
+ :attr:`__path__` attribute or an item from :attr:`__path__`.
+
+
+.. class:: InspectLoader
+
+ An abstract base class for a :term:`loader` which implements the optional
+ :pep:`302` protocol for loaders which inspect modules.
+
+ ..method:: is_package(fullname)
+
+ An abstract method to return a true value if the module is a package, a
+ false value otherwise. :exc:`ImportError` is raised if the
+ :term:`loader` cannot find the module.
+
+ ..method:: get_source(fullname)
+
+ An abstract method to return the source of a module. It is returned as
+ a string with universal newline support. Returns :keyword:`None` if no
+ source is available (e.g. a built-in module). Raises :exc:`ImportError`
+ if the loader cannot find the module specified.
+
+ ..method:: get_code(fullname)
+
+ An abstract method to return the :class:`code` object for a module.
+ :keyword:`None` is returned if the module does not have a code object
+ (e.g. built-in module). :exc:`ImportError` is raised if loader cannot
+ find the requested module.
+
+
+.. class:: PyLoader
+
+ An abstract base class inheriting from :class:`importlib.abc.InspectLoader`
+ and :class:`importlib.abc.ResourceLoader` designed to ease the loading of
+ Python source modules (bytecode is not handled; see
+ :class:`importlib.abc.PyPycLoader` 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.
+
+ ..method:: source_path(fullname)
+
+ An abstract method that returns the path to the source code for a
+ module. Should return :keyword:`None` if there is no source code.
+ :exc:`ImportError` if the module cannot be found.
+
+ ..method:: load_module(fullname)
+
+ A concrete implementation of :meth:`importlib.abc.Loader.load_module`
+ that loads Python source code.
+
+ ..method:: get_code(fullname)
+
+ A concrete implementation of
+ :meth:`importlib.abc.InspectLoader.get_code` that creates code objects
+ from Python source code.
+
+
+.. class:: PyPycLoader
+
+ An abstract base class inheriting from :class:`importlib.abc.PyLoader`.
+ This ABC is meant to help in creating loaders that support both Python
+ source and bytecode.
+
+ ..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 :keyword:`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. :keyword:`None` is returned if there is no bytecode.
+ :exc:`ImportError` is raised if the module is not found.
+
+ ..method:: write_bytecode(fullname, bytecode)
+
+ An abstract method which has the loader write *bytecode* for future
+ use. If the bytecode is written, return :keyword:`True`. Return
+ :keyword:`False` if the bytecode could not be written. This method
+ should not be called if :data:`sys.dont_write_bytecode` is true.
+
+
:mod:`importlib.machinery` -- Importers and path hooks
------------------------------------------------------
@@ -93,44 +262,27 @@ find and load modules.
.. class:: BuiltinImporter
- :term:`Importer` for built-in modules. All known built-in modules are
- listed in :data:`sys.builtin_module_names`.
+ An :term:`importer` for built-in modules. All known built-in modules are
+ listed in :data:`sys.builtin_module_names`. This class implements the
+ :class:`importlib.abc.Finder` and :class:`importlib.abc.Loader` ABCs.
Only class methods are defined by this class to alleviate the need for
instantiation.
- .. classmethod:: find_module(fullname, path=None)
-
- Class method that allows this class to be a :term:`finder` for built-in
- modules.
-
- .. classmethod:: load_module(fullname)
-
- Class method that allows this class to be a :term:`loader` for built-in
- modules.
-
.. class:: FrozenImporter
- :term:`Importer` for frozen modules.
+ An :term:`importer` for frozen modules. This class implements the
+ :class:`importlib.abc.Finder` and :class:`importlib.abc.Loader` ABCs.
Only class methods are defined by this class to alleviate the need for
instantiation.
- .. classmethod:: find_module(fullname, path=None)
-
- Class method that allows this class to be a :term:`finder` for frozen
- modules.
-
- .. classmethod:: load_module(fullname)
-
- Class method that allows this class to be a :term:`loader` for frozen
- modules.
-
.. class:: PathFinder
- :term:`Finder` for :data:`sys.path`.
+ :term:`Finder` for :data:`sys.path`. This class implements the
+ :class:`importlib.abc.Finder` ABC.
This class does not perfectly mirror the semantics of :keyword:`import` in
terms of :data:`sys.path`. No implicit path hooks are assumed for
@@ -142,15 +294,15 @@ find and load modules.
.. classmethod:: find_module(fullname, path=None)
Class method that attempts to find a :term:`loader` for the module
- specified by *fullname* either on :data:`sys.path` or, if defined, on
+ specified by *fullname* on :data:`sys.path` or, if defined, on
*path*. For each path entry that is searched,
:data:`sys.path_importer_cache` is checked. If an non-false object is
- found then it is used as the :term:`finder` to query for the module
- being searched for. For no entry is found in
+ found then it is used as the :term:`finder` to look for the module
+ being searched for. If no entry is found in
:data:`sys.path_importer_cache`, then :data:`sys.path_hooks` is
searched for a finder for the path entry and, if found, is stored in
:data:`sys.path_importer_cache` along with being queried about the
- module.
+ module. If no finder is ever found then :keyword:`None` is returned.
:mod:`importlib.util` -- Utility code for importers
@@ -166,10 +318,11 @@ an :term:`importer`.
A :term:`decorator` for a :term:`loader` which handles selecting the proper
module object to load with. The decorated method is expected to have a call
- signature of ``method(self, module_object)`` for which the second argument
- will be the module object to be used by the loader (note that the decorator
+ 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).
+ 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
diff --git a/Lib/importlib/NOTES b/Lib/importlib/NOTES
index 72b7da8..bbbb485 100644
--- a/Lib/importlib/NOTES
+++ b/Lib/importlib/NOTES
@@ -3,42 +3,12 @@ to do
* Public API left to expose (w/ docs!)
- + abc
+ + abc.PyLoader.get_source
+ + util.set_loader
- - Finder
+* Implement InspectLoader for BuiltinImporter and FrozenImporter.
- * find_module
-
- - Loader
-
- * load_module
-
- - ResourceLoader(Loader)
-
- * get_data
-
- - InspectLoader(Loader)
-
- * is_package
- * get_code
- * get_source
-
- - PyLoader(ResourceLoader)
-
- * source_path
-
- - PyPycLoader(PyLoader)
-
- * source_mtime
- * bytecode_path
- * write_bytecode
-
- + test (Really want to worry about compatibility with future versions?)
-
- - abc
-
- * FinderTests [doc]
- * LoaderTests [doc]
+ + Expose function to see if a frozen module is a package.
* Remove ``import *`` from importlib.__init__.
@@ -68,3 +38,4 @@ in the language specification).
+ imp
+ py_compile
+ compileall
+ + zipimport
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index c294490..58b5a46 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -383,14 +383,8 @@ class PyPycLoader(PyLoader):
def load_module(self, module):
"""Load a module from source or bytecode."""
name = module.__name__
- try:
- source_path = self.source_path(name)
- except ImportError:
- source_path = None
- try:
- bytecode_path = self.bytecode_path(name)
- except ImportError:
- bytecode_path = None
+ source_path = self.source_path(name)
+ bytecode_path = self.bytecode_path(name)
# get_code can worry about no viable paths existing.
module.__file__ = source_path or bytecode_path
return self._load_module(module)
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
new file mode 100644
index 0000000..2ecb821
--- /dev/null
+++ b/Lib/importlib/abc.py
@@ -0,0 +1,110 @@
+"""Abstract base classes related to import."""
+from . import _bootstrap
+from . import machinery
+import abc
+import types
+
+
+class Loader(metaclass=abc.ABCMeta):
+
+ """Abstract base class for import loaders.
+
+ See PEP 302 for details.
+
+ """
+
+ def load_module(self, fullname:str) -> types.ModuleType:
+ raise NotImplementedError
+
+Loader.register(machinery.BuiltinImporter)
+Loader.register(machinery.FrozenImporter)
+
+
+class Finder(metaclass=abc.ABCMeta):
+
+ """Abstract base class for import finders.
+
+ See PEP 302 for details.
+
+ """
+
+ @abc.abstractmethod
+ def find_module(self, fullname:str, path:[str]=None) -> Loader:
+ raise NotImplementedError
+
+Finder.register(machinery.BuiltinImporter)
+Finder.register(machinery.FrozenImporter)
+Finder.register(machinery.PathFinder)
+
+
+class Importer(Finder, Loader):
+
+ """Abstract base class for importers."""
+
+
+
+class ResourceLoader(Loader):
+
+ """Abstract base class for loaders which can return data from the back-end
+ storage.
+
+ This ABC represents one of the optional protocols specified by PEP 302.
+
+ """
+
+ @abc.abstractmethod
+ def get_data(self, path:str) -> bytes:
+ raise NotImplementedError
+
+
+class InspectLoader(Loader):
+
+ """Abstract base class for loaders which supports introspection.
+
+ This ABC represents one of the optional protocols specified by PEP 302.
+
+ """
+
+ @abc.abstractmethod
+ def is_package(self, fullname:str) -> bool:
+ return NotImplementedError
+
+ @abc.abstractmethod
+ def get_code(self, fullname:str) -> types.CodeType:
+ return NotImplementedError
+
+ @abc.abstractmethod
+ def get_source(self, fullname:str) -> str:
+ return NotImplementedError
+
+
+class PyLoader(_bootstrap.PyLoader, InspectLoader):
+
+ """Abstract base class that implements the core parts needed to load Python
+ source code."""
+
+ # load_module and get_code are implemented.
+
+ @abc.abstractmethod
+ def source_path(self, fullname:str) -> object:
+ raise NotImplementedError
+
+
+class PyPycLoader(_bootstrap.PyPycLoader, PyLoader):
+
+ """Abstract base class that implements the core parts needed to load Python
+ source and bytecode."""
+
+ # Implements load_module and get_code.
+
+ @abc.abstractmethod
+ def source_mtime(self, fullname:str) -> int:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def bytecode_path(self, fullname:str) -> object:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def write_bytecode(self, fullname:str, bytecode:bytes):
+ raise NotImplementedError
diff --git a/Lib/importlib/test/abc.py b/Lib/importlib/test/abc.py
index 4acbfc9..2c17ac3 100644
--- a/Lib/importlib/test/abc.py
+++ b/Lib/importlib/test/abc.py
@@ -65,10 +65,11 @@ class LoaderTests(unittest.TestCase, metaclass=abc.ABCMeta):
Attributes to verify:
- * __file__
- * __loader__
* __name__
+ * __file__
+ * __package__
* __path__
+ * __loader__
"""
pass
diff --git a/Lib/importlib/test/source/test_abc_loader.py b/Lib/importlib/test/source/test_abc_loader.py
new file mode 100644
index 0000000..c937793
--- /dev/null
+++ b/Lib/importlib/test/source/test_abc_loader.py
@@ -0,0 +1,390 @@
+import importlib
+from importlib import abc
+from .. import abc as testing_abc
+from .. import util
+from . import util as source_util
+import imp
+import marshal
+import os
+import sys
+import types
+import unittest
+
+
+class PyLoaderMock(abc.PyLoader):
+
+ # Globals that should be defined for all modules.
+ source = ("_ = '::'.join([__name__, __file__, __package__, "
+ "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.encode('utf-8')
+
+ def is_package(self, name):
+ try:
+ return '__init__' in self.module_paths[name]
+ except KeyError:
+ raise ImportError
+
+ def get_source(self, name): # Should not be needed.
+ raise NotImplementedError
+
+ def source_path(self, name):
+ try:
+ return self.module_paths[name]
+ except KeyError:
+ raise ImportError
+
+
+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))
+ if 'bc' in data:
+ bc = data['bc']
+ else:
+ bc = self.compile_bc(name)
+ self.module_bytecode[name] = magic + mtime + 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]
+
+
+class PyLoaderTests(testing_abc.LoaderTests):
+
+ """Tests for importlib.abc.PyLoader."""
+
+ mocker = PyLoaderMock
+
+ def eq_attrs(self, ob, **kwargs):
+ for attr, val in kwargs.items():
+ self.assertEqual(getattr(ob, attr), val)
+
+ def test_module(self):
+ name = '<module>'
+ path = 'path/to/module'
+ mock = self.mocker({name: path})
+ with util.uncache(name):
+ module = mock.load_module(name)
+ self.assert_(name in sys.modules)
+ self.eq_attrs(module, __name__=name, __file__=path, __package__='',
+ __loader__=mock)
+ self.assert_(not hasattr(module, '__path__'))
+ return mock, name
+
+ def test_package(self):
+ name = '<pkg>'
+ path = '/path/to/<pkg>/__init__'
+ mock = self.mocker({name: path})
+ with util.uncache(name):
+ module = mock.load_module(name)
+ self.assert_(name in 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 = 'path/to/pkg/mod'
+ mock = self.mocker({name: path})
+ with util.uncache(name):
+ module = mock.load_module(name)
+ self.assert_(name in sys.modules)
+ self.eq_attrs(module, __name__=name, __file__=path, __package__='pkg',
+ __loader__=mock)
+ self.assert_(not hasattr(module, '__path__'))
+ return mock, name
+
+ def test_module_reuse(self):
+ name = 'mod'
+ path = '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.assert_(loaded_module is module)
+ self.assert_(sys.modules[name] is module)
+ return mock, name
+
+ def test_state_after_failure(self):
+ name = "mod"
+ module = imp.new_module(name)
+ module.blah = None
+ mock = self.mocker({name: 'path/to/mod'})
+ mock.source = "1/0"
+ with util.uncache(name):
+ sys.modules[name] = module
+ self.assertRaises(ZeroDivisionError, mock.load_module, name)
+ self.assert_(sys.modules[name] is module)
+ self.assert_(hasattr(module, 'blah'))
+ return mock
+
+ def test_unloadable(self):
+ name = "mod"
+ mock = self.mocker({name: 'path/to/mod'})
+ mock.source = "1/0"
+ with util.uncache(name):
+ self.assertRaises(ZeroDivisionError, mock.load_module, name)
+ self.assert_(name not in sys.modules)
+ return mock
+
+
+class PyLoaderInterfaceTests(unittest.TestCase):
+
+
+ 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)
+
+
+class PyPycLoaderTests(PyLoaderTests):
+
+ """Tests for importlib.abc.PyPycLoader."""
+
+ mocker = PyPycLoaderMock
+
+ @source_util.writes_bytecode
+ def verify_bytecode(self, mock, name):
+ assert name in mock.module_paths
+ self.assert_(name in 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)
+ bc = mock.module_bytecode[name][8:]
+
+
+ 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 SkipWritingBytecodeTests(unittest.TestCase):
+
+ """Test that bytecode is properly handled based on
+ sys.dont_write_bytecode."""
+
+ @source_util.writes_bytecode
+ def run_test(self, dont_write_bytecode):
+ name = 'mod'
+ mock = PyPycLoaderMock({name: 'path/to/mod'})
+ sys.dont_write_bytecode = dont_write_bytecode
+ with util.uncache(name):
+ mock.load_module(name)
+ self.assert_((name in mock.module_bytecode) is not
+ 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
+ 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: 'path/to/mod'},
+ {name: {'path': 'path/to/mod.bytecode',
+ 'magic': bad_magic}})
+ with util.uncache(name):
+ mock.load_module(name)
+ self.assert_(name in mock.module_bytecode)
+ magic = mock.module_bytecode[name][:4]
+ self.assertEqual(magic, imp.get_magic())
+
+ @source_util.writes_bytecode
+ def test_old_mtime(self):
+ # Bytecode with an older mtime should be regenerated.
+ name = 'mod'
+ old_mtime = PyPycLoaderMock.default_mtime - 1
+ mock = PyPycLoaderMock({name: 'path/to/mod'},
+ {name: {'path': 'path/to/mod.bytecode', 'mtime': old_mtime}})
+ with util.uncache(name):
+ mock.load_module(name)
+ self.assert_(name in 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'
+ mock = PyPycLoaderMock({}, {name: {'path': 'path/to/mod',
+ 'magic': bad_magic}})
+ with util.uncache(name):
+ self.assertRaises(ImportError, mock.load_module, name)
+
+ def test_bad_bytecode(self):
+ # Bad code object bytecode should elad to an ImportError.
+ name = 'mod'
+ mock = PyPycLoaderMock({}, {name: {'path': '/path/to/mod', 'bc': b''}})
+ with util.uncache(name):
+ self.assertRaises(ImportError, mock.load_module, name)
+
+
+def raise_ImportError(*args, **kwargs):
+ 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, mock.load_module, name)
+
+ def test_source_path_ImportError(self):
+ # An ImportError from source_path should trigger an ImportError.
+ name = 'mod'
+ mock = PyPycLoaderMock({}, {name: {'path': '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: 'path/to/mod'})
+ bad_meth = types.MethodType(raise_ImportError, mock)
+ mock.bytecode_path = bad_meth
+ with util.uncache(name):
+ self.assertRaises(ImportError, mock.load_module, name)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(PyLoaderTests, PyLoaderInterfaceTests,
+ PyPycLoaderTests, SkipWritingBytecodeTests,
+ RegeneratedBytecodeTests, BadBytecodeFailureTests,
+ MissingPathsTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_loader.py b/Lib/importlib/test/source/test_file_loader.py
index 960210f..3da4426 100644
--- a/Lib/importlib/test/source/test_loader.py
+++ b/Lib/importlib/test/source/test_file_loader.py
@@ -102,102 +102,6 @@ class SimpleTest(unittest.TestCase):
self.assert_('_temp' not in sys.modules)
-class DontWriteBytecodeTest(unittest.TestCase):
-
- """If sys.dont_write_bytcode is true then no bytecode should be created."""
-
- def tearDown(self):
- sys.dont_write_bytecode = False
-
- @source_util.writes_bytecode
- def run_test(self, assertion):
- with source_util.create_modules('_temp') as mapping:
- loader = importlib.PyPycFileLoader('_temp', mapping['_temp'], False)
- loader.load_module('_temp')
- bytecode_path = source_util.bytecode_path(mapping['_temp'])
- assertion(bytecode_path)
-
- def test_bytecode_written(self):
- fxn = lambda bc_path: self.assert_(os.path.exists(bc_path))
- self.run_test(fxn)
-
- def test_bytecode_not_written(self):
- sys.dont_write_bytecode = True
- fxn = lambda bc_path: self.assert_(not os.path.exists(bc_path))
- self.run_test(fxn)
-
-
-class BadDataTest(unittest.TestCase):
-
- """If the bytecode has a magic number that does not match the
- interpreters', ImportError is raised [bad magic]. The timestamp can have
- any value. And bad marshal data raises ValueError.
-
- """
-
- # [bad magic]
- def test_bad_magic(self):
- with source_util.create_modules('_temp') as mapping:
- py_compile.compile(mapping['_temp'])
- os.unlink(mapping['_temp'])
- bytecode_path = source_util.bytecode_path(mapping['_temp'])
- with open(bytecode_path, 'r+b') as file:
- file.seek(0)
- file.write(b'\x00\x00\x00\x00')
- loader = importlib.PyPycFileLoader('_temp', mapping['_temp'], False)
- self.assertRaises(ImportError, loader.load_module, '_temp')
- self.assert_('_temp' not in sys.modules)
-
-
-class SourceBytecodeInteraction(unittest.TestCase):
-
- """When both source and bytecode are present, certain rules dictate which
- version of the code takes precedent. All things being equal, the bytecode
- is used with the value of __file__ set to the source [basic top-level],
- [basic package], [basic sub-module], [basic sub-package].
-
- """
-
- def import_(self, file, module, *, pkg=False):
- loader = importlib.PyPycFileLoader(module, file, pkg)
- return loader.load_module(module)
-
- def run_test(self, test, *create, pkg=False):
- create += (test,)
- with source_util.create_modules(*create) as mapping:
- for name in create:
- py_compile.compile(mapping[name])
- if pkg:
- import_name = test.rsplit('.', 1)[0]
- else:
- import_name = test
- loader = importlib.PyPycFileLoader(import_name, mapping[test], pkg)
- # Because some platforms only have a granularity to the second for
- # atime you can't check the physical files. Instead just make it an
- # exception trigger if source was read.
- loader.get_source = lambda self, x: 42
- module = loader.load_module(import_name)
- self.assertEqual(module.__file__, mapping[name])
- self.assert_(import_name in sys.modules)
- self.assertEqual(id(module), id(sys.modules[import_name]))
-
- # [basic top-level]
- def test_basic_top_level(self):
- self.run_test('top_level')
-
- # [basic package]
- def test_basic_package(self):
- self.run_test('pkg.__init__', pkg=True)
-
- # [basic sub-module]
- def test_basic_sub_module(self):
- self.run_test('pkg.sub', 'pkg.__init__')
-
- # [basic sub-package]
- def test_basic_sub_package(self):
- self.run_test('pkg.sub.__init__', 'pkg.__init__', pkg=True)
-
-
class BadBytecodeTest(unittest.TestCase):
"""But there are several things about the bytecode which might lead to the
diff --git a/Lib/importlib/test/source/util.py b/Lib/importlib/test/source/util.py
index f02d491..280edb4 100644
--- a/Lib/importlib/test/source/util.py
+++ b/Lib/importlib/test/source/util.py
@@ -1,5 +1,6 @@
from .. import util
import contextlib
+import functools
import imp
import os
import os.path
@@ -9,11 +10,24 @@ from test import support
def writes_bytecode(fxn):
+ """Decorator to protect sys.dont_write_bytecode from mutation."""
+ @functools.wraps(fxn)
+ def wrapper(*args, **kwargs):
+ original = sys.dont_write_bytecode
+ sys.dont_write_bytecode = False
+ to_return = fxn(*args, **kwargs)
+ sys.dont_write_bytecode = original
+ return to_return
+ return wrapper
+
+
+def writes_bytecode_files(fxn):
"""Decorator that returns the function if writing bytecode is enabled, else
a stub function that accepts anything and simply returns None."""
if sys.dont_write_bytecode:
return lambda *args, **kwargs: None
else:
+ @functools.wraps(fxn)
def wrapper(*args, **kwargs):
to_return = fxn(*args, **kwargs)
sys.dont_write_bytecode = False
diff --git a/Lib/importlib/test/test_abc.py b/Lib/importlib/test/test_abc.py
new file mode 100644
index 0000000..a54adb9
--- /dev/null
+++ b/Lib/importlib/test/test_abc.py
@@ -0,0 +1,31 @@
+from importlib import abc
+from importlib import machinery
+import unittest
+
+
+class SubclassTests(unittest.TestCase):
+
+ """Test that the various classes in importlib are subclasses of the
+ expected ABCS."""
+
+ def verify(self, ABC, *classes):
+ """Verify the classes are subclasses of the ABC."""
+ for cls in classes:
+ self.assert_(issubclass(cls, ABC))
+
+ def test_Finder(self):
+ self.verify(abc.Finder, machinery.BuiltinImporter,
+ machinery.FrozenImporter, machinery.PathFinder)
+
+ def test_Loader(self):
+ self.verify(abc.Loader, machinery.BuiltinImporter,
+ machinery.FrozenImporter)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(SubclassTests)
+
+
+if __name__ == '__main__':
+ test_main()