summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/importlib/NOTES78
-rw-r--r--Lib/importlib/__init__.py133
-rw-r--r--Lib/importlib/_bootstrap.py997
-rw-r--r--Lib/importlib/test/__init__.py26
-rw-r--r--Lib/importlib/test/builtin/__init__.py12
-rw-r--r--Lib/importlib/test/builtin/test_finder.py36
-rw-r--r--Lib/importlib/test/builtin/test_loader.py52
-rw-r--r--Lib/importlib/test/extension/__init__.py13
-rw-r--r--Lib/importlib/test/extension/test_case_sensitivity.py39
-rw-r--r--Lib/importlib/test/extension/test_finder.py29
-rw-r--r--Lib/importlib/test/extension/test_loader.py37
-rw-r--r--Lib/importlib/test/extension/test_path_hook.py50
-rw-r--r--Lib/importlib/test/finder_tests.py39
-rw-r--r--Lib/importlib/test/frozen/__init__.py13
-rw-r--r--Lib/importlib/test/frozen/support.py24
-rw-r--r--Lib/importlib/test/frozen/test_finder.py44
-rw-r--r--Lib/importlib/test/frozen/test_loader.py27
-rw-r--r--Lib/importlib/test/import_/__init__.py13
-rw-r--r--Lib/importlib/test/import_/test___package__.py110
-rw-r--r--Lib/importlib/test/import_/test_caching.py75
-rw-r--r--Lib/importlib/test/import_/test_fromlist.py116
-rw-r--r--Lib/importlib/test/import_/test_meta_path.py99
-rw-r--r--Lib/importlib/test/import_/test_packages.py29
-rw-r--r--Lib/importlib/test/import_/test_path.py158
-rw-r--r--Lib/importlib/test/import_/test_relative_imports.py199
-rw-r--r--Lib/importlib/test/source/__init__.py13
-rw-r--r--Lib/importlib/test/source/test_case_sensitivity.py57
-rw-r--r--Lib/importlib/test/source/test_finder.py130
-rw-r--r--Lib/importlib/test/source/test_loader.py201
-rw-r--r--Lib/importlib/test/source/test_path_hook.py23
-rw-r--r--Lib/importlib/test/source/test_reload.py71
-rw-r--r--Lib/importlib/test/source/test_source_encoding.py122
-rw-r--r--Lib/importlib/test/support.py223
-rw-r--r--Lib/importlib/test/test_api.py62
-rw-r--r--Lib/test/test_importlib.py10
35 files changed, 3360 insertions, 0 deletions
diff --git a/Lib/importlib/NOTES b/Lib/importlib/NOTES
new file mode 100644
index 0000000..134f112
--- /dev/null
+++ b/Lib/importlib/NOTES
@@ -0,0 +1,78 @@
+to do
+/////
+
+* Write importlib.__import__
+
+* Document
+ + Package.
+ + import_module
+ + __import__
+
+* Create reasonable base tests that all finders and loaders must pass so
+ that various implementations can just subclass as needed.
+
+* Expose built-in and frozen importers.
+ + Make staticmethods so that class can be used directly.
+
+* Reorganize support code.
+ + Separate general support code and importer-specific (e.g. source) support
+ code.
+ - Create support modules for each subdirectory (as needed).
+ + Add a file loader mock that returns monotonically increasing mtime.
+ - Use in source/test_reload.
+ - Use in source/test_load_module_mixed.
+
+* API simplification?
+ + read_source -> get_data/source_path
+ + read_bytecode -> get_data/bytecode_path
+ + write_bytecode -> complete set of bytes for bytecode instead of
+ individual arguments.
+
+* Implement PEP 302 protocol for loaders (should just be a matter of testing).
+ + Built-in.
+ + Frozen.
+ + Extension.
+ + Source/bytecode.
+
+* Create meta_path importer for sys.path.
+
+* OPTIMIZE!
+ + Write benchmark suite.
+ + Fast path common cases.
+ - Absolute name from sys.path.
+ - Relative name from sys.path.
+
+* Public API (w/ docs!)
+ + abc
+ - Finder
+ * find_module
+ - Loader
+ * load_module
+ - ResourceLoader(Loader)
+ * get_data
+ - InspectLoader(Loader)
+ * is_package
+ * get_code
+ * get_source
+ - (?) SourceLoader(ResourceLoader)
+ * source_path
+ * bytecode_path
+ * write_bytecode
+ + util
+ - get_module decorator (new name)
+ - check_name decorator (new name)
+ + hooks (?)
+ - (?) Chained path hook/finder
+ - BuiltinImporter
+ - FrozenImporter
+ - (?) FileFinder
+ - Extensions importers
+ * ExtensionFinder
+ * (?) Loader
+ - Source/bytecode importers
+ * SourceFinder
+ * (?) Loader
+ + __init__
+ - __import__
+ - import_module (backport to 2.7)
+ - resolve_name (backport to 2.7)
diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py
new file mode 100644
index 0000000..b59c9c4
--- /dev/null
+++ b/Lib/importlib/__init__.py
@@ -0,0 +1,133 @@
+"""A pure Python implementation of import.
+
+References on import:
+
+ * Language reference
+ http://docs.python.org/ref/import.html
+ * __import__ function
+ http://docs.python.org/lib/built-in-funcs.html
+ * Packages
+ http://www.python.org/doc/essays/packages.html
+ * PEP 235: Import on Case-Insensitive Platforms
+ http://www.python.org/dev/peps/pep-0235
+ * PEP 275: Import Modules from Zip Archives
+ http://www.python.org/dev/peps/pep-0273
+ * PEP 302: New Import Hooks
+ http://www.python.org/dev/peps/pep-0302/
+ * PEP 328: Imports: Multi-line and Absolute/Relative
+ http://www.python.org/dev/peps/pep-0328
+
+"""
+from . import _bootstrap
+
+# XXX Temporary functions that should eventually be removed.
+import os
+import re
+import tokenize
+
+def _set__import__():
+ """Set __import__ to an instance of Import."""
+ global original__import__
+ original__import__ = __import__
+ __builtins__['__import__'] = Import()
+
+
+def _reset__import__():
+ """Set __import__ back to the original implementation (assumes
+ _set__import__ was called previously)."""
+ __builtins__['__import__'] = original__import__
+
+
+def _case_ok(directory, check):
+ """Check if the directory contains something matching 'check'.
+
+ No check is done if the file/directory exists or not.
+
+ """
+ if 'PYTHONCASEOK' in os.environ:
+ return True
+ elif check in os.listdir(directory):
+ return True
+ return False
+
+
+def _w_long(x):
+ """Convert a 32-bit integer to little-endian.
+
+ XXX Temporary until marshal's long functions are exposed.
+
+ """
+ x = int(x)
+ int_bytes = []
+ int_bytes.append(x & 0xFF)
+ int_bytes.append((x >> 8) & 0xFF)
+ int_bytes.append((x >> 16) & 0xFF)
+ int_bytes.append((x >> 24) & 0xFF)
+ return bytearray(int_bytes)
+
+
+def _r_long(int_bytes):
+ """Convert 4 bytes in little-endian to an integer.
+
+ XXX Temporary until marshal's long function are exposed.
+
+ """
+ x = int_bytes[0]
+ x |= int_bytes[1] << 8
+ x |= int_bytes[2] << 16
+ x |= int_bytes[3] << 24
+ return x
+
+
+def import_module(name, package=None):
+ """Import a module.
+
+ The 'package' argument is used to resolve relative import names. Typically
+ this is the __package__ attribute of the module making the function call.
+
+ """
+ if name.startswith('.'):
+ if not package:
+ raise TypeError("relative imports require the 'package' argument")
+ level = 0
+ for character in name:
+ if character != '.':
+ break
+ level += 1
+ name = Import._resolve_name(name[level:], package, level)
+ __import__(name)
+ return sys.modules[name]
+
+
+
+# Required built-in modules.
+try:
+ import posix as _os
+except ImportError:
+ try:
+ import nt as _os
+ except ImportError:
+ try:
+ import os2 as _os
+ except ImportError:
+ raise ImportError('posix, nt, or os2 module required for importlib')
+_bootstrap._os = _os
+import imp, sys, marshal, errno, _fileio
+_bootstrap.imp = imp
+_bootstrap.sys = sys
+_bootstrap.marshal = marshal
+_bootstrap.errno = errno
+_bootstrap._fileio = _fileio
+import _warnings
+_bootstrap._warnings = _warnings
+
+
+from os import sep
+# For os.path.join replacement; pull from Include/osdefs.h:SEP .
+_bootstrap.path_sep = sep
+
+_bootstrap._case_ok = _case_ok
+marshal._w_long = _w_long
+marshal._r_long = _r_long
+
+from ._bootstrap import *
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
new file mode 100644
index 0000000..68f0017
--- /dev/null
+++ b/Lib/importlib/_bootstrap.py
@@ -0,0 +1,997 @@
+"""Core implementation of import.
+
+This module is NOT meant to be directly imported! It has been designed such
+that it can be bootstrapped into Python as the implementation of import. As
+such it requires the injection of specific modules and attributes in order to
+work. One should use importlib as the public-facing version of this module.
+
+"""
+
+# Injected modules are '_warnings', 'imp', 'sys', 'marshal', 'errno', and '_os'
+# (a.k.a. 'posix', 'nt' or 'os2').
+# Injected attribute is path_sep.
+#
+# When editing this code be aware that code executed at import time CANNOT
+# reference any injected objects! This includes not only global code but also
+# anything specified at the class level.
+
+
+# XXX Could also expose Modules/getpath.c:joinpath()
+def _path_join(*args):
+ """Replacement for os.path.join."""
+ return path_sep.join(x[:-len(path_sep)] if x.endswith(path_sep) else x
+ for x in args)
+
+
+def _path_exists(path):
+ """Replacement for os.path.exists."""
+ try:
+ _os.stat(path)
+ except OSError:
+ return False
+ else:
+ return True
+
+
+def _path_is_mode_type(path, mode):
+ """Test whether the path is the specified mode type."""
+ try:
+ stat_info = _os.stat(path)
+ except OSError:
+ return False
+ return (stat_info.st_mode & 0o170000) == mode
+
+
+# XXX Could also expose Modules/getpath.c:isfile()
+def _path_isfile(path):
+ """Replacement for os.path.isfile."""
+ return _path_is_mode_type(path, 0o100000)
+
+
+# XXX Could also expose Modules/getpath.c:isdir()
+def _path_isdir(path):
+ """Replacement for os.path.isdir."""
+ return _path_is_mode_type(path, 0o040000)
+
+
+def _path_without_ext(path, ext_type):
+ """Replacement for os.path.splitext()[0]."""
+ for suffix in suffix_list(ext_type):
+ if path.endswith(suffix):
+ return path[:-len(suffix)]
+ else:
+ raise ValueError("path is not of the specified type")
+
+
+def _path_absolute(path):
+ """Replacement for os.path.abspath."""
+ if not path:
+ path = _os.getcwd()
+ try:
+ return _os._getfullpathname(path)
+ except AttributeError:
+ if path.startswith('/'):
+ return path
+ else:
+ return _path_join(_os.getcwd(), path)
+
+
+class closing:
+
+ """Simple replacement for contextlib.closing."""
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ def __enter__(self):
+ return self.obj
+
+ def __exit__(self, *args):
+ self.obj.close()
+
+
+class _BuiltinFrozenBaseLoader(object):
+
+ """Base class for meta_path loaders for built-in and frozen modules.
+
+ Subclasses must implement:
+
+ * _find(fullname:str) -> bool
+ Finder which returns whether the class can handle the module.
+
+ * _load(fullname:str) -> module
+ Loader which returns the loaded module. The check for sys.modules
+ does not need to be handled by this method.
+
+ * type_:str
+ Name of the type of module being handled. Used in error messages.
+
+ """
+
+ def find_module(self, fullname, path=None):
+ """Find a module."""
+ if not self._find(fullname):
+ return None
+ return self
+
+ def load_module(self, fullname):
+ """Load a module."""
+ try:
+ return sys.modules[fullname]
+ except KeyError:
+ pass
+ mod = self._load(fullname)
+ if not mod:
+ raise ImportError("expected {0} module not "
+ "loaded".format(self.type_))
+ return mod
+
+
+class BuiltinImporter(_BuiltinFrozenBaseLoader):
+
+ """Meta path loader for built-in modules."""
+
+ type_ = "built-in"
+
+ def __init__(self):
+ """Set the methods needed by the class.
+
+ Cannot be set at the class level because the imp module is not
+ necessarily injected until after the class is created.
+
+ """
+ self._find = imp.is_builtin
+ self._load = imp.init_builtin
+
+ def find_module(self, fullname, path=None):
+ """Try to find the built-in module.
+
+ If 'path' is ever specified then the search is considered a failure.
+
+ """
+ if path is not None:
+ return None
+ return super().find_module(fullname, path)
+
+ def load_module(self, fullname):
+ """Load a built-in module."""
+ if fullname not in sys.builtin_module_names:
+ raise ImportError("{0} is not a built-in module".format(fullname))
+ return super().load_module(fullname)
+
+
+class FrozenImporter(_BuiltinFrozenBaseLoader):
+
+ """Meta path class for importing frozen modules."""
+
+ type_ = 'frozen'
+
+ def __init__(self):
+ """Specify the methods needed by the superclass.
+
+ Because imp may not be injected until after class creation these
+ methods cannot be set at the class level.
+
+ """
+ self._find = imp.is_frozen
+ self._load = imp.init_frozen
+
+ def load_module(self, fullname):
+ """Load a frozen module."""
+ if not self.find_module(fullname):
+ raise ImportError("{0} is not a frozen module".format(fullname))
+ return super().load_module(fullname)
+
+
+class ChainedImporter(object):
+
+ """Finder that sequentially calls other finders."""
+
+ def __init__(self, *importers):
+ self._importers = importers
+
+ def find_module(self, fullname, path=None):
+ for importer in self._importers:
+ result = importer.find_module(fullname, path)
+ if result:
+ return result
+ else:
+ return None
+
+
+# XXX Don't make filesystem-specific and instead make generic for any path
+# hooks.
+def chaining_fs_path_hook(*path_hooks):
+ """Create a closure which calls the path hooks sequentially looking for
+ which path hooks can handle a path entry.
+
+
+ Passed-in path hooks work as any other path hooks, raising ImportError if
+ they cannot handle the path, otherwise returning a finder.
+
+ """
+ def chained_fs_path_hook(path_entry):
+ """Closure which sees which of the captured path hooks can handle the
+ path entry."""
+ absolute_path = _path_absolute(path_entry)
+ if not _path_isdir(absolute_path):
+ raise ImportError("only directories are supported")
+ accepted = []
+ for path_hook in path_hooks:
+ try:
+ accepted.append(path_hook(absolute_path))
+ except ImportError:
+ continue
+ if not accepted:
+ raise ImportError("no path hooks could handle %s" % path_entry)
+ return ChainedImporter(*accepted)
+ return chained_fs_path_hook
+
+
+def check_name(method):
+ """Decorator to verify that the module being requested matches the one the
+ loader can handle.
+
+ The first argument (self) must define _name which the second argument is
+ comapred against. If the comparison fails then ImportError is raised.
+
+ """
+ def inner(self, name, *args, **kwargs):
+ if self._name != name:
+ raise ImportError("loader cannot handle %s" % name)
+ return method(self, name, *args, **kwargs)
+ inner.__name__ = method.__name__
+ inner.__doc__ = method.__doc__
+ inner.__dict__.update(method.__dict__)
+ return inner
+
+
+class _ExtensionFileLoader(object):
+
+ """Loader for extension modules.
+
+ The constructor is designed to work with FileImporter.
+
+ """
+
+ def __init__(self, name, path, is_pkg):
+ """Initialize the loader.
+
+ If is_pkg is True then an exception is raised as extension modules
+ cannot be the __init__ module for an extension module.
+
+ """
+ self._name = name
+ self._path = path
+ if is_pkg:
+ raise ValueError("extension modules cannot be packages")
+
+ @check_name
+ def load_module(self, fullname):
+ """Load an extension module."""
+ assert self._name == fullname
+ try:
+ module = imp.load_dynamic(fullname, self._path)
+ module.__loader__ = self
+ return module
+ except:
+ # If an error occurred, don't leave a partially initialized module.
+ if fullname in sys.modules:
+ del sys.modules[fullname]
+ raise
+
+ @check_name
+ def is_package(self, fullname):
+ """Return False as an extension module can never be a package."""
+ return False
+
+ @check_name
+ def get_code(self, fullname):
+ """Return None as an extension module cannot create a code object."""
+ return None
+
+ @check_name
+ def get_source(self, fullname):
+ """Return None as extension modules have no source code."""
+ return None
+
+
+def suffix_list(suffix_type):
+ """Return a list of file suffixes based on the imp file type."""
+ return [suffix[0] for suffix in imp.get_suffixes()
+ if suffix[2] == suffix_type]
+
+
+# XXX Need a better name.
+def get_module(fxn):
+ """Decorator to handle selecting the proper module for load_module
+ implementations.
+
+ Decorated modules are passed the module to use instead of the module name.
+ The module is either from sys.modules if it already exists (for reloading)
+ or is a new module which has __name__ set. If any exception is raised by
+ the decorated method then __loader__, __name__, __file__, and __path__ are
+ all restored on the module to their original values.
+
+ """
+ def decorated(self, fullname):
+ module = sys.modules.get(fullname)
+ is_reload = bool(module)
+ if not is_reload:
+ # This must be done before open() is called as the 'io' module
+ # implicitly imports 'locale' and would otherwise trigger an
+ # infinite loop.
+ module = imp.new_module(fullname)
+ module.__name__ = fullname
+ sys.modules[fullname] = module
+ else:
+ original_values = {}
+ modified_attrs = ['__loader__', '__name__', '__file__', '__path__']
+ for attr in modified_attrs:
+ try:
+ original_values[attr] = getattr(module, attr)
+ except AttributeError:
+ pass
+ try:
+ return fxn(self, module)
+ except:
+ if not is_reload:
+ del sys.modules[fullname]
+ else:
+ for attr in modified_attrs:
+ if attr in original_values:
+ setattr(module, attr, original_values[attr])
+ elif hasattr(module, attr):
+ delattr(module, attr)
+ raise
+ return decorated
+
+
+class _PyFileLoader(object):
+ # XXX Still smart to have this as a separate class? Or would it work
+ # better to integrate with PyFileImporter? Could cache _is_pkg info.
+ # FileImporter can be changed to return self instead of a specific loader
+ # call. Otherwise _base_path can be calculated on the fly without issue if
+ # it is known whether a module should be treated as a path or package to
+ # minimize stat calls. Could even go as far as to stat the directory the
+ # importer is in to detect changes and then cache all the info about what
+ # files were found (if stating directories is platform-dependent).
+
+ """Load a Python source or bytecode file."""
+
+ def __init__(self, name, path, is_pkg):
+ self._name = name
+ self._is_pkg = is_pkg
+ # Figure out the base path based on whether it was source or bytecode
+ # that was found.
+ try:
+ self._base_path = _path_without_ext(path, imp.PY_SOURCE)
+ except ValueError:
+ self._base_path = _path_without_ext(path, imp.PY_COMPILED)
+
+ def _find_path(self, ext_type):
+ """Find a path from the base path and the specified extension type that
+ exists, returning None if one is not found."""
+ for suffix in suffix_list(ext_type):
+ path = self._base_path + suffix
+ if _path_exists(path):
+ return path
+ else:
+ return None
+
+ def _source_path(self):
+ """Return the path to an existing source file for the module, or None
+ if one cannot be found."""
+ # Not a property so that it is easy to override.
+ return self._find_path(imp.PY_SOURCE)
+
+ def _bytecode_path(self):
+ """Return the path to a bytecode file, or None if one does not
+ exist."""
+ # Not a property for easy overriding.
+ return self._find_path(imp.PY_COMPILED)
+
+ @check_name
+ @get_module
+ def load_module(self, module):
+ """Load a Python source or bytecode module."""
+ source_path = self._source_path()
+ bytecode_path = self._bytecode_path()
+ code_object = self.get_code(module.__name__)
+ module.__file__ = source_path if source_path else bytecode_path
+ module.__loader__ = self
+ if self._is_pkg:
+ module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]]
+ module.__package__ = module.__name__
+ elif '.' in module.__name__:
+ module.__package__ = module.__name__.rsplit('.', 1)[0]
+ else:
+ module.__package__ = None
+ exec(code_object, module.__dict__)
+ return module
+
+ @check_name
+ def source_mtime(self, name):
+ """Return the modification time of the source for the specified
+ module."""
+ source_path = self._source_path()
+ if not source_path:
+ return None
+ return int(_os.stat(source_path).st_mtime)
+
+ @check_name
+ def get_source(self, fullname):
+ """Return the source for the module as a string.
+
+ Return None if the source is not available. Raise ImportError if the
+ laoder cannot handle the specified module.
+
+ """
+ source_path = self._source_path()
+ if source_path is None:
+ return None
+ import tokenize
+ with closing(_fileio_FileIO(source_path, 'r')) as file:
+ encoding, lines = tokenize.detect_encoding(file.readline)
+ # XXX Will fail when passed to compile() if the encoding is
+ # anything other than UTF-8.
+ return open(source_path, encoding=encoding).read()
+
+ @check_name
+ def read_source(self, fullname):
+ """Return the source for the specified module as bytes along with the
+ path where the source came from.
+
+ The returned path is used by 'compile' for error messages.
+
+ """
+ source_path = self._source_path()
+ if source_path is None:
+ return None
+ with closing(_fileio._FileIO(source_path, 'r')) as bytes_file:
+ return bytes_file.read(), source_path
+
+ @check_name
+ def read_bytecode(self, name):
+ """Return the magic number, timestamp, and the module bytecode for the
+ module.
+
+ Raises ImportError (just like get_source) if the laoder cannot handle
+ the module. Returns None if there is no bytecode.
+
+ """
+ path = self._bytecode_path()
+ if path is None:
+ return None
+ file = _fileio._FileIO(path, 'r')
+ try:
+ with closing(file) as bytecode_file:
+ data = bytecode_file.read()
+ return data[:4], marshal._r_long(data[4:8]), data[8:]
+ except AttributeError:
+ return None
+
+ @check_name
+ def write_bytecode(self, name, magic, timestamp, data):
+ """Write out 'data' for the specified module using the specific
+ timestamp, returning a boolean
+ signifying if the write-out actually occurred.
+
+ Raises ImportError (just like get_source) if the specified module
+ cannot be handled by the loader.
+
+ """
+ bytecode_path = self._bytecode_path()
+ if not bytecode_path:
+ bytecode_path = self._base_path + suffix_list(imp.PY_COMPILED)[0]
+ file = _fileio._FileIO(bytecode_path, 'w')
+ try:
+ with closing(file) as bytecode_file:
+ bytecode_file.write(magic)
+ bytecode_file.write(marshal._w_long(timestamp))
+ bytecode_file.write(data)
+ return True
+ except IOError as exc:
+ if exc.errno == errno.EACCES:
+ return False
+ else:
+ raise
+
+ # XXX Take an optional argument to flag whether to write bytecode?
+ @check_name
+ def get_code(self, name):
+ """Return the code object for the module.
+
+ 'self' must implement:
+
+ * read_bytecode(name:str) -> (int, int, bytes) or None
+ Return the magic number, timestamp, and bytecode for the
+ module. None is returned if not bytecode is available.
+
+ * source_mtime(name:str) -> int
+ Return the last modification time for the source of the module.
+ Returns None if their is no source.
+
+ * read_source(name:str) -> (bytes, str)
+ Return the source code for the module and the path to use in
+ the call to 'compile'. Not called if source_mtime returned
+ None.
+
+ * write_bytecode(name:str, magic:bytes, timestamp:int, data:str)
+ Write out bytecode for the module with the specified magic
+ number and timestamp. Not called if sys.dont_write_bytecode is
+ True.
+
+ """
+ # XXX Care enough to make sure this call does not happen if the magic
+ # number is bad?
+ source_timestamp = self.source_mtime(name)
+ # Try to use bytecode if it is available.
+ bytecode_tuple = self.read_bytecode(name)
+ if bytecode_tuple:
+ magic, pyc_timestamp, bytecode = bytecode_tuple
+ try:
+ # Verify that the magic number is valid.
+ if imp.get_magic() != magic:
+ raise ImportError("bad magic number")
+ # 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("bytcode is stale")
+ except ImportError:
+ # 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.
+ # XXX If the bytecode is ill-formed, would it be beneficial to
+ # try for using source if available and issue a warning?
+ return marshal.loads(bytecode)
+ elif source_timestamp is None:
+ raise ImportError("no source or bytecode available to create code "
+ "object for {0!r}".format(name))
+ # Use the source.
+ source, source_path = self.read_source(name)
+ # Convert to universal newlines.
+ line_endings = b'\n'
+ for index, c in enumerate(source):
+ if c == ord(b'\n'):
+ break
+ elif c == ord(b'\r'):
+ line_endings = b'\r'
+ try:
+ if source[index+1] == ord(b'\n'):
+ line_endings += b'\n'
+ except IndexError:
+ pass
+ break
+ if line_endings != b'\n':
+ source = source.replace(line_endings, b'\n')
+ code_object = compile(source, source_path, 'exec', dont_inherit=True)
+ # Generate bytecode and write it out.
+ if not sys.dont_write_bytecode:
+ data = marshal.dumps(code_object)
+ self.write_bytecode(name, imp.get_magic(), source_timestamp, data)
+ return code_object
+
+ def get_data(self, path):
+ """Return the data from path as raw bytes."""
+ return _fileio._FileIO(path, 'r').read()
+
+ @check_name
+ def is_package(self, fullname):
+ """Return a boolean based on whether the module is a package.
+
+ Raises ImportError (like get_source) if the loader cannot handle the
+ package.
+
+ """
+ return self._is_pkg
+
+
+class FileImporter(object):
+
+ """Base class for file importers.
+
+ Subclasses are expected to define the following attributes:
+
+ * _suffixes
+ Sequence of file suffixes whose order will be followed.
+
+ * _possible_package
+ True if importer should check for packages.
+
+ * _loader
+ A callable that takes the module name, a file path, and whether
+ the path points to a package and returns a loader for the module
+ found at that path.
+
+ """
+
+ def __init__(self, path_entry):
+ """Initialize an importer for the passed-in sys.path entry (which is
+ assumed to have already been verified as an existing directory).
+
+ Can be used as an entry on sys.path_hook.
+
+ """
+ self._path_entry = path_entry
+
+ def find_module(self, fullname, path=None):
+ tail_module = fullname.rsplit('.', 1)[-1]
+ package_directory = None
+ if self._possible_package:
+ for ext in self._suffixes:
+ package_directory = _path_join(self._path_entry, tail_module)
+ init_filename = '__init__' + ext
+ package_init = _path_join(package_directory, init_filename)
+ if (_path_isfile(package_init) and
+ _case_ok(self._path_entry, tail_module) and
+ _case_ok(package_directory, init_filename)):
+ return self._loader(fullname, package_init, True)
+ for ext in self._suffixes:
+ file_name = tail_module + ext
+ file_path = _path_join(self._path_entry, file_name)
+ if (_path_isfile(file_path) and
+ _case_ok(self._path_entry, file_name)):
+ return self._loader(fullname, file_path, False)
+ else:
+ # Raise a warning if it matches a directory w/o an __init__ file.
+ if (package_directory is not None and
+ _path_isdir(package_directory) and
+ _case_ok(self._path_entry, tail_module)):
+ _warnings.warn("Not importing directory %s: missing __init__"
+ % package_directory, ImportWarning)
+ return None
+
+
+class ExtensionFileImporter(FileImporter):
+
+ """Importer for extension files."""
+
+ _possible_package = False
+ _loader = _ExtensionFileLoader
+
+ def __init__(self, path_entry):
+ # Assigning to _suffixes here instead of at the class level because
+ # imp is not imported at the time of class creation.
+ self._suffixes = suffix_list(imp.C_EXTENSION)
+ super(ExtensionFileImporter, self).__init__(path_entry)
+
+
+class PyFileImporter(FileImporter):
+
+ """Importer for source/bytecode files."""
+
+ _possible_package = True
+ _loader = _PyFileLoader
+
+ def __init__(self, path_entry):
+ # Lack of imp during class creation means _suffixes is set here.
+ # Make sure that Python source files are listed first! Needed for an
+ # optimization by the loader.
+ self._suffixes = suffix_list(imp.PY_SOURCE)
+ self._suffixes += suffix_list(imp.PY_COMPILED)
+ super(PyFileImporter, self).__init__(path_entry)
+
+
+class ImportLockContext(object):
+
+ """Context manager for the import lock."""
+
+ def __enter__(self):
+ """Acquire the import lock."""
+ imp.acquire_lock()
+
+ def __exit__(self, exc_type, exc_value, exc_traceback):
+ """Release the import lock regardless of any raised exceptions."""
+ imp.release_lock()
+
+
+class Import(object):
+
+ """Class that implements the __import__ interface.
+
+ Backwards compatibility is maintained by extending sys.meta_path
+ interally (for handling built-in and frozen modules) and providing a
+ default path hooks entry for extension modules, .py, and .pyc
+ files. Both are controlled during instance initialization.
+
+ """
+
+ def __init__(self, default_path_hook=None,
+ extended_meta_path=None):
+ """Store a default path hook entry and a sequence to internally extend
+ sys.meta_path by (passing in None uses default importers)."""
+ if extended_meta_path is None:
+ self.extended_meta_path = BuiltinImporter(), FrozenImporter()
+ else:
+ self.extended_meta_path = extended_meta_path
+ self.default_path_hook = default_path_hook
+ if self.default_path_hook is None:
+ # Create a handler to deal with extension modules, .py, and .pyc
+ # files. Built-in and frozen modules are handled by sys.meta_path
+ # entries.
+ importers = [ExtensionFileImporter, PyFileImporter]
+ self.default_path_hook = chaining_fs_path_hook(*importers)
+
+ def _search_meta_path(self, name, path=None):
+ """Check the importers on sys.meta_path for a loader along with the
+ extended meta path sequence stored within this instance.
+
+ The extended sys.meta_path entries are searched after the entries on
+ sys.meta_path.
+
+ """
+ for entry in (tuple(sys.meta_path) + self.extended_meta_path):
+ loader = entry.find_module(name, path)
+ if loader:
+ return loader
+ else:
+ raise ImportError("No module named %s" % name)
+
+ def _sys_path_importer(self, path_entry):
+ """Return the importer for the specified path, from
+ sys.path_importer_cache if possible.
+
+ If None is stored in sys.path_importer_cache then use the default path
+ hook.
+
+ """
+ try:
+ # See if an importer is cached.
+ importer = sys.path_importer_cache[path_entry]
+ # If None was returned, use default importer factory.
+ if importer is None:
+ return self.default_path_hook(path_entry)
+ else:
+ return importer
+ except KeyError:
+ # No cached importer found; try to get a new one from
+ # sys.path_hooks or imp.NullImporter.
+ for importer_factory in (sys.path_hooks + [imp.NullImporter]):
+ try:
+ importer = importer_factory(path_entry)
+ sys.path_importer_cache[path_entry] = importer
+ return importer
+ except ImportError:
+ continue
+ else:
+ # No importer factory on sys.path_hooks works; use the default
+ # importer factory and store None in sys.path_importer_cache.
+ try:
+ importer = self.default_path_hook(path_entry)
+ sys.path_importer_cache[path_entry] = None
+ return importer
+ except ImportError:
+ raise ImportError("no importer found for %s" % path_entry)
+
+ def _search_std_path(self, name, path=None):
+ """Check sys.path or 'path' (depending if 'path' is set) for the
+ named module and return its loader."""
+ if path:
+ search_paths = path
+ else:
+ search_paths = sys.path
+ for entry in search_paths:
+ try:
+ importer = self._sys_path_importer(entry)
+ except ImportError:
+ continue
+ loader = importer.find_module(name)
+ if loader:
+ return loader
+ else:
+ raise ImportError("No module named %s" % name)
+
+ def module_from_cache(self, name):
+ """Try to return the named module from sys.modules.
+
+ Return False if the module is not in the cache.
+ """
+ if name in sys.modules:
+ return sys.modules[name]
+ else:
+ return False
+
+ def post_import(self, module):
+ """Perform any desired post-import processing on the module."""
+ return module
+
+ def _import_module(self, name, path=None):
+ """Import the specified module with no handling of parent modules.
+
+ If None is set for a value in sys.modules (to signify that a relative
+ import was attempted and failed) then ImportError is raised.
+
+ """
+ cached_module = self.module_from_cache(name)
+ if cached_module is not False:
+ if cached_module is None:
+ raise ImportError("relative import redirect")
+ else:
+ return cached_module
+ try:
+ # Attempt to find a loader on sys.meta_path.
+ loader = self._search_meta_path(name, path)
+ except ImportError:
+ # sys.meta_path search failed. Attempt to find a loader on
+ # sys.path. If this fails then module cannot be found.
+ loader = self._search_std_path(name, path)
+ # A loader was found. It is the loader's responsibility to have put an
+ # entry in sys.modules.
+ module = self.post_import(loader.load_module(name))
+ # 'module' could be something like None.
+ if not hasattr(module, '__name__'):
+ return module
+ # Set __package__.
+ if not hasattr(module, '__package__') or module.__package__ is None:
+ if hasattr(module, '__path__'):
+ module.__package__ = module.__name__
+ elif '.' in module.__name__:
+ pkg_name = module.__name__.rsplit('.', 1)[0]
+ module.__package__ = pkg_name
+ else:
+ module.__package__ = None
+ return module
+
+
+ def _import_full_module(self, name):
+ """Import a module and set it on its parent if needed."""
+ path_list = None
+ parent_name = name.rsplit('.', 1)[0]
+ parent = None
+ if parent_name != name:
+ parent = sys.modules[parent_name]
+ try:
+ path_list = parent.__path__
+ except AttributeError:
+ pass
+ self._import_module(name, path_list)
+ module = sys.modules[name]
+ if parent:
+ tail = name.rsplit('.', 1)[-1]
+ setattr(parent, tail, module)
+
+ def _find_package(self, name, has_path):
+ """Return the package that the caller is in or None."""
+ if has_path:
+ return name
+ elif '.' in name:
+ return name.rsplit('.', 1)[0]
+ else:
+ return None
+
+ @staticmethod
+ def _resolve_name(name, package, level):
+ """Return the absolute name of the module to be imported."""
+ level -= 1
+ try:
+ if package.count('.') < level:
+ raise ValueError("attempted relative import beyond top-level "
+ "package")
+ except AttributeError:
+ raise ValueError("__package__ not set to a string")
+ base = package.rsplit('.', level)[0]
+ if name:
+ return "{0}.{1}".format(base, name)
+ else:
+ return base
+
+ def _return_module(self, absolute_name, relative_name, fromlist):
+ """Return the proper module based on what module was requested (and its
+ absolute module name), who is requesting it, and whether any specific
+ attributes were specified.
+
+ The semantics of this method revolve around 'fromlist'. When it is
+ empty, the module up to the first dot is to be returned. When the
+ module being requested is an absolute name this is simple (and
+ relative_name is an empty string). But if the requested module was
+ a relative import (as signaled by relative_name having a non-false
+ value), then the name up to the first dot in the relative name resolved
+ to an absolute name is to be returned.
+
+ When fromlist is not empty and the module being imported is a package,
+ then the values
+ in fromlist need to be checked for. If a value is not a pre-existing
+ attribute a relative import is attempted. If it fails then suppressed
+ the failure silently.
+
+ """
+ if not fromlist:
+ if relative_name:
+ absolute_base = absolute_name.rpartition(relative_name)[0]
+ relative_head = relative_name.split('.', 1)[0]
+ to_return = absolute_base + relative_head
+ else:
+ to_return = absolute_name.split('.', 1)[0]
+ return sys.modules[to_return]
+ # When fromlist is not empty, return the actual module specified in
+ # the import.
+ else:
+ module = sys.modules[absolute_name]
+ if hasattr(module, '__path__') and hasattr(module, '__name__'):
+ # When fromlist has a value and the imported module is a
+ # package, then if a name in fromlist is not found as an
+ # attribute on module, try a relative import to find it.
+ # Failure is fine and the exception is suppressed.
+ check_for = list(fromlist)
+ if '*' in check_for and hasattr(module, '__all__'):
+ check_for.extend(module.__all__)
+ for item in check_for:
+ if item == '*':
+ continue
+ if not hasattr(module, item):
+ resolved_name = self._resolve_name(item,
+ module.__name__, 1)
+ try:
+ self._import_full_module(resolved_name)
+ except ImportError:
+ pass
+ return module
+
+ def __call__(self, name, globals={}, locals={}, fromlist=[], level=0):
+ """Import a module.
+
+ The 'name' argument is the name of the module to be imported (e.g.,
+ 'foo' in ``import foo`` or ``from foo import ...``).
+
+ 'globals' and 'locals' are the global and local namespace dictionaries
+ of the module where the import statement appears. 'globals' is used to
+ introspect the __path__ and __name__ attributes of the module making
+ the call. 'local's is ignored.
+
+ 'fromlist' lists any specific objects that are to eventually be put
+ into the namespace (e.g., ``from for.bar import baz`` would have 'baz'
+ in the fromlist, and this includes '*'). An entry of '*' will lead to
+ a check for __all__ being defined on the module. If it is defined then
+ the values in __all__ will be checked to make sure that all values are
+ attributes on the module, attempting a module import relative to 'name'
+ to set that attribute.
+
+ When 'name' is a dotted name, there are two different situations to
+ consider for the return value. One is when the fromlist is empty.
+ In this situation the import statement imports and returns the name up
+ to the first dot. All subsequent names are imported but set as
+ attributes as needed on parent modules. When fromlist is not empty
+ then the module represented by the full dotted name is returned.
+
+ 'level' represents possible relative imports.
+ A value of 0 is for absolute module names. Any positive value
+ represents the number of dots listed in the relative import statement
+ (e.g. has a value of 2 for ``from .. import foo``).
+
+ """
+ if not name and level < 1:
+ raise ValueError("Empty module name")
+ is_pkg = True if '__path__' in globals else False
+ caller_name = globals.get('__name__')
+ package = globals.get('__package__')
+ if caller_name and not package:
+ package = self._find_package(caller_name, '__path__' in globals)
+ if package and package not in sys.modules:
+ if not hasattr(package, 'rsplit'):
+ raise ValueError("__package__ not set to a string")
+ msg = ("Parent module {0!r} not loaded, "
+ "cannot perform relative import")
+ raise SystemError(msg.format(package))
+ with ImportLockContext():
+ if level:
+ imported_name = self._resolve_name(name, package, level)
+ else:
+ imported_name = name
+ parent_name = imported_name.rsplit('.', 1)[0]
+ if parent_name != imported_name and parent_name not in sys.modules:
+ self.__call__(parent_name, level=0)
+ # This call will also handle setting the attribute on the
+ # package.
+ self._import_full_module(imported_name)
+ relative_name = '' if imported_name == name else name
+ return self._return_module(imported_name, relative_name, fromlist)
+
+# XXX Eventually replace with a proper __all__ value (i.e., don't expose os
+# replacements but do expose _ExtensionFileLoader, etc. for testing).
+__all__ = [obj for obj in globals().keys() if not obj.startswith('__')]
diff --git a/Lib/importlib/test/__init__.py b/Lib/importlib/test/__init__.py
new file mode 100644
index 0000000..6492707
--- /dev/null
+++ b/Lib/importlib/test/__init__.py
@@ -0,0 +1,26 @@
+import os.path
+import sys
+import unittest
+
+
+def test_suite(package=__package__, directory=os.path.dirname(__file__)):
+ suite = unittest.TestSuite()
+ for name in os.listdir(directory):
+ path = os.path.join(directory, name)
+ if os.path.isfile(path) and name.startswith('test_'):
+ submodule_name = os.path.splitext(name)[0]
+ module_name = "{0}.{1}".format(package, submodule_name)
+ __import__(module_name, level=0)
+ module_tests = unittest.findTestCases(sys.modules[module_name])
+ suite.addTest(module_tests)
+ elif os.path.isdir(path):
+ package_name = "{0}.{1}".format(package, name)
+ __import__(package_name, level=0)
+ package_tests = getattr(sys.modules[package_name], 'test_suite')()
+ suite.addTest(package_tests)
+ return suite
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite('importlib.test'))
diff --git a/Lib/importlib/test/builtin/__init__.py b/Lib/importlib/test/builtin/__init__.py
new file mode 100644
index 0000000..31a3b5f
--- /dev/null
+++ b/Lib/importlib/test/builtin/__init__.py
@@ -0,0 +1,12 @@
+import importlib.test
+import os
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.builtin', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/builtin/test_finder.py b/Lib/importlib/test/builtin/test_finder.py
new file mode 100644
index 0000000..5262aaa
--- /dev/null
+++ b/Lib/importlib/test/builtin/test_finder.py
@@ -0,0 +1,36 @@
+import importlib
+from .. import support
+
+import sys
+import unittest
+
+class FinderTests(unittest.TestCase):
+
+ """Test find_module() for built-in modules."""
+
+ assert 'errno' in sys.builtin_module_names
+ name = 'errno'
+
+ find_module = staticmethod(lambda name, path=None:
+ importlib.BuiltinImporter().find_module(name, path))
+
+
+ def test_find_module(self):
+ # Common case.
+ with support.uncache(self.name):
+ self.assert_(self.find_module(self.name))
+
+ def test_ignore_path(self):
+ # The value for 'path' should always trigger a failed import.
+ with support.uncache(self.name):
+ self.assert_(self.find_module(self.name, ['pkg']) is None)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/builtin/test_loader.py b/Lib/importlib/test/builtin/test_loader.py
new file mode 100644
index 0000000..5aa3d79
--- /dev/null
+++ b/Lib/importlib/test/builtin/test_loader.py
@@ -0,0 +1,52 @@
+import importlib
+from .. import support
+
+import sys
+import types
+import unittest
+
+
+class LoaderTests(unittest.TestCase):
+
+ """Test load_module() for built-in modules."""
+
+ assert 'errno' in sys.builtin_module_names
+ name = 'errno'
+
+ verification = {'__name__': 'errno', '__package__': None}
+
+ def verify(self, module):
+ """Verify that the module matches against what it should have."""
+ self.assert_(isinstance(module, types.ModuleType))
+ for attr, value in self.verification.items():
+ self.assertEqual(getattr(module, attr), value)
+ self.assert_(module.__name__ in sys.modules)
+
+ load_module = staticmethod(lambda name:
+ importlib.BuiltinImporter().load_module(name))
+
+ def test_load_module(self):
+ # Common case.
+ with support.uncache(self.name):
+ module = self.load_module(self.name)
+ self.verify(module)
+
+ def test_nonexistent(self):
+ name = 'dssdsdfff'
+ assert name not in sys.builtin_module_names
+ self.assertRaises(ImportError, self.load_module, name)
+
+ def test_already_imported(self):
+ # Using the name of a module already imported but not a built-in should
+ # still fail.
+ assert hasattr(importlib, '__file__') # Not a built-in.
+ self.assertRaises(ImportError, self.load_module, 'importlib')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(LoaderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/__init__.py b/Lib/importlib/test/extension/__init__.py
new file mode 100644
index 0000000..2ec5840
--- /dev/null
+++ b/Lib/importlib/test/extension/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.extension', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/extension/test_case_sensitivity.py b/Lib/importlib/test/extension/test_case_sensitivity.py
new file mode 100644
index 0000000..c566b23
--- /dev/null
+++ b/Lib/importlib/test/extension/test_case_sensitivity.py
@@ -0,0 +1,39 @@
+import sys
+from test import support as test_support
+import unittest
+import importlib
+from . import test_path_hook
+
+
+class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
+
+ def find_module(self):
+ good_name = test_path_hook.NAME
+ bad_name = good_name.upper()
+ assert good_name != bad_name
+ finder = importlib.ExtensionFileImporter(test_path_hook.PATH)
+ return finder.find_module(bad_name)
+
+ def test_case_sensitive(self):
+ with test_support.EnvironmentVarGuard() as env:
+ env.unset('PYTHONCASEOK')
+ loader = self.find_module()
+ self.assert_(loader is None)
+
+ def test_case_insensitivity(self):
+ with test_support.EnvironmentVarGuard() as env:
+ env.set('PYTHONCASEOK', '1')
+ loader = self.find_module()
+ self.assert_(hasattr(loader, 'load_module'))
+
+
+
+
+def test_main():
+ if sys.platform not in ('win32', 'darwin', 'cygwin'):
+ return
+ test_support.run_unittest(ExtensionModuleCaseSensitivityTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/test_finder.py b/Lib/importlib/test/extension/test_finder.py
new file mode 100644
index 0000000..5766910
--- /dev/null
+++ b/Lib/importlib/test/extension/test_finder.py
@@ -0,0 +1,29 @@
+import importlib
+from . import test_path_hook
+
+import unittest
+
+class FinderTests(unittest.TestCase):
+
+ """Test the finder for extension modules."""
+
+ def find_module(self, fullname):
+ importer = importlib.ExtensionFileImporter(test_path_hook.PATH)
+ return importer.find_module(fullname)
+
+ def test_success(self):
+ self.assert_(self.find_module(test_path_hook.NAME))
+
+ def test_failure(self):
+ self.assert_(self.find_module('asdfjkl;') is None)
+
+ # XXX Raise an exception if someone tries to use the 'path' argument?
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/test_loader.py b/Lib/importlib/test/extension/test_loader.py
new file mode 100644
index 0000000..4f2a0d8
--- /dev/null
+++ b/Lib/importlib/test/extension/test_loader.py
@@ -0,0 +1,37 @@
+import importlib
+from . import test_path_hook
+from .. import support
+
+import sys
+import unittest
+
+
+class LoaderTests(unittest.TestCase):
+
+ """Test load_module() for extension modules."""
+
+ def load_module(self, fullname):
+ loader = importlib._ExtensionFileLoader(test_path_hook.NAME,
+ test_path_hook.FILEPATH,
+ False)
+ return loader.load_module(fullname)
+
+ def test_success(self):
+ with support.uncache(test_path_hook.NAME):
+ module = self.load_module(test_path_hook.NAME)
+ for attr, value in [('__name__', test_path_hook.NAME),
+ ('__file__', test_path_hook.FILEPATH)]:
+ self.assertEqual(getattr(module, attr), value)
+ self.assert_(test_path_hook.NAME in sys.modules)
+
+ def test_failure(self):
+ self.assertRaises(ImportError, self.load_module, 'asdfjkl;')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(LoaderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/test_path_hook.py b/Lib/importlib/test/extension/test_path_hook.py
new file mode 100644
index 0000000..f1774fd
--- /dev/null
+++ b/Lib/importlib/test/extension/test_path_hook.py
@@ -0,0 +1,50 @@
+import importlib
+
+import collections
+import imp
+from os import path
+import sys
+import unittest
+
+
+PATH = None
+EXT = None
+FILENAME = None
+NAME = '_testcapi'
+_file_exts = [x[0] for x in imp.get_suffixes() if x[2] == imp.C_EXTENSION]
+try:
+ for PATH in sys.path:
+ for EXT in _file_exts:
+ FILENAME = NAME + EXT
+ FILEPATH = path.join(PATH, FILENAME)
+ if path.exists(path.join(PATH, FILENAME)):
+ raise StopIteration
+ else:
+ PATH = EXT = FILENAME = FILEPATH = None
+except StopIteration:
+ pass
+del _file_exts
+
+
+class PathHookTests(unittest.TestCase):
+
+ """Test the path hook for extension modules."""
+ # XXX Should it only succeed for pre-existing directories?
+ # XXX Should it only work for directories containing an extension module?
+
+ def hook(self, entry):
+ return importlib.ExtensionFileImporter(entry)
+
+ def test_success(self):
+ # Path hook should handle a directory where a known extension module
+ # exists.
+ self.assert_(hasattr(self.hook(PATH), 'find_module'))
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(PathHookTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/finder_tests.py b/Lib/importlib/test/finder_tests.py
new file mode 100644
index 0000000..9bbf85a
--- /dev/null
+++ b/Lib/importlib/test/finder_tests.py
@@ -0,0 +1,39 @@
+# top-level.
+# Package.
+# module in pacakge.
+# Package within a package.
+# At least one tests with 'path'.
+# Module that is not handled.
+
+import unittest
+
+
+class FinderTests(unittest.TestCase):
+
+ """Basic tests for a finder to pass."""
+
+ def test_module(self):
+ # Test importing a top-level module.
+ raise NotImplementedError
+
+ def test_package(self):
+ # Test importing a package.
+ raise NotImplementedError
+
+ def test_module_in_package(self):
+ # Test importing a module contained within a package.
+ # A value for 'path' should be used if for a meta_path finder.
+ raise NotImplementedError
+
+ def test_package_in_package(self):
+ # Test importing a subpackage.
+ # A value for 'path' should be used if for a meta_path finder.
+ raise NotImplementedError
+
+ def test_package_over_module(self):
+ # Test that packages are chosen over modules.
+ raise NotImplementedError
+
+ def test_failure(self):
+ # Test trying to find a module that cannot be handled.
+ raise NotImplementedError
diff --git a/Lib/importlib/test/frozen/__init__.py b/Lib/importlib/test/frozen/__init__.py
new file mode 100644
index 0000000..2945eeb
--- /dev/null
+++ b/Lib/importlib/test/frozen/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.frozen', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/frozen/support.py b/Lib/importlib/test/frozen/support.py
new file mode 100644
index 0000000..e08b89e
--- /dev/null
+++ b/Lib/importlib/test/frozen/support.py
@@ -0,0 +1,24 @@
+import sys
+
+
+class Null:
+
+ """Just absorb what is given."""
+
+ def __getattr__(self):
+ return lambda *args, **kwargs: None
+
+
+class SilenceStdout:
+
+ """Silence sys.stdout."""
+
+ def setUp(self):
+ """Substitute sys.stdout with something that does not print to the
+ screen thanks to what bytecode is frozen."""
+ sys.stdout = Null()
+ super().setUp()
+
+ def tearDown(self):
+ sys.stdout = sys.__stdout__
+ super().tearDown()
diff --git a/Lib/importlib/test/frozen/test_finder.py b/Lib/importlib/test/frozen/test_finder.py
new file mode 100644
index 0000000..2541019
--- /dev/null
+++ b/Lib/importlib/test/frozen/test_finder.py
@@ -0,0 +1,44 @@
+import importlib
+from ..builtin import test_finder
+from .. import support
+
+import unittest
+
+
+class FinderTests(test_finder.FinderTests):
+
+ """Test finding frozen modules."""
+
+ def find(self, name, path=None):
+ finder = importlib.FrozenImporter()
+ return finder.find_module(name, path)
+
+
+ def test_module(self):
+ name = '__hello__'
+ loader = self.find(name)
+ self.assert_(hasattr(loader, 'load_module'))
+
+ def test_package(self):
+ loader = self.find('__phello__')
+ self.assert_(hasattr(loader, 'load_module'))
+
+ def test_module_in_package(self):
+ loader = self.find('__phello__.spam', ['__phello__'])
+ self.assert_(hasattr(loader, 'load_module'))
+
+ def test_package_in_package(self):
+ pass
+
+ def test_failure(self):
+ loader = self.find('<not real>')
+ self.assert_(loader is None)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/frozen/test_loader.py b/Lib/importlib/test/frozen/test_loader.py
new file mode 100644
index 0000000..b37ae7f
--- /dev/null
+++ b/Lib/importlib/test/frozen/test_loader.py
@@ -0,0 +1,27 @@
+import importlib
+from ..builtin import test_loader
+
+
+class LoaderTests(test_loader.LoaderTests):
+
+ name = '__phello__'
+ load_module = staticmethod(lambda name:
+ importlib.FrozenImporter().load_module(name))
+ verification = {'__name__': '__phello__', '__file__': '<frozen>',
+ '__package__': None, '__path__': ['__phello__']}
+
+
+class SubmoduleLoaderTests(LoaderTests):
+
+ name = '__phello__.spam'
+ verification = {'__name__': '__phello__.spam', '__file__': '<frozen>',
+ '__package__': None}
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(LoaderTests, SubmoduleLoaderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/__init__.py b/Lib/importlib/test/import_/__init__.py
new file mode 100644
index 0000000..fdf7661
--- /dev/null
+++ b/Lib/importlib/test/import_/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.import_', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/import_/test___package__.py b/Lib/importlib/test/import_/test___package__.py
new file mode 100644
index 0000000..64dab3a
--- /dev/null
+++ b/Lib/importlib/test/import_/test___package__.py
@@ -0,0 +1,110 @@
+"""PEP 366 ("Main module explicit relative imports") specifies the
+semantics for the __package__ attribute on modules. This attribute is
+used, when available, to detect which package a module belongs to (instead
+of using the typical __path__/__name__ test).
+
+"""
+import unittest
+from .. import support
+
+
+class Using__package__(unittest.TestCase):
+
+ """Use of __package__ supercedes the use of __name__/__path__ to calculate
+ what package a module belongs to. The basic algorithm is [__package__]::
+
+ def resolve_name(name, package, level):
+ level -= 1
+ base = package.rsplit('.', level)[0]
+ return '{0}.{1}'.format(base, name)
+
+ But since there is no guarantee that __package__ has been set, there has to
+ be a way to calculate the attribute's value [__name__]::
+
+ def calc_package(caller_name, has___path__):
+ if has__path__:
+ return caller_name
+ else:
+ return caller_name.rsplit('.', 1)[0]
+
+ Then the normal algorithm for relative name imports can proceed as if
+ __package__ had been set.
+
+ """
+
+ def test_using___package__(self):
+ # [__package__]
+ with support.mock_modules('pkg.__init__', 'pkg.fake') as importer:
+ with support.import_state(meta_path=[importer]):
+ support.import_('pkg.fake')
+ module = support.import_('', globals={'__package__': 'pkg.fake'},
+ fromlist=['attr'], level=2)
+ self.assertEquals(module.__name__, 'pkg')
+
+ def test_using___name__(self):
+ # [__name__]
+ with support.mock_modules('pkg.__init__', 'pkg.fake') as importer:
+ with support.import_state(meta_path=[importer]):
+ support.import_('pkg.fake')
+ module = support.import_('',
+ globals={'__name__': 'pkg.fake',
+ '__path__': []},
+ fromlist=['attr'], level=2)
+ self.assertEquals(module.__name__, 'pkg')
+
+ def test_bad__package__(self):
+ globals = {'__package__': '<not real>'}
+ self.assertRaises(SystemError, support.import_,'', globals, {},
+ ['relimport'], 1)
+
+ def test_bunk__package__(self):
+ globals = {'__package__': 42}
+ self.assertRaises(ValueError, support.import_, '', globals, {},
+ ['relimport'], 1)
+
+
+class Setting__package__(unittest.TestCase):
+
+ """Because __package__ is a new feature, it is not always set by a loader.
+ Import will set it as needed to help with the transition to relying on
+ __package__.
+
+ For a top-level module, __package__ is set to None [top-level]. For a
+ package __name__ is used for __package__ [package]. For submodules the
+ value is __name__.rsplit('.', 1)[0] [submodule].
+
+ """
+
+ # [top-level]
+ def test_top_level(self):
+ with support.mock_modules('top_level') as mock:
+ with support.import_state(meta_path=[mock]):
+ del mock['top_level'].__package__
+ module = support.import_('top_level')
+ self.assert_(module.__package__ is None)
+
+ # [package]
+ def test_package(self):
+ with support.mock_modules('pkg.__init__') as mock:
+ with support.import_state(meta_path=[mock]):
+ del mock['pkg'].__package__
+ module = support.import_('pkg')
+ self.assertEqual(module.__package__, 'pkg')
+
+ # [submodule]
+ def test_submodule(self):
+ with support.mock_modules('pkg.__init__', 'pkg.mod') as mock:
+ with support.import_state(meta_path=[mock]):
+ del mock['pkg.mod'].__package__
+ pkg = support.import_('pkg.mod')
+ module = getattr(pkg, 'mod')
+ self.assertEqual(module.__package__, 'pkg')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(Using__package__, Setting__package__)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_caching.py b/Lib/importlib/test/import_/test_caching.py
new file mode 100644
index 0000000..57690d4
--- /dev/null
+++ b/Lib/importlib/test/import_/test_caching.py
@@ -0,0 +1,75 @@
+"""Test that sys.modules is used properly by import."""
+from ..support import import_, mock_modules, importlib_only, import_state
+
+import sys
+from types import MethodType
+import unittest
+
+
+class UseCache(unittest.TestCase):
+
+ """When it comes to sys.modules, import prefers it over anything else.
+
+ Once a name has been resolved, sys.modules is checked to see if it contains
+ the module desired. If so, then it is returned [use cache]. If it is not
+ found, then the proper steps are taken to perform the import, but
+ sys.modules is still used to return the imported module (e.g., not what a
+ loader returns) [from cache on return]. This also applies to imports of
+ things contained within a package and thus get assigned as an attribute
+ [from cache to attribute] or pulled in thanks to a fromlist import
+ [from cache for fromlist].
+
+ """
+ def test_using_cache(self):
+ # [use cache]
+ module_to_use = "some module found!"
+ sys.modules['some_module'] = module_to_use
+ module = import_('some_module')
+ self.assertEqual(id(module_to_use), id(module))
+
+ def create_mock(self, *names, return_=None):
+ mock = mock_modules(*names)
+ original_load = mock.load_module
+ def load_module(self, fullname):
+ original_load(fullname)
+ return return_
+ mock.load_module = MethodType(load_module, mock)
+ return mock
+
+ # __import__ inconsistent between loaders and built-in import when it comes
+ # to when to use the module in sys.modules and when not to.
+ @importlib_only
+ def test_using_cache_after_loader(self):
+ # [from cache on return]
+ with self.create_mock('module') as mock:
+ with import_state(meta_path=[mock]):
+ module = import_('module')
+ self.assertEquals(id(module), id(sys.modules['module']))
+
+ # See test_using_cache_after_loader() for reasoning.
+ @importlib_only
+ def test_using_cache_for_assigning_to_attribute(self):
+ # [from cache to attribute]
+ with self.create_mock('pkg.__init__', 'pkg.module') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('pkg.module')
+ self.assert_(hasattr(module, 'module'))
+ self.assert_(id(module.module), id(sys.modules['pkg.module']))
+
+ # See test_using_cache_after_loader() for reasoning.
+ @importlib_only
+ def test_using_cache_for_fromlist(self):
+ # [from cache for fromlist]
+ with self.create_mock('pkg.__init__', 'pkg.module') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('pkg', fromlist=['module'])
+ self.assert_(hasattr(module, 'module'))
+ self.assertEquals(id(module.module), id(sys.modules['pkg.module']))
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(UseCache)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_fromlist.py b/Lib/importlib/test/import_/test_fromlist.py
new file mode 100644
index 0000000..884b516
--- /dev/null
+++ b/Lib/importlib/test/import_/test_fromlist.py
@@ -0,0 +1,116 @@
+"""Test that the semantics relating to the 'fromlist' argument are correct."""
+from ..support import import_, mock_modules, import_state
+
+import unittest
+
+class ReturnValue(unittest.TestCase):
+
+ """The use of fromlist influences what import returns.
+
+ If direct ``import ...`` statement is used, the root module or package is
+ returned [import return]. But if fromlist is set, then the specified module
+ is actually returned (whether it is a relative import or not)
+ [from return].
+
+ """
+
+ def test_return_from_import(self):
+ # [import return]
+ with mock_modules('pkg.__init__', 'pkg.module') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('pkg.module')
+ self.assertEquals(module.__name__, 'pkg')
+
+ def test_return_from_from_import(self):
+ # [from return]
+ with mock_modules('pkg.__init__', 'pkg.module')as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('pkg.module', fromlist=['attr'])
+ self.assertEquals(module.__name__, 'pkg.module')
+
+
+class HandlingFromlist(unittest.TestCase):
+
+ """Using fromlist triggers different actions based on what is being asked
+ of it.
+
+ If fromlist specifies an object on a module, nothing special happens
+ [object case]. This is even true if the object does not exist [bad object].
+
+ If a package is being imported, then what is listed in fromlist may be
+ treated as a module to be imported [module]. But once again, even if
+ something in fromlist does not exist as a module, no error is thrown
+ [no module]. And this extends to what is contained in __all__ when '*' is
+ imported [using *]. And '*' does not need to be the only name in the
+ fromlist [using * with others].
+
+ """
+
+ def test_object(self):
+ # [object case]
+ with mock_modules('module') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('module', fromlist=['attr'])
+ self.assertEquals(module.__name__, 'module')
+
+ def test_unexistent_object(self):
+ # [bad object]
+ with mock_modules('module') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('module', fromlist=['non_existent'])
+ self.assertEquals(module.__name__, 'module')
+ self.assert_(not hasattr(module, 'non_existent'))
+
+ def test_module_from_package(self):
+ # [module]
+ with mock_modules('pkg.__init__', 'pkg.module') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('pkg', fromlist=['module'])
+ self.assertEquals(module.__name__, 'pkg')
+ self.assert_(hasattr(module, 'module'))
+ self.assertEquals(module.module.__name__, 'pkg.module')
+
+ def test_no_module_from_package(self):
+ # [no module]
+ with mock_modules('pkg.__init__') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('pkg', fromlist='non_existent')
+ self.assertEquals(module.__name__, 'pkg')
+ self.assert_(not hasattr(module, 'non_existent'))
+
+ def test_empty_string(self):
+ with mock_modules('pkg.__init__', 'pkg.mod') as importer:
+ with import_state(meta_path=[importer]):
+ module = import_('pkg.mod', fromlist=[''])
+ self.assertEquals(module.__name__, 'pkg.mod')
+
+ def test_using_star(self):
+ # [using *]
+ with mock_modules('pkg.__init__', 'pkg.module') as mock:
+ with import_state(meta_path=[mock]):
+ mock['pkg'].__all__ = ['module']
+ module = import_('pkg', fromlist=['*'])
+ self.assertEquals(module.__name__, 'pkg')
+ self.assert_(hasattr(module, 'module'))
+ self.assertEqual(module.module.__name__, 'pkg.module')
+
+ def test_star_with_others(self):
+ # [using * with others]
+ context = mock_modules('pkg.__init__', 'pkg.module1', 'pkg.module2')
+ with context as mock:
+ with import_state(meta_path=[mock]):
+ mock['pkg'].__all__ = ['module1']
+ module = import_('pkg', fromlist=['module2', '*'])
+ self.assertEquals(module.__name__, 'pkg')
+ self.assert_(hasattr(module, 'module1'))
+ self.assert_(hasattr(module, 'module2'))
+ self.assertEquals(module.module1.__name__, 'pkg.module1')
+ self.assertEquals(module.module2.__name__, 'pkg.module2')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(ReturnValue, HandlingFromlist)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_meta_path.py b/Lib/importlib/test/import_/test_meta_path.py
new file mode 100644
index 0000000..37f40d7
--- /dev/null
+++ b/Lib/importlib/test/import_/test_meta_path.py
@@ -0,0 +1,99 @@
+from ..support import import_state, mock_modules, import_
+
+from contextlib import nested
+from types import MethodType
+import unittest
+
+
+class CallingOrder(unittest.TestCase):
+
+ """Calls to the importers on sys.meta_path happen in order that they are
+ specified in the sequence, starting with the first importer
+ [first called], and then continuing on down until one is found that doesn't
+ return None [continuing]."""
+
+
+ def test_first_called(self):
+ # [first called]
+ mod = 'top_level'
+ first = mock_modules(mod)
+ second = mock_modules(mod)
+ with nested(mock_modules(mod), mock_modules(mod)) as (first, second):
+ first.modules[mod] = 42
+ second.modules[mod] = -13
+ with import_state(meta_path=[first, second]):
+ self.assertEquals(import_(mod), 42)
+
+ def test_continuing(self):
+ # [continuing]
+ mod_name = 'for_real'
+ first = mock_modules('nonexistent')
+ second = mock_modules(mod_name)
+ with nested(first, second):
+ first.find_module = lambda self, fullname, path=None: None
+ second.modules[mod_name] = 42
+ with import_state(meta_path=[first, second]):
+ self.assertEquals(import_(mod_name), 42)
+
+
+class CallSignature(unittest.TestCase):
+
+ """If there is no __path__ entry on the parent module, then 'path' is None
+ [no path]. Otherwise, the value for __path__ is passed in for the 'path'
+ argument [path set]."""
+
+ def log(self, fxn):
+ log = []
+ def wrapper(self, *args, **kwargs):
+ log.append([args, kwargs])
+ return fxn(*args, **kwargs)
+ return log, wrapper
+
+
+ def test_no_path(self):
+ # [no path]
+ mod_name = 'top_level'
+ assert '.' not in mod_name
+ with mock_modules(mod_name) as importer:
+ log, wrapped_call = self.log(importer.find_module)
+ importer.find_module = MethodType(wrapped_call, importer)
+ with import_state(meta_path=[importer]):
+ import_(mod_name)
+ assert len(log) == 1
+ args = log[0][0]
+ kwargs = log[0][1]
+ # Assuming all arguments are positional.
+ self.assertEquals(len(args), 2)
+ self.assertEquals(len(kwargs), 0)
+ self.assertEquals(args[0], mod_name)
+ self.assert_(args[1] is None)
+
+ def test_with_path(self):
+ # [path set]
+ pkg_name = 'pkg'
+ mod_name = pkg_name + '.module'
+ path = [42]
+ assert '.' in mod_name
+ with mock_modules(pkg_name+'.__init__', mod_name) as importer:
+ importer.modules[pkg_name].__path__ = path
+ log, wrapped_call = self.log(importer.find_module)
+ importer.find_module = MethodType(wrapped_call, importer)
+ with import_state(meta_path=[importer]):
+ import_(mod_name)
+ assert len(log) == 2
+ args = log[1][0]
+ kwargs = log[1][1]
+ # Assuming all arguments are positional.
+ self.assert_(not kwargs)
+ self.assertEquals(args[0], mod_name)
+ self.assert_(args[1] is path)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(CallingOrder, CallSignature)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_packages.py b/Lib/importlib/test/import_/test_packages.py
new file mode 100644
index 0000000..013bbdc
--- /dev/null
+++ b/Lib/importlib/test/import_/test_packages.py
@@ -0,0 +1,29 @@
+import sys
+import unittest
+import importlib
+from .. import support
+
+
+class ParentModuleTests(unittest.TestCase):
+
+ """Importing a submodule should import the parent modules."""
+
+ def test_import_parent(self):
+ with support.mock_modules('pkg.__init__', 'pkg.module') as mock:
+ with support.import_state(meta_path=[mock]):
+ module = support.import_('pkg.module')
+ self.assert_('pkg' in sys.modules)
+
+ def test_bad_parent(self):
+ with support.mock_modules('pkg.module') as mock:
+ with support.import_state(meta_path=[mock]):
+ self.assertRaises(ImportError, support.import_, 'pkg.module')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(ParentModuleTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_path.py b/Lib/importlib/test/import_/test_path.py
new file mode 100644
index 0000000..c939907
--- /dev/null
+++ b/Lib/importlib/test/import_/test_path.py
@@ -0,0 +1,158 @@
+from ..support import (mock_modules, import_state, import_, mock_path_hook,
+ importlib_only, uncache)
+
+from contextlib import nested
+from imp import new_module
+import sys
+from types import MethodType
+import unittest
+
+
+class BaseTests(unittest.TestCase):
+
+ """When sys.meta_path cannot find the desired module, sys.path is
+ consulted. For each entry on the sequence [order], sys.path_importer_cache
+ is checked to see if it contains a key for the entry [cache check]. If an
+ importer is found then it is consulted before trying the next entry in
+ sys.path [cache use]. The 'path' argument to find_module() is never used
+ when trying to find a module [path not used].
+
+ If an entry from sys.path is not in sys.path_importer_cache, sys.path_hooks
+ is called in turn [hooks order]. If a path hook cannot handle an entry,
+ ImportError is raised [hook failure]. Otherwise the resulting object is
+ cached in sys.path_importer_cache and then consulted [hook success]. If no
+ hook is found, None is set in sys.path_importer_cache and the default
+ importer is tried [no hook].
+
+ For use of __path__ in a package, the above is all true, just substitute
+ "sys.path" for "__path__".
+
+ """
+
+ def order_test(self, to_import, entry, search_path, path=[]):
+ # [order]
+ log = []
+ class LogFindModule(mock_modules):
+ def find_module(self, fullname):
+ log.append(self)
+ return super().find_module(fullname)
+
+ assert len(search_path) == 2
+ misser = LogFindModule(search_path[0])
+ hitter = LogFindModule(to_import)
+ with nested(misser, hitter):
+ cache = dict(zip(search_path, (misser, hitter)))
+ with import_state(path=path, path_importer_cache=cache):
+ import_(to_import)
+ self.assertEquals(log[0], misser)
+ self.assertEquals(log[1], hitter)
+
+ @importlib_only # __import__ uses PyDict_GetItem(), bypassing log.
+ def cache_use_test(self, to_import, entry, path=[]):
+ # [cache check], [cache use]
+ log = []
+ class LoggingDict(dict):
+ def __getitem__(self, item):
+ log.append(item)
+ return super(LoggingDict, self).__getitem__(item)
+
+ with mock_modules(to_import) as importer:
+ cache = LoggingDict()
+ cache[entry] = importer
+ with import_state(path=[entry], path_importer_cache=cache):
+ module = import_(to_import, fromlist=['a'])
+ self.assert_(module is importer[to_import])
+ self.assertEquals(len(cache), 1)
+ self.assertEquals([entry], log)
+
+ def hooks_order_test(self, to_import, entry, path=[]):
+ # [hooks order], [hooks failure], [hook success]
+ log = []
+ def logging_hook(entry):
+ log.append(entry)
+ raise ImportError
+ with mock_modules(to_import) as importer:
+ hitter = mock_path_hook(entry, importer=importer)
+ path_hooks = [logging_hook, logging_hook, hitter]
+ with import_state(path_hooks=path_hooks, path=path):
+ import_(to_import)
+ self.assertEquals(sys.path_importer_cache[entry], importer)
+ self.assertEquals(len(log), 2)
+
+ # [no hook] XXX Worry about after deciding how to handle the default hook.
+
+ def path_argument_test(self, to_import):
+ # [path not used]
+ class BadImporter:
+ """Class to help detect TypeError from calling find_module() with
+ an improper number of arguments."""
+ def find_module(name):
+ raise ImportError
+
+ try:
+ import_(to_import)
+ except ImportError:
+ pass
+
+
+class PathTests(BaseTests):
+
+ """Tests for sys.path."""
+
+ def test_order(self):
+ self.order_test('hit', 'second', ['first', 'second'],
+ ['first', 'second'])
+
+ def test_cache_use(self):
+ entry = "found!"
+ self.cache_use_test('hit', entry, [entry])
+
+ def test_hooks_order(self):
+ entry = "found!"
+ self.hooks_order_test('hit', entry, [entry])
+
+ def test_path_argument(self):
+ name = 'total junk'
+ with uncache(name):
+ self.path_argument_test(name)
+
+
+class __path__Tests(BaseTests):
+
+ """Tests for __path__."""
+
+ def run_test(self, test, entry, path, *args):
+ with mock_modules('pkg.__init__') as importer:
+ importer['pkg'].__path__ = path
+ importer.load_module('pkg')
+ test('pkg.hit', entry, *args)
+
+
+ @importlib_only # XXX Unknown reason why this fails.
+ def test_order(self):
+ self.run_test(self.order_test, 'second', ('first', 'second'), ['first',
+ 'second'])
+
+ def test_cache_use(self):
+ location = "I'm here!"
+ self.run_test(self.cache_use_test, location, [location])
+
+ def test_hooks_order(self):
+ location = "I'm here!"
+ self.run_test(self.hooks_order_test, location, [location])
+
+ def test_path_argument(self):
+ module = new_module('pkg')
+ module.__path__ = ['random __path__']
+ name = 'pkg.whatever'
+ sys.modules['pkg'] = module
+ with uncache('pkg', name):
+ self.path_argument_test(name)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(PathTests, __path__Tests)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_relative_imports.py b/Lib/importlib/test/import_/test_relative_imports.py
new file mode 100644
index 0000000..73ef530
--- /dev/null
+++ b/Lib/importlib/test/import_/test_relative_imports.py
@@ -0,0 +1,199 @@
+"""Test relative imports (PEP 328)."""
+
+from ..support import uncache, import_, mock_modules, import_state
+
+import sys
+import unittest
+
+class RelativeImports(unittest.TestCase):
+
+ """PEP 328 introduced relative imports. This allows for imports to occur
+ from within a package without having to specify the actual package name.
+
+ A simple example is to import another module within the same package
+ [module from module]::
+
+ # From pkg.mod1 with pkg.mod2 being a module.
+ from . import mod2
+
+ This also works for getting an attribute from a module that is specified
+ in a relative fashion [attr from module]::
+
+ # From pkg.mod1.
+ from .mod2 import attr
+
+ But this is in no way restricted to working between modules; it works
+ from [package to module],::
+
+ # From pkg, importing pkg.module which is a module.
+ from . import module
+
+ [module to package],::
+
+ # Pull attr from pkg, called from pkg.module which is a module.
+ from . import attr
+
+ and [package to package]::
+
+ # From pkg.subpkg1 (both pkg.subpkg[1,2] are packages).
+ from .. import subpkg2
+
+ The number of dots used is in no way restricted [deep import]::
+
+ # Import pkg.attr from pkg.pkg1.pkg2.pkg3.pkg4.pkg5.
+ from ...... import attr
+
+ To prevent someone from accessing code that is outside of a package, one
+ cannot reach the location containing the root package itself::
+
+ # From pkg.__init__ [too high from package]
+ from .. import top_level
+
+ # From pkg.module [too high from module]
+ from .. import top_level
+
+ Relative imports are the only type of import that allow for an empty
+ module name for an import [empty name].
+
+ """
+
+ def relative_import_test(self, create, globals_, callback):
+ """Abstract out boilerplace for setting up for an import test."""
+ uncache_names = []
+ for name in create:
+ if not name.endswith('.__init__'):
+ uncache_names.append(name)
+ else:
+ uncache_names.append(name[:-len('.__init__')])
+ with mock_modules(*create) as importer:
+ with import_state(meta_path=[importer]):
+ for global_ in globals_:
+ with uncache(*uncache_names):
+ callback(global_)
+
+
+ def test_module_from_module(self):
+ # [module from module]
+ create = 'pkg.__init__', 'pkg.mod2'
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.mod1'}
+ def callback(global_):
+ import_('pkg') # For __import__().
+ module = import_('', global_, fromlist=['mod2'], level=1)
+ self.assertEqual(module.__name__, 'pkg')
+ self.assert_(hasattr(module, 'mod2'))
+ self.assertEqual(module.mod2.attr, 'pkg.mod2')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_attr_from_module(self):
+ # [attr from module]
+ create = 'pkg.__init__', 'pkg.mod2'
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.mod1'}
+ def callback(global_):
+ import_('pkg') # For __import__().
+ module = import_('mod2', global_, fromlist=['attr'], level=1)
+ self.assertEqual(module.__name__, 'pkg.mod2')
+ self.assertEqual(module.attr, 'pkg.mod2')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_package_to_module(self):
+ # [package to module]
+ create = 'pkg.__init__', 'pkg.module'
+ globals_ = ({'__package__': 'pkg'},
+ {'__name__': 'pkg', '__path__': ['blah']})
+ def callback(global_):
+ import_('pkg') # For __import__().
+ module = import_('', global_, fromlist=['module'],
+ level=1)
+ self.assertEqual(module.__name__, 'pkg')
+ self.assert_(hasattr(module, 'module'))
+ self.assertEqual(module.module.attr, 'pkg.module')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_module_to_package(self):
+ # [module to package]
+ create = 'pkg.__init__', 'pkg.module'
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
+ def callback(global_):
+ import_('pkg') # For __import__().
+ module = import_('', global_, fromlist=['attr'], level=1)
+ self.assertEqual(module.__name__, 'pkg')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_package_to_package(self):
+ # [package to package]
+ create = ('pkg.__init__', 'pkg.subpkg1.__init__',
+ 'pkg.subpkg2.__init__')
+ globals_ = ({'__package__': 'pkg.subpkg1'},
+ {'__name__': 'pkg.subpkg1', '__path__': ['blah']})
+ def callback(global_):
+ module = import_('', global_, fromlist=['subpkg2'], level=2)
+ self.assertEqual(module.__name__, 'pkg')
+ self.assert_(hasattr(module, 'subpkg2'))
+ self.assertEqual(module.subpkg2.attr, 'pkg.subpkg2.__init__')
+
+ def test_deep_import(self):
+ # [deep import]
+ create = ['pkg.__init__']
+ for count in range(1,6):
+ create.append('{0}.pkg{1}.__init__'.format(
+ create[-1][:-len('.__init__')], count))
+ globals_ = ({'__package__': 'pkg.pkg1.pkg2.pkg3.pkg4.pkg5'},
+ {'__name__': 'pkg.pkg1.pkg2.pkg3.pkg4.pkg5',
+ '__path__': ['blah']})
+ def callback(global_):
+ import_(globals_[0]['__package__'])
+ module = import_('', global_, fromlist=['attr'], level=6)
+ self.assertEqual(module.__name__, 'pkg')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_too_high_from_package(self):
+ # [too high from package]
+ create = ['top_level', 'pkg.__init__']
+ globals_ = ({'__package__': 'pkg'},
+ {'__name__': 'pkg', '__path__': ['blah']})
+ def callback(global_):
+ import_('pkg')
+ self.assertRaises(ValueError, import_, '', global_,
+ fromlist=['top_level'], level=2)
+ self.relative_import_test(create, globals_, callback)
+
+ def test_too_high_from_module(self):
+ # [too high from module]
+ create = ['top_level', 'pkg.__init__', 'pkg.module']
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
+ def callback(global_):
+ import_('pkg')
+ self.assertRaises(ValueError, import_, '', global_,
+ fromlist=['top_level'], level=2)
+ self.relative_import_test(create, globals_, callback)
+
+ def test_empty_name_w_level_0(self):
+ # [empty name]
+ self.assertRaises(ValueError, import_, '')
+
+ def test_import_from_different_package(self):
+ # Test importing from a different package than the caller.
+ # in pkg.subpkg1.mod
+ # from ..subpkg2 import mod
+ # XXX
+ create = ['__runpy_pkg__.__init__',
+ '__runpy_pkg__.__runpy_pkg__.__init__',
+ '__runpy_pkg__.uncle.__init__',
+ '__runpy_pkg__.uncle.cousin.__init__',
+ '__runpy_pkg__.uncle.cousin.nephew']
+ globals_ = {'__package__': '__runpy_pkg__.__runpy_pkg__'}
+ def callback(global_):
+ import_('__runpy_pkg__.__runpy_pkg__')
+ module = import_('uncle.cousin', globals_, {}, fromlist=['nephew'],
+ level=2)
+ self.assertEqual(module.__name__, '__runpy_pkg__.uncle.cousin')
+ self.relative_import_test(create, globals_, callback)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(RelativeImports)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/__init__.py b/Lib/importlib/test/source/__init__.py
new file mode 100644
index 0000000..8d7c49d
--- /dev/null
+++ b/Lib/importlib/test/source/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.source', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/source/test_case_sensitivity.py b/Lib/importlib/test/source/test_case_sensitivity.py
new file mode 100644
index 0000000..1a5ff2f
--- /dev/null
+++ b/Lib/importlib/test/source/test_case_sensitivity.py
@@ -0,0 +1,57 @@
+"""Test case-sensitivity (PEP 235)."""
+import importlib
+from .. import support
+import os
+import sys
+from test import support as test_support
+import unittest
+
+
+class CaseSensitivityTest(unittest.TestCase):
+
+ """PEP 235 dictates that on case-preserving, case-insensitive file systems
+ that imports are case-sensitive unless the PYTHONCASEOK environment
+ variable is set."""
+
+ name = 'MoDuLe'
+ assert name != name.lower()
+
+ def find(self, path):
+ finder = importlib.PyFileImporter(path)
+ return finder.find_module(self.name)
+
+ def sensitivity_test(self):
+ """Look for a module with matching and non-matching sensitivity."""
+ sensitive_pkg = 'sensitive.{0}'.format(self.name)
+ insensitive_pkg = 'insensitive.{0}'.format(self.name.lower())
+ with support.create_modules(insensitive_pkg, sensitive_pkg) as mapping:
+ sensitive_path = os.path.join(mapping['.root'], 'sensitive')
+ insensitive_path = os.path.join(mapping['.root'], 'insensitive')
+ return self.find(sensitive_path), self.find(insensitive_path)
+
+ def test_sensitive(self):
+ with test_support.EnvironmentVarGuard() as env:
+ env.unset('PYTHONCASEOK')
+ sensitive, insensitive = self.sensitivity_test()
+ self.assert_(hasattr(sensitive, 'load_module'))
+ self.assert_(self.name in sensitive._base_path)
+ self.assert_(insensitive is None)
+
+ def test_insensitive(self):
+ with test_support.EnvironmentVarGuard() as env:
+ env.set('PYTHONCASEOK', '1')
+ sensitive, insensitive = self.sensitivity_test()
+ self.assert_(hasattr(sensitive, 'load_module'))
+ self.assert_(self.name in sensitive._base_path)
+ self.assert_(hasattr(insensitive, 'load_module'))
+ self.assert_(self.name in insensitive._base_path)
+
+
+def test_main():
+ if sys.platform not in ('win32', 'darwin', 'cygwin'):
+ return
+ test_support.run_unittest(CaseSensitivityTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_finder.py b/Lib/importlib/test/source/test_finder.py
new file mode 100644
index 0000000..cf80799
--- /dev/null
+++ b/Lib/importlib/test/source/test_finder.py
@@ -0,0 +1,130 @@
+import importlib
+from .. import finder_tests
+from .. import support
+import os
+import py_compile
+import unittest
+import warnings
+
+
+class FinderTests(finder_tests.FinderTests):
+
+ """For a top-level module, it should just be found directly in the
+ directory being searched. This is true for a directory with source
+ [top-level source], bytecode [top-level bc], or both [top-level both].
+ There is also the possibility that it is a package [top-level package], in
+ which case there will be a directory with the module name and an
+ __init__.py file. If there is a directory without an __init__.py an
+ ImportWarning is returned [empty dir].
+
+ For sub-modules and sub-packages, the same happens as above but only use
+ the tail end of the name [sub module] [sub package] [sub empty].
+
+ When there is a conflict between a package and module having the same name
+ in the same directory, the package wins out [package over module]. This is
+ so that imports of modules within the package can occur rather than trigger
+ an import error.
+
+ When there is a package and module with the same name, always pick the
+ package over the module [package over module]. This is so that imports from
+ the package have the possibility of succeeding.
+
+ """
+
+ def import_(self, root, module):
+ finder = importlib.PyFileImporter(root)
+ return finder.find_module(module)
+
+ def run_test(self, test, create=None, *, compile_=None, unlink=None):
+ """Test the finding of 'test' with the creation of modules listed in
+ 'create'.
+
+ Any names listed in 'compile_' are byte-compiled. Modules
+ listed in 'unlink' have their source files deleted.
+
+ """
+ if create is None:
+ create = {test}
+ with support.create_modules(*create) as mapping:
+ if compile_:
+ for name in compile_:
+ py_compile.compile(mapping[name])
+ if unlink:
+ for name in unlink:
+ os.unlink(mapping[name])
+ loader = self.import_(mapping['.root'], test)
+ self.assert_(hasattr(loader, 'load_module'))
+ return loader
+
+ def test_module(self):
+ # [top-level source]
+ self.run_test('top_level')
+ # [top-level bc]
+ self.run_test('top_level', compile_={'top_level'}, unlink={'top_level'})
+ # [top-level both]
+ self.run_test('top_level', compile_={'top_level'})
+
+ # [top-level package]
+ def test_package(self):
+ # Source.
+ self.run_test('pkg', {'pkg.__init__'})
+ # Bytecode.
+ self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'},
+ unlink={'pkg.__init__'})
+ # Both.
+ self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'})
+
+ # [sub module]
+ def test_module_in_package(self):
+ with support.create_modules('pkg.__init__', 'pkg.sub') as mapping:
+ pkg_dir = os.path.dirname(mapping['pkg.__init__'])
+ loader = self.import_(pkg_dir, 'pkg.sub')
+ self.assert_(hasattr(loader, 'load_module'))
+
+ # [sub package]
+ def test_package_in_package(self):
+ context = support.create_modules('pkg.__init__', 'pkg.sub.__init__')
+ with context as mapping:
+ pkg_dir = os.path.dirname(mapping['pkg.__init__'])
+ loader = self.import_(pkg_dir, 'pkg.sub')
+ self.assert_(hasattr(loader, 'load_module'))
+
+ # [sub empty]
+ def test_empty_sub_directory(self):
+ context = support.create_modules('pkg.__init__', 'pkg.sub.__init__')
+ with warnings.catch_warnings():
+ warnings.simplefilter("error", ImportWarning)
+ with context as mapping:
+ os.unlink(mapping['pkg.sub.__init__'])
+ pkg_dir = os.path.dirname(mapping['pkg.__init__'])
+ self.assertRaises(ImportWarning, self.import_, pkg_dir,
+ 'pkg.sub')
+
+ # [package over modules]
+ def test_package_over_module(self):
+ # XXX This is not a blackbox test!
+ name = '_temp'
+ loader = self.run_test(name, {'{0}.__init__'.format(name), name})
+ self.assert_('__init__' in loader._base_path)
+
+
+ def test_failure(self):
+ with support.create_modules('blah') as mapping:
+ nothing = self.import_(mapping['.root'], 'sdfsadsadf')
+ self.assert_(nothing is None)
+
+ # [empty dir]
+ def test_empty_dir(self):
+ with warnings.catch_warnings():
+ warnings.simplefilter("error", ImportWarning)
+ self.assertRaises(ImportWarning, self.run_test, 'pkg',
+ {'pkg.__init__'}, unlink={'pkg.__init__'})
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_loader.py b/Lib/importlib/test/source/test_loader.py
new file mode 100644
index 0000000..249bdc0
--- /dev/null
+++ b/Lib/importlib/test/source/test_loader.py
@@ -0,0 +1,201 @@
+import importlib
+from .. import support
+
+import imp
+import os
+import py_compile
+import sys
+import unittest
+
+
+class SimpleTest(unittest.TestCase):
+
+ """Should have no issue importing a source module [basic]. And if there is
+ a syntax error, it should raise a SyntaxError [syntax error].
+
+ """
+
+ # [basic]
+ def test_basic(self):
+ with support.create_modules('_temp') as mapping:
+ loader = importlib._PyFileLoader('_temp', mapping['_temp'], False)
+ loader.load_module('_temp')
+ self.assert_('_temp' in sys.modules)
+
+ # [syntax error]
+ def test_bad_syntax(self):
+ with support.create_modules('_temp') as mapping:
+ with open(mapping['_temp'], 'w') as file:
+ file.write('=')
+ loader = importlib._PyFileLoader('_temp', mapping['_temp'], False)
+ self.assertRaises(SyntaxError, loader.load_module, '_temp')
+ 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
+
+ @support.writes_bytecode
+ def run_test(self, assertion):
+ with support.create_modules('_temp') as mapping:
+ loader = importlib._PyFileLoader('_temp', mapping['_temp'], False)
+ loader.load_module('_temp')
+ bytecode_path = support.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 support.create_modules('_temp') as mapping:
+ py_compile.compile(mapping['_temp'])
+ os.unlink(mapping['_temp'])
+ bytecode_path = support.bytecode_path(mapping['_temp'])
+ with open(bytecode_path, 'r+b') as file:
+ file.seek(0)
+ file.write(b'\x00\x00\x00\x00')
+ loader = importlib._PyFileLoader('_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._PyFileLoader(module, file, pkg)
+ return loader.load_module(module)
+
+ def run_test(self, test, *create, pkg=False):
+ create += (test,)
+ with support.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._PyFileLoader(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
+ source being preferred. If the magic number differs from what the
+ interpreter uses, then the source is used with the bytecode regenerated.
+ If the timestamp is older than the modification time for the source then
+ the bytecode is not used [bad timestamp].
+
+ But if the marshal data is bad, even if the magic number and timestamp
+ work, a ValueError is raised and the source is not used [bad marshal].
+
+ """
+
+ def import_(self, file, module_name):
+ loader = importlib._PyFileLoader(module_name, file, False)
+ module = loader.load_module(module_name)
+ self.assert_(module_name in sys.modules)
+
+ # [bad magic]
+ @support.writes_bytecode
+ def test_bad_magic(self):
+ with support.create_modules('_temp') as mapping:
+ py_compile.compile(mapping['_temp'])
+ bytecode_path = support.bytecode_path(mapping['_temp'])
+ with open(bytecode_path, 'r+b') as bytecode_file:
+ bytecode_file.seek(0)
+ bytecode_file.write(b'\x00\x00\x00\x00')
+ self.import_(mapping['_temp'], '_temp')
+ with open(bytecode_path, 'rb') as bytecode_file:
+ self.assertEqual(bytecode_file.read(4), imp.get_magic())
+
+ # [bad timestamp]
+ @support.writes_bytecode
+ def test_bad_bytecode(self):
+ zeros = b'\x00\x00\x00\x00'
+ with support.create_modules('_temp') as mapping:
+ py_compile.compile(mapping['_temp'])
+ bytecode_path = support.bytecode_path(mapping['_temp'])
+ with open(bytecode_path, 'r+b') as bytecode_file:
+ bytecode_file.seek(4)
+ bytecode_file.write(zeros)
+ self.import_(mapping['_temp'], '_temp')
+ source_mtime = os.path.getmtime(mapping['_temp'])
+ source_timestamp = importlib._w_long(source_mtime)
+ with open(bytecode_path, 'rb') as bytecode_file:
+ bytecode_file.seek(4)
+ self.assertEqual(bytecode_file.read(4), source_timestamp)
+
+ # [bad marshal]
+ def test_bad_marshal(self):
+ with support.create_modules('_temp') as mapping:
+ bytecode_path = support.bytecode_path(mapping['_temp'])
+ source_mtime = os.path.getmtime(mapping['_temp'])
+ source_timestamp = importlib._w_long(source_mtime)
+ with open(bytecode_path, 'wb') as bytecode_file:
+ bytecode_file.write(imp.get_magic())
+ bytecode_file.write(source_timestamp)
+ bytecode_file.write(b'AAAA')
+ self.assertRaises(ValueError, self.import_, mapping['_temp'],
+ '_temp')
+ self.assert_('_temp' not in sys.modules)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(SimpleTest, DontWriteBytecodeTest, BadDataTest,
+ SourceBytecodeInteraction, BadBytecodeTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_path_hook.py b/Lib/importlib/test/source/test_path_hook.py
new file mode 100644
index 0000000..ef410779
--- /dev/null
+++ b/Lib/importlib/test/source/test_path_hook.py
@@ -0,0 +1,23 @@
+import importlib
+from .. import support
+import unittest
+
+
+class PathHookTest(unittest.TestCase):
+
+ """Test the path hook for source."""
+
+ def test_success(self):
+ # XXX Only work on existing directories?
+ with support.create_modules('dummy') as mapping:
+ self.assert_(hasattr(importlib.FileImporter(mapping['.root']),
+ 'find_module'))
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(PathHookTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_reload.py b/Lib/importlib/test/source/test_reload.py
new file mode 100644
index 0000000..e123a28
--- /dev/null
+++ b/Lib/importlib/test/source/test_reload.py
@@ -0,0 +1,71 @@
+"""Test reload support.
+
+Reload support requires two things. One is that if module is loaded that
+already exists in sys.modules then it is reused. And two, if a reload fails the
+pre-existing module is left in a sane state.
+
+"""
+import imp
+import sys
+import types
+import unittest
+import importlib
+from .. import support
+
+
+class ReloadTests(unittest.TestCase):
+
+ name = '_temp'
+
+ def load_module(self, mapping):
+ return importlib._PyFileLoader(self.name, mapping[self.name], False)
+
+ def fake_mtime(self, fxn):
+ """Fake mtime to always be higher than expected."""
+ return lambda name: fxn(name) + 1
+
+ def test_module_reuse(self):
+ with support.create_modules(self.name) as mapping:
+ loader = self.load_module(mapping)
+ module = loader.load_module(self.name)
+ module_id = id(module)
+ module_dict_id = id(module.__dict__)
+ with open(mapping[self.name], 'w') as file:
+ file.write("testing_var = 42\n")
+ # For filesystems where the mtime is only to a second granularity,
+ # everything that has happened above can be too fast;
+ # force an mtime on the source that is guaranteed to be different
+ # than the original mtime.
+ loader.source_mtime = self.fake_mtime(loader.source_mtime)
+ module = loader.load_module(self.name)
+ self.assert_('testing_var' in module.__dict__,
+ "'testing_var' not in "
+ "{0}".format(list(module.__dict__.keys())))
+ self.assertEqual(module, sys.modules[self.name])
+ self.assertEqual(id(module), module_id)
+ self.assertEqual(id(module.__dict__), module_dict_id)
+
+ def test_bad_reload(self):
+ # A failed reload should leave the original module intact.
+ attributes = ('__file__', '__path__', '__package__')
+ value = '<test>'
+ with support.create_modules(self.name) as mapping:
+ orig_module = imp.new_module(self.name)
+ for attr in attributes:
+ setattr(orig_module, attr, value)
+ with open(mapping[self.name], 'w') as file:
+ file.write('+++ bad syntax +++')
+ loader = self.load_module(mapping)
+ self.assertRaises(SyntaxError, loader.load_module, self.name)
+ for attr in attributes:
+ self.assertEqual(getattr(orig_module, attr), value)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(ReloadTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_source_encoding.py b/Lib/importlib/test/source/test_source_encoding.py
new file mode 100644
index 0000000..97096df
--- /dev/null
+++ b/Lib/importlib/test/source/test_source_encoding.py
@@ -0,0 +1,122 @@
+import importlib
+from .. import support
+
+import codecs
+import re
+import sys
+# Because sys.path gets essentially blanked, need to have unicodedata already
+# imported for the parser to use.
+import unicodedata
+import unittest
+
+
+CODING_RE = re.compile(r'coding[:=]\s*([-\w.]+)')
+
+
+class EncodingTest(unittest.TestCase):
+
+ """PEP 3120 makes UTF-8 the default encoding for source code
+ [default encoding].
+
+ PEP 263 specifies how that can change on a per-file basis. Either the first
+ or second line can contain the encoding line [encoding first line]
+ encoding second line]. If the file has the BOM marker it is considered UTF-8
+ implicitly [BOM]. If any encoding is specified it must be UTF-8, else it is
+ an error [BOM and utf-8][BOM conflict].
+
+ """
+
+ variable = '\u00fc'
+ character = '\u00c9'
+ source_line = "{0} = '{1}'\n".format(variable, character)
+ module_name = '_temp'
+
+ def run_test(self, source):
+ with support.create_modules(self.module_name) as mapping:
+ with open(mapping[self.module_name], 'wb')as file:
+ file.write(source)
+ loader = importlib._PyFileLoader(self.module_name,
+ mapping[self.module_name], False)
+ return loader.load_module(self.module_name)
+
+ def create_source(self, encoding):
+ encoding_line = "# coding={0}".format(encoding)
+ assert CODING_RE.search(encoding_line)
+ source_lines = [encoding_line.encode('utf-8')]
+ source_lines.append(self.source_line.encode(encoding))
+ return b'\n'.join(source_lines)
+
+ def test_non_obvious_encoding(self):
+ # Make sure that an encoding that has never been a standard one for
+ # Python works.
+ encoding_line = "# coding=koi8-r"
+ assert CODING_RE.search(encoding_line)
+ source = "{0}\na=42\n".format(encoding_line).encode("koi8-r")
+ self.run_test(source)
+
+ # [default encoding]
+ def test_default_encoding(self):
+ self.run_test(self.source_line.encode('utf-8'))
+
+ # [encoding first line]
+ def test_encoding_on_first_line(self):
+ encoding = 'Latin-1'
+ source = self.create_source(encoding)
+ self.run_test(source)
+
+ # [encoding second line]
+ def test_encoding_on_second_line(self):
+ source = b"#/usr/bin/python\n" + self.create_source('Latin-1')
+ self.run_test(source)
+
+ # [BOM]
+ def test_bom(self):
+ self.run_test(codecs.BOM_UTF8 + self.source_line.encode('utf-8'))
+
+ # [BOM and utf-8]
+ def test_bom_and_utf_8(self):
+ source = codecs.BOM_UTF8 + self.create_source('utf-8')
+ self.run_test(source)
+
+ # [BOM conflict]
+ def test_bom_conflict(self):
+ source = codecs.BOM_UTF8 + self.create_source('latin-1')
+ self.assertRaises(SyntaxError, self.run_test, source)
+
+
+class LineEndingTest(unittest.TestCase):
+
+ r"""Source written with the three types of line endings (\n, \r\n, \r)
+ need to be readable [cr][crlf][lf]."""
+
+ def run_test(self, line_ending):
+ module_name = '_temp'
+ source_lines = [b"a = 42", b"b = -13", b'']
+ source = line_ending.join(source_lines)
+ with support.create_modules(module_name) as mapping:
+ with open(mapping[module_name], 'wb') as file:
+ file.write(source)
+ loader = importlib._PyFileLoader(module_name, mapping[module_name],
+ False)
+ return loader.load_module(module_name)
+
+ # [cr]
+ def test_cr(self):
+ self.run_test(b'\r')
+
+ # [crlf]
+ def test_crlf(self):
+ self.run_test(b'\r\n')
+
+ # [lf]
+ def test_lf(self):
+ self.run_test(b'\n')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(EncodingTest, LineEndingTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/support.py b/Lib/importlib/test/support.py
new file mode 100644
index 0000000..4e63cd1
--- /dev/null
+++ b/Lib/importlib/test/support.py
@@ -0,0 +1,223 @@
+from importlib import Import
+
+from contextlib import contextmanager
+from functools import update_wrapper
+import imp
+import os.path
+from test.support import unlink
+import sys
+from tempfile import gettempdir
+
+
+using___import__ = False
+
+def import_(*args, **kwargs):
+ """Delegate to allow for injecting different implementations of import."""
+ if using___import__:
+ return __import__(*args, **kwargs)
+ return Import()(*args, **kwargs)
+
+def importlib_only(fxn):
+ """Decorator to mark which tests are not supported by the current
+ implementation of __import__()."""
+ def inner(*args, **kwargs):
+ if using___import__:
+ return
+ else:
+ return fxn(*args, **kwargs)
+ update_wrapper(inner, fxn)
+ return inner
+
+def writes_bytecode(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:
+ return fxn
+
+@contextmanager
+def uncache(*names):
+ """Uncache a module from sys.modules.
+
+ A basic sanity check is performed to prevent uncaching modules that either
+ cannot/shouldn't be uncached.
+
+ """
+ for name in names:
+ if name in ('sys', 'marshal', 'imp'):
+ raise ValueError(
+ "cannot uncache {0} as it will break _importlib".format(name))
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+ try:
+ yield
+ finally:
+ for name in names:
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+
+@contextmanager
+def import_state(**kwargs):
+ """Context manager to manage the various importers and stored state in the
+ sys module.
+
+ The 'modules' attribute is not supported as the interpreter state stores a
+ pointer to the dict that the interpreter uses internally;
+ reassigning to sys.modules does not have the desired effect.
+
+ """
+ originals = {}
+ try:
+ for attr, default in (('meta_path', []), ('path', []),
+ ('path_hooks', []),
+ ('path_importer_cache', {})):
+ originals[attr] = getattr(sys, attr)
+ if attr in kwargs:
+ new_value = kwargs[attr]
+ del kwargs[attr]
+ else:
+ new_value = default
+ setattr(sys, attr, new_value)
+ if len(kwargs):
+ raise ValueError(
+ 'unrecognized arguments: {0}'.format(kwargs.keys()))
+ yield
+ finally:
+ for attr, value in originals.items():
+ setattr(sys, attr, value)
+
+
+@contextmanager
+def create_modules(*names):
+ """Temporarily create each named module with an attribute (named 'attr')
+ that contains the name passed into the context manager that caused the
+ creation of the module.
+
+ All files are created in a temporary directory specified by
+ tempfile.gettempdir(). This directory is inserted at the beginning of
+ sys.path. When the context manager exits all created files (source and
+ bytecode) are explicitly deleted.
+
+ No magic is performed when creating packages! This means that if you create
+ a module within a package you must also create the package's __init__ as
+ well.
+
+ """
+ source = 'attr = {0!r}'
+ created_paths = []
+ mapping = {}
+ try:
+ temp_dir = gettempdir()
+ mapping['.root'] = temp_dir
+ import_names = set()
+ for name in names:
+ if not name.endswith('__init__'):
+ import_name = name
+ else:
+ import_name = name[:-len('.__init__')]
+ import_names.add(import_name)
+ if import_name in sys.modules:
+ del sys.modules[import_name]
+ name_parts = name.split('.')
+ file_path = temp_dir
+ for directory in name_parts[:-1]:
+ file_path = os.path.join(file_path, directory)
+ if not os.path.exists(file_path):
+ os.mkdir(file_path)
+ created_paths.append(file_path)
+ file_path = os.path.join(file_path, name_parts[-1] + '.py')
+ with open(file_path, 'w') as file:
+ file.write(source.format(name))
+ created_paths.append(file_path)
+ mapping[name] = file_path
+ uncache_manager = uncache(*import_names)
+ uncache_manager.__enter__()
+ state_manager = import_state(path=[temp_dir])
+ state_manager.__enter__()
+ yield mapping
+ finally:
+ state_manager.__exit__(None, None, None)
+ uncache_manager.__exit__(None, None, None)
+ # Reverse the order for path removal to unroll directory creation.
+ for path in reversed(created_paths):
+ if file_path.endswith('.py'):
+ unlink(path)
+ unlink(path + 'c')
+ unlink(path + 'o')
+ else:
+ os.rmdir(path)
+
+
+class mock_modules:
+
+ """A mock importer/loader."""
+
+ def __init__(self, *names):
+ self.modules = {}
+ for name in names:
+ if not name.endswith('.__init__'):
+ import_name = name
+ else:
+ import_name = name[:-len('.__init__')]
+ if '.' not in name:
+ package = None
+ elif import_name == name:
+ package = name.rsplit('.', 1)[0]
+ else:
+ package = import_name
+ module = imp.new_module(import_name)
+ module.__loader__ = self
+ module.__file__ = '<mock __file__>'
+ module.__package__ = package
+ module.attr = name
+ if import_name != name:
+ module.__path__ = ['<mock __path__>']
+ self.modules[import_name] = module
+
+ def __getitem__(self, name):
+ return self.modules[name]
+
+ def find_module(self, fullname, path=None):
+ if fullname not in self.modules:
+ return None
+ else:
+ return self
+
+ def load_module(self, fullname):
+ if fullname not in self.modules:
+ raise ImportError
+ else:
+ sys.modules[fullname] = self.modules[fullname]
+ return self.modules[fullname]
+
+ def __enter__(self):
+ self._uncache = uncache(*self.modules.keys())
+ self._uncache.__enter__()
+ return self
+
+ def __exit__(self, *exc_info):
+ self._uncache.__exit__(None, None, None)
+
+
+def mock_path_hook(*entries, importer):
+ """A mock sys.path_hooks entry."""
+ def hook(entry):
+ if entry not in entries:
+ raise ImportError
+ return importer
+ return hook
+
+
+def bytecode_path(source_path):
+ for suffix, _, type_ in imp.get_suffixes():
+ if type_ == imp.PY_COMPILED:
+ bc_suffix = suffix
+ break
+ else:
+ raise ValueError("no bytecode suffix is defined")
+ return os.path.splitext(source_path)[0] + bc_suffix
diff --git a/Lib/importlib/test/test_api.py b/Lib/importlib/test/test_api.py
new file mode 100644
index 0000000..75053a0
--- /dev/null
+++ b/Lib/importlib/test/test_api.py
@@ -0,0 +1,62 @@
+import unittest
+import importlib
+from . import support
+
+
+class ImportModuleTests(unittest.TestCase):
+
+ """Test importlib.import_module."""
+
+ def test_module_import(self):
+ # Test importing a top-level module.
+ with support.mock_modules('top_level') as mock:
+ with support.import_state(meta_path=[mock]):
+ module = importlib.import_module('top_level')
+ self.assertEqual(module.__name__, 'top_level')
+
+ def test_absolute_package_import(self):
+ # Test importing a module from a package with an absolute name.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ name = '{0}.mod'.format(pkg_name)
+ with support.mock_modules(pkg_long_name, name) as mock:
+ with support.import_state(meta_path=[mock]):
+ module = importlib.import_module(name)
+ self.assertEqual(module.__name__, name)
+
+ def test_relative_package_import(self):
+ # Test importing a module from a package through a relatve import.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ module_name = 'mod'
+ absolute_name = '{0}.{1}'.format(pkg_name, module_name)
+ relative_name = '.{0}'.format(module_name)
+ with support.mock_modules(pkg_long_name, absolute_name) as mock:
+ with support.import_state(meta_path=[mock]):
+ module = importlib.import_module(relative_name, pkg_name)
+ self.assertEqual(module.__name__, absolute_name)
+
+ def test_absolute_import_with_package(self):
+ # Test importing a module from a package with an absolute name with
+ # the 'package' argument given.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ name = '{0}.mod'.format(pkg_name)
+ with support.mock_modules(pkg_long_name, name) as mock:
+ with support.import_state(meta_path=[mock]):
+ module = importlib.import_module(name, pkg_name)
+ self.assertEqual(module.__name__, name)
+
+ def test_relative_import_wo_package(self):
+ # Relative imports cannot happen without the 'package' argument being
+ # set.
+ self.assertRaises(TypeError, importlib.import_module, '.support')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(ImportModuleTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/test/test_importlib.py b/Lib/test/test_importlib.py
new file mode 100644
index 0000000..cd13e32
--- /dev/null
+++ b/Lib/test/test_importlib.py
@@ -0,0 +1,10 @@
+from test.support import run_unittest
+import importlib.test
+
+
+def test_main():
+ run_unittest(importlib.test.test_suite('importlib.test'))
+
+
+if __name__ == '__main__':
+ test_main()