aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/importlib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/importlib')
-rw-r--r--Lib/importlib/__init__.py110
-rw-r--r--Lib/importlib/_bootstrap.py540
-rw-r--r--Lib/importlib/abc.py33
-rw-r--r--Lib/importlib/test/__main__.py19
-rw-r--r--Lib/importlib/test/benchmark.py157
-rw-r--r--Lib/importlib/test/extension/test_case_sensitivity.py6
-rw-r--r--Lib/importlib/test/import_/test___package__.py2
-rw-r--r--Lib/importlib/test/import_/test_api.py7
-rw-r--r--Lib/importlib/test/import_/test_path.py10
-rw-r--r--Lib/importlib/test/regrtest.py7
-rw-r--r--Lib/importlib/test/source/test_abc_loader.py17
-rw-r--r--Lib/importlib/test/source/test_case_sensitivity.py6
-rw-r--r--Lib/importlib/test/source/test_file_loader.py44
-rw-r--r--Lib/importlib/test/source/test_finder.py7
-rw-r--r--Lib/importlib/test/test_api.py28
-rw-r--r--Lib/importlib/test/test_util.py10
16 files changed, 657 insertions, 346 deletions
diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py
index 2baaf937329..57fb284ab12 100644
--- a/Lib/importlib/__init__.py
+++ b/Lib/importlib/__init__.py
@@ -1,103 +1,19 @@
-"""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
-
-"""
-__all__ = ['__import__', 'import_module']
+"""A pure Python implementation of import."""
+__all__ = ['__import__', 'import_module', 'invalidate_caches']
from . import _bootstrap
-import os
-import re
-import tokenize
-
-# Bootstrap help #####################################################
-
-def _case_ok(directory, check):
- """Check if the directory contains something matching 'check'.
- No check is done if the file/directory exists or not.
+# To simplify imports in test code
+_w_long = _bootstrap._w_long
+_r_long = _bootstrap._r_long
- """
- if 'PYTHONCASEOK' in os.environ:
- return True
- elif check in os.listdir(directory if directory else os.getcwd()):
- 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)
+# Bootstrap help #####################################################
+import imp
+import sys
-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
-
-
-# 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, _io
-_bootstrap.imp = imp
-_bootstrap.sys = sys
-_bootstrap.marshal = marshal
-_bootstrap.errno = errno
-_bootstrap._io = _io
-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
+_bootstrap._setup(sys, imp)
# Public API #########################################################
@@ -105,6 +21,14 @@ marshal._r_long = _r_long
from ._bootstrap import __import__
+def invalidate_caches():
+ """Call the invalidate_caches() method on all finders stored in
+ sys.path_importer_caches (where implemented)."""
+ for finder in sys.path_importer_cache.values():
+ if hasattr(finder, 'invalidate_caches'):
+ finder.invalidate_caches()
+
+
def import_module(name, package=None):
"""Import a module.
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index 90eb1a770f9..e0f86fc1727 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -7,9 +7,10 @@ work. One should use importlib as the public-facing version of this module.
"""
-# Injected modules are '_warnings', 'imp', 'sys', 'marshal', 'errno', '_io',
+# Injected modules are '_warnings', 'imp', 'sys', 'marshal', '_io',
# and '_os' (a.k.a. 'posix', 'nt' or 'os2').
# Injected attribute is path_sep.
+# Most injection is handled by _setup().
#
# 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
@@ -18,11 +19,57 @@ work. One should use importlib as the public-facing version of this module.
# Bootstrap-related code ######################################################
+CASE_INSENSITIVE_PLATFORMS = 'win', 'cygwin', 'darwin'
+
+
+def _make_relax_case():
+ if any(map(sys.platform.startswith, CASE_INSENSITIVE_PLATFORMS)):
+ def _relax_case():
+ """True if filenames must be checked case-insensitively."""
+ return b'PYTHONCASEOK' in _os.environ
+ else:
+ def _relax_case():
+ """True if filenames must be checked case-insensitively."""
+ return False
+ return _relax_case
+
+
+# TODO: Expose from marshal
+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)
+
+
+# TODO: Expose from marshal
+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
+
+
+
# 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 if x)
+ for x in args if x)
def _path_exists(path):
@@ -80,10 +127,32 @@ def _path_absolute(path):
return _path_join(_os.getcwd(), path)
+def _write_atomic(path, data):
+ """Best-effort function to write data to a path atomically.
+ Be prepared to handle a FileExistsError if concurrent writing of the
+ temporary file is attempted."""
+ # id() is used to generate a pseudo-random filename.
+ path_tmp = '{}.{}'.format(path, id(path))
+ fd = _os.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, 0o666)
+ try:
+ # We first write data to a temporary file, and then use os.replace() to
+ # perform an atomic rename.
+ with _io.FileIO(fd, 'wb') as file:
+ file.write(data)
+ _os.replace(path_tmp, path)
+ except OSError:
+ try:
+ _os.unlink(path_tmp)
+ except OSError:
+ pass
+ raise
+
+
def _wrap(new, old):
"""Simple substitute for functools.wraps."""
- for replace in ['__module__', '__name__', '__doc__']:
- setattr(new, replace, getattr(old, replace))
+ for replace in ['__module__', '__name__', '__qualname__', '__doc__']:
+ if hasattr(old, replace):
+ setattr(new, replace, getattr(old, replace))
new.__dict__.update(old.__dict__)
@@ -91,28 +160,29 @@ code_type = type(_wrap.__code__)
# Finder/loader utility code ##################################################
+
def set_package(fxn):
"""Set __package__ on the returned module."""
- def wrapper(*args, **kwargs):
+ def set_package_wrapper(*args, **kwargs):
module = fxn(*args, **kwargs)
if not hasattr(module, '__package__') or module.__package__ is None:
module.__package__ = module.__name__
if not hasattr(module, '__path__'):
module.__package__ = module.__package__.rpartition('.')[0]
return module
- _wrap(wrapper, fxn)
- return wrapper
+ _wrap(set_package_wrapper, fxn)
+ return set_package_wrapper
def set_loader(fxn):
"""Set __loader__ on the returned module."""
- def wrapper(self, *args, **kwargs):
+ def set_loader_wrapper(self, *args, **kwargs):
module = fxn(self, *args, **kwargs)
if not hasattr(module, '__loader__'):
module.__loader__ = self
return module
- _wrap(wrapper, fxn)
- return wrapper
+ _wrap(set_loader_wrapper, fxn)
+ return set_loader_wrapper
def module_for_loader(fxn):
@@ -128,7 +198,7 @@ def module_for_loader(fxn):
the second argument.
"""
- def decorated(self, fullname, *args, **kwargs):
+ def module_for_loader_wrapper(self, fullname, *args, **kwargs):
module = sys.modules.get(fullname)
is_reload = bool(module)
if not is_reload:
@@ -143,8 +213,8 @@ def module_for_loader(fxn):
if not is_reload:
del sys.modules[fullname]
raise
- _wrap(decorated, fxn)
- return decorated
+ _wrap(module_for_loader_wrapper, fxn)
+ return module_for_loader_wrapper
def _check_name(method):
@@ -155,32 +225,32 @@ def _check_name(method):
compared against. If the comparison fails then ImportError is raised.
"""
- def inner(self, name, *args, **kwargs):
+ def _check_name_wrapper(self, name, *args, **kwargs):
if self._name != name:
raise ImportError("loader cannot handle %s" % name)
return method(self, name, *args, **kwargs)
- _wrap(inner, method)
- return inner
+ _wrap(_check_name_wrapper, method)
+ return _check_name_wrapper
def _requires_builtin(fxn):
"""Decorator to verify the named module is built-in."""
- def wrapper(self, fullname):
+ def _requires_builtin_wrapper(self, fullname):
if fullname not in sys.builtin_module_names:
raise ImportError("{0} is not a built-in module".format(fullname))
return fxn(self, fullname)
- _wrap(wrapper, fxn)
- return wrapper
+ _wrap(_requires_builtin_wrapper, fxn)
+ return _requires_builtin_wrapper
def _requires_frozen(fxn):
"""Decorator to verify the named module is frozen."""
- def wrapper(self, fullname):
+ def _requires_frozen_wrapper(self, fullname):
if not imp.is_frozen(fullname):
raise ImportError("{0} is not a frozen module".format(fullname))
return fxn(self, fullname)
- _wrap(wrapper, fxn)
- return wrapper
+ _wrap(_requires_frozen_wrapper, fxn)
+ return _requires_frozen_wrapper
def _suffix_list(suffix_type):
@@ -240,7 +310,7 @@ class BuiltinImporter:
@classmethod
@_requires_builtin
def is_package(cls, fullname):
- """Return None as built-in module are never packages."""
+ """Return None as built-in modules are never packages."""
return False
@@ -302,25 +372,42 @@ class _LoaderBasics:
filename = self.get_filename(fullname).rpartition(path_sep)[2]
return filename.rsplit('.', 1)[0] == '__init__'
- def _bytes_from_bytecode(self, fullname, data, source_mtime):
+ def _bytes_from_bytecode(self, fullname, data, source_stats):
"""Return the marshalled bytes from bytecode, verifying the magic
- number and timestamp along the way.
+ number, timestamp and source size along the way.
- If source_mtime is None then skip the timestamp check.
+ If source_stats is None then skip the timestamp check.
"""
magic = data[:4]
raw_timestamp = data[4:8]
+ raw_size = data[8:12]
if len(magic) != 4 or magic != imp.get_magic():
raise ImportError("bad magic number in {}".format(fullname))
elif len(raw_timestamp) != 4:
raise EOFError("bad timestamp in {}".format(fullname))
- elif source_mtime is not None:
- if marshal._r_long(raw_timestamp) != source_mtime:
- raise ImportError("bytecode is stale for {}".format(fullname))
+ elif len(raw_size) != 4:
+ raise EOFError("bad size in {}".format(fullname))
+ if source_stats is not None:
+ try:
+ source_mtime = int(source_stats['mtime'])
+ except KeyError:
+ pass
+ else:
+ if _r_long(raw_timestamp) != source_mtime:
+ raise ImportError(
+ "bytecode is stale for {}".format(fullname))
+ try:
+ source_size = source_stats['size'] & 0xFFFFFFFF
+ except KeyError:
+ pass
+ else:
+ if _r_long(raw_size) != source_size:
+ raise ImportError(
+ "bytecode is stale for {}".format(fullname))
# Can't return the code object as errors from marshal loading need to
# propagate even when source is available.
- return data[8:]
+ return data[12:]
@module_for_loader
def _load_module(self, module, *, sourceless=False):
@@ -348,11 +435,20 @@ class SourceLoader(_LoaderBasics):
def path_mtime(self, path):
"""Optional method that returns the modification time (an int) for the
specified path, where path is a str.
+ """
+ raise NotImplementedError
- Implementing this method allows the loader to read bytecode files.
+ def path_stats(self, path):
+ """Optional method returning a metadata dict for the specified path
+ to by the path (str).
+ Possible keys:
+ - 'mtime' (mandatory) is the numeric timestamp of last source
+ code modification;
+ - 'size' (optional) is the size in bytes of the source code.
+ Implementing this method allows the loader to read bytecode files.
"""
- raise NotImplementedError
+ return {'mtime': self.path_mtime(path)}
def set_data(self, path, data):
"""Optional method which writes data (bytes) to a file path (a str).
@@ -378,7 +474,7 @@ class SourceLoader(_LoaderBasics):
def get_code(self, fullname):
"""Concrete implementation of InspectLoader.get_code.
- Reading of bytecode requires path_mtime to be implemented. To write
+ Reading of bytecode requires path_stats to be implemented. To write
bytecode, set_data must also be implemented.
"""
@@ -387,10 +483,11 @@ class SourceLoader(_LoaderBasics):
source_mtime = None
if bytecode_path is not None:
try:
- source_mtime = self.path_mtime(source_path)
+ st = self.path_stats(source_path)
except NotImplementedError:
pass
else:
+ source_mtime = int(st['mtime'])
try:
data = self.get_data(bytecode_path)
except IOError:
@@ -398,12 +495,13 @@ class SourceLoader(_LoaderBasics):
else:
try:
bytes_data = self._bytes_from_bytecode(fullname, data,
- source_mtime)
+ st)
except (ImportError, EOFError):
pass
else:
found = marshal.loads(bytes_data)
if isinstance(found, code_type):
+ imp._fix_co_filename(found, source_path)
return found
else:
msg = "Non-code object in {}"
@@ -412,12 +510,13 @@ class SourceLoader(_LoaderBasics):
code_object = compile(source_bytes, source_path, 'exec',
dont_inherit=True)
if (not sys.dont_write_bytecode and bytecode_path is not None and
- source_mtime is not None):
+ source_mtime is not None):
# If e.g. Jython ever implements imp.cache_from_source to have
# their own cached file format, this block of code will most likely
# throw an exception.
data = bytearray(imp.get_magic())
- data.extend(marshal._w_long(source_mtime))
+ data.extend(_w_long(source_mtime))
+ data.extend(_w_long(len(source_bytes)))
data.extend(marshal.dumps(code_object))
try:
self.set_data(bytecode_path, data)
@@ -462,9 +561,10 @@ class _SourceFileLoader(_FileLoader, SourceLoader):
"""Concrete implementation of SourceLoader using the file system."""
- def path_mtime(self, path):
- """Return the modification time for the path."""
- return int(_os.stat(path).st_mtime)
+ def path_stats(self, path):
+ """Return the metadat for the path."""
+ st = _os.stat(path)
+ return {'mtime': st.st_mtime, 'size': st.st_size}
def set_data(self, path, data):
"""Write bytes data to a file."""
@@ -479,28 +579,19 @@ class _SourceFileLoader(_FileLoader, SourceLoader):
parent = _path_join(parent, part)
try:
_os.mkdir(parent)
- except OSError as exc:
+ except FileExistsError:
# Probably another Python process already created the dir.
- if exc.errno == errno.EEXIST:
- continue
- else:
- raise
- except IOError as exc:
+ continue
+ except PermissionError:
# If can't get proper access, then just forget about writing
# the data.
- if exc.errno == errno.EACCES:
- return
- else:
- raise
- try:
- with _io.FileIO(path, 'wb') as file:
- file.write(data)
- except IOError as exc:
- # Don't worry if you can't write bytecode.
- if exc.errno == errno.EACCES:
return
- else:
- raise
+ try:
+ _write_atomic(path, data)
+ except (PermissionError, FileExistsError):
+ # Don't worry if you can't write bytecode or someone is writing
+ # it at the same time.
+ pass
class _SourcelessFileLoader(_FileLoader, _LoaderBasics):
@@ -534,12 +625,6 @@ class _ExtensionFileLoader:
"""
def __init__(self, name, path):
- """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
@@ -608,6 +693,8 @@ class PathFinder:
the default hook, for which ImportError is raised.
"""
+ if path == '':
+ path = '.'
try:
finder = sys.path_importer_cache[path]
except KeyError:
@@ -659,29 +746,61 @@ class _FileFinder:
for suffix in detail.suffixes)
self.packages = packages
self.modules = modules
- self.path = path
+ # Base (directory) path
+ self.path = path or '.'
+ self._path_mtime = -1
+ self._path_cache = set()
+ self._relaxed_path_cache = set()
+
+ def invalidate_caches(self):
+ """Invalidate the directory mtime."""
+ self._path_mtime = -1
def find_module(self, fullname):
"""Try to find a loader for the specified module."""
tail_module = fullname.rpartition('.')[2]
- base_path = _path_join(self.path, tail_module)
- if _path_isdir(base_path) and _case_ok(self.path, tail_module):
- for suffix, loader in self.packages:
- init_filename = '__init__' + suffix
- full_path = _path_join(base_path, init_filename)
- if (_path_isfile(full_path) and
- _case_ok(base_path, init_filename)):
- return loader(fullname, full_path)
- else:
- msg = "Not importing directory {}: missing __init__"
- _warnings.warn(msg.format(base_path), ImportWarning)
+ try:
+ mtime = _os.stat(self.path).st_mtime
+ except OSError:
+ mtime = -1
+ if mtime != self._path_mtime:
+ self._fill_cache()
+ self._path_mtime = mtime
+ # tail_module keeps the original casing, for __file__ and friends
+ if _relax_case():
+ cache = self._relaxed_path_cache
+ cache_module = tail_module.lower()
+ else:
+ cache = self._path_cache
+ cache_module = tail_module
+ if cache_module in cache:
+ base_path = _path_join(self.path, tail_module)
+ if _path_isdir(base_path):
+ for suffix, loader in self.packages:
+ init_filename = '__init__' + suffix
+ full_path = _path_join(base_path, init_filename)
+ if _path_isfile(full_path):
+ return loader(fullname, full_path)
+ else:
+ msg = "Not importing directory {}: missing __init__"
+ _warnings.warn(msg.format(base_path), ImportWarning)
for suffix, loader in self.modules:
- mod_filename = tail_module + suffix
- full_path = _path_join(self.path, mod_filename)
- if _path_isfile(full_path) and _case_ok(self.path, mod_filename):
- return loader(fullname, full_path)
+ if cache_module + suffix in cache:
+ full_path = _path_join(self.path, tail_module + suffix)
+ if _path_isfile(full_path):
+ return loader(fullname, full_path)
return None
+ def _fill_cache(self):
+ """Fill the cache of potential modules and packages for this directory."""
+ path = self.path
+ contents = _os.listdir(path)
+ # We store two cached versions, to handle runtime changes of the
+ # PYTHONCASEOK environment variable.
+ self._path_cache = set(contents)
+ self._relaxed_path_cache = set(fn.lower() for fn in contents)
+
+
class _SourceFinderDetails:
loader = _SourceFileLoader
@@ -756,40 +875,100 @@ class _ImportLockContext:
imp.release_lock()
+def _resolve_name(name, package, level):
+ """Resolve a relative module name to an absolute one."""
+ bits = package.rsplit('.', level - 1)
+ if len(bits) < level:
+ raise ValueError('attempted relative import beyond top-level package')
+ base = bits[0]
+ return '{0}.{1}'.format(base, name) if name else base
+
+
+def _find_module(name, path):
+ """Find a module's loader."""
+ meta_path = sys.meta_path + _IMPLICIT_META_PATH
+ for finder in meta_path:
+ loader = finder.find_module(name, path)
+ if loader is not None:
+ # The parent import may have already imported this module.
+ if name not in sys.modules:
+ return loader
+ else:
+ return sys.modules[name].__loader__
+ else:
+ return None
+
+
+def _sanity_check(name, package, level):
+ """Verify arguments are "sane"."""
+ if not isinstance(name, str):
+ raise TypeError("module name must be str, not {}".format(type(name)))
+ if level < 0:
+ raise ValueError('level must be >= 0')
+ if package:
+ if not isinstance(package, str):
+ raise TypeError("__package__ not set to a string")
+ elif package not in sys.modules:
+ msg = ("Parent module {0!r} not loaded, cannot perform relative "
+ "import")
+ raise SystemError(msg.format(package))
+ if not name and level == 0:
+ raise ValueError("Empty module name")
+
+
_IMPLICIT_META_PATH = [BuiltinImporter, FrozenImporter, _DefaultPathFinder]
-_ERR_MSG = 'No module named {}'
+_ERR_MSG = 'No module named {!r}'
+
+def _find_and_load(name, import_):
+ """Find and load the module."""
+ path = None
+ parent = name.rpartition('.')[0]
+ if parent:
+ if parent not in sys.modules:
+ import_(parent)
+ # Backwards-compatibility; be nicer to skip the dict lookup.
+ parent_module = sys.modules[parent]
+ try:
+ path = parent_module.__path__
+ except AttributeError:
+ msg = (_ERR_MSG + '; {} is not a package').format(name, parent)
+ raise ImportError(msg)
+ loader = _find_module(name, path)
+ if loader is None:
+ raise ImportError(_ERR_MSG.format(name))
+ elif name not in sys.modules:
+ # The parent import may have already imported this module.
+ loader.load_module(name)
+ # Backwards-compatibility; be nicer to skip the dict lookup.
+ module = sys.modules[name]
+ if parent:
+ # Set the module as an attribute on its parent.
+ parent_module = sys.modules[parent]
+ setattr(parent_module, name.rpartition('.')[2], module)
+ # Set __package__ if the loader did not.
+ if not hasattr(module, '__package__') or module.__package__ is None:
+ try:
+ module.__package__ = module.__name__
+ if not hasattr(module, '__path__'):
+ module.__package__ = module.__package__.rpartition('.')[0]
+ except AttributeError:
+ pass
+ return module
+
def _gcd_import(name, package=None, level=0):
"""Import and return the module based on its name, the package the call is
being made from, and the level adjustment.
This function represents the greatest common denominator of functionality
- between import_module and __import__. This includes settting __package__ if
+ between import_module and __import__. This includes setting __package__ if
the loader did not.
"""
- if package:
- if not hasattr(package, 'rindex'):
- raise ValueError("__package__ not set to a string")
- elif package not in sys.modules:
- msg = ("Parent module {0!r} not loaded, cannot perform relative "
- "import")
- raise SystemError(msg.format(package))
- if not name and level == 0:
- raise ValueError("Empty module name")
+ _sanity_check(name, package, level)
if level > 0:
- dot = len(package)
- for x in range(level, 1, -1):
- try:
- dot = package.rindex('.', 0, dot)
- except ValueError:
- raise ValueError("attempted relative import beyond "
- "top-level package")
- if name:
- name = "{0}.{1}".format(package[:dot], name)
- else:
- name = package[:dot]
+ name = _resolve_name(name, package, level)
with _ImportLockContext():
try:
module = sys.modules[name]
@@ -799,45 +978,46 @@ def _gcd_import(name, package=None, level=0):
raise ImportError(message)
return module
except KeyError:
- pass
- parent = name.rpartition('.')[0]
- path = None
- if parent:
- if parent not in sys.modules:
- _gcd_import(parent)
- # Backwards-compatibility; be nicer to skip the dict lookup.
- parent_module = sys.modules[parent]
- try:
- path = parent_module.__path__
- except AttributeError:
- msg = (_ERR_MSG + '; {} is not a package').format(name, parent)
- raise ImportError(msg)
- meta_path = sys.meta_path + _IMPLICIT_META_PATH
- for finder in meta_path:
- loader = finder.find_module(name, path)
- if loader is not None:
- # The parent import may have already imported this module.
- if name not in sys.modules:
- loader.load_module(name)
- break
- else:
- raise ImportError(_ERR_MSG.format(name))
- # Backwards-compatibility; be nicer to skip the dict lookup.
- module = sys.modules[name]
- if parent:
- # Set the module as an attribute on its parent.
- setattr(parent_module, name.rpartition('.')[2], module)
- # Set __package__ if the loader did not.
- if not hasattr(module, '__package__') or module.__package__ is None:
- # Watch out for what comes out of sys.modules to not be a module,
- # e.g. an int.
+ pass # Don't want to chain the exception
+ return _find_and_load(name, _gcd_import)
+
+
+def _handle_fromlist(module, fromlist, import_):
+ """Figure out what __import__ should return.
+
+ The import_ parameter is a callable which takes the name of module to
+ import. It is required to decouple the function from assuming importlib's
+ import implementation is desired.
+
+ """
+ # The hell that is fromlist ...
+ # If a package was imported, try to import stuff from fromlist.
+ if hasattr(module, '__path__'):
+ if '*' in fromlist and hasattr(module, '__all__'):
+ fromlist = list(fromlist)
+ fromlist.remove('*')
+ fromlist.extend(module.__all__)
+ for x in (y for y in fromlist if not hasattr(module, y)):
try:
- module.__package__ = module.__name__
- if not hasattr(module, '__path__'):
- module.__package__ = module.__package__.rpartition('.')[0]
- except AttributeError:
+ import_('{0}.{1}'.format(module.__name__, x))
+ except ImportError:
pass
- return module
+ return module
+
+
+def _calc___package__(globals):
+ """Calculate what __package__ should be.
+
+ __package__ is not guaranteed to be defined or could be set to None
+ to represent that its proper value is unknown.
+
+ """
+ package = globals.get('__package__')
+ if package is None:
+ package = globals['__name__']
+ if '__path__' not in globals:
+ package = package.rpartition('.')[0]
+ return package
def __import__(name, globals={}, locals={}, fromlist=[], level=0):
@@ -851,20 +1031,11 @@ def __import__(name, globals={}, locals={}, fromlist=[], level=0):
import (e.g. ``from ..pkg import mod`` would have a 'level' of 2).
"""
- if not hasattr(name, 'rpartition'):
- raise TypeError("module name must be str, not {}".format(type(name)))
if level == 0:
module = _gcd_import(name)
else:
- # __package__ is not guaranteed to be defined or could be set to None
- # to represent that it's proper value is unknown
- package = globals.get('__package__')
- if package is None:
- package = globals['__name__']
- if '__path__' not in globals:
- package = package.rpartition('.')[0]
+ package = _calc___package__(globals)
module = _gcd_import(name, package, level)
- # The hell that is fromlist ...
if not fromlist:
# Return up to the first dot in 'name'. This is complicated by the fact
# that 'name' may be relative.
@@ -876,15 +1047,62 @@ def __import__(name, globals={}, locals={}, fromlist=[], level=0):
cut_off = len(name) - len(name.partition('.')[0])
return sys.modules[module.__name__[:-cut_off]]
else:
- # If a package was imported, try to import stuff from fromlist.
- if hasattr(module, '__path__'):
- if '*' in fromlist and hasattr(module, '__all__'):
- fromlist = list(fromlist)
- fromlist.remove('*')
- fromlist.extend(module.__all__)
- for x in (y for y in fromlist if not hasattr(module,y)):
- try:
- _gcd_import('{0}.{1}'.format(module.__name__, x))
- except ImportError:
- pass
- return module
+ return _handle_fromlist(module, fromlist, _gcd_import)
+
+
+def _setup(sys_module, imp_module):
+ """Setup importlib by importing needed built-in modules and injecting them
+ into the global namespace.
+
+ As sys is needed for sys.modules access and imp is needed to load built-in
+ modules, those two modules must be explicitly passed in.
+
+ """
+ global imp, sys
+ imp = imp_module
+ sys = sys_module
+
+ for module in (imp, sys):
+ if not hasattr(module, '__loader__'):
+ module.__loader__ = BuiltinImporter
+
+ self_module = sys.modules[__name__]
+ for builtin_name in ('_io', '_warnings', 'builtins', 'marshal'):
+ if builtin_name not in sys.modules:
+ builtin_module = BuiltinImporter.load_module(builtin_name)
+ else:
+ builtin_module = sys.modules[builtin_name]
+ setattr(self_module, builtin_name, builtin_module)
+
+ for builtin_os, path_sep in [('posix', '/'), ('nt', '\\'), ('os2', '\\')]:
+ if builtin_os in sys.modules:
+ os_module = sys.modules[builtin_os]
+ break
+ else:
+ try:
+ os_module = BuiltinImporter.load_module(builtin_os)
+ # TODO: rip out os2 code after 3.3 is released as per PEP 11
+ if builtin_os == 'os2' and 'EMX GCC' in sys.version:
+ path_sep = '/'
+ break
+ except ImportError:
+ continue
+ else:
+ raise ImportError('importlib requires posix or nt')
+ setattr(self_module, '_os', os_module)
+ setattr(self_module, 'path_sep', path_sep)
+ # Constants
+ setattr(self_module, '_relax_case', _make_relax_case())
+
+
+def _install(sys_module, imp_module):
+ """Install importlib as the implementation of import.
+
+ It is assumed that imp and sys have been imported and injected into the
+ global namespace for the module prior to calling this function.
+
+ """
+ _setup(sys_module, imp_module)
+ orig_import = builtins.__import__
+ builtins.__import__ = __import__
+ builtins.__original_import__ = orig_import
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
index fa343f85a47..22a7c1a46de 100644
--- a/Lib/importlib/abc.py
+++ b/Lib/importlib/abc.py
@@ -1,15 +1,11 @@
"""Abstract base classes related to import."""
from . import _bootstrap
from . import machinery
-from . import util
import abc
import imp
-import io
import marshal
-import os.path
import sys
import tokenize
-import types
import warnings
@@ -123,7 +119,20 @@ class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
def path_mtime(self, path):
"""Return the (int) modification time for the path (str)."""
- raise NotImplementedError
+ if self.path_stats.__func__ is SourceLoader.path_stats:
+ raise NotImplementedError
+ return int(self.path_stats(path)['mtime'])
+
+ def path_stats(self, path):
+ """Return a metadata dict for the source pointed to by the path (str).
+ Possible keys:
+ - 'mtime' (mandatory) is the numeric timestamp of last source
+ code modification;
+ - 'size' (optional) is the size in bytes of the source code.
+ """
+ if self.path_mtime.__func__ is SourceLoader.path_mtime:
+ raise NotImplementedError
+ return {'mtime': self.path_mtime(path)}
def set_data(self, path, data):
"""Write the bytes to the path (if possible).
@@ -195,7 +204,7 @@ class PyLoader(SourceLoader):
"use SourceLoader instead. "
"See the importlib documentation on how to be "
"compatible with Python 3.1 onwards.",
- PendingDeprecationWarning)
+ DeprecationWarning)
path = self.source_path(fullname)
if path is None:
raise ImportError
@@ -234,7 +243,7 @@ class PyPycLoader(PyLoader):
"removal in Python 3.4; use SourceLoader instead. "
"If Python 3.1 compatibility is required, see the "
"latest documentation for PyLoader.",
- PendingDeprecationWarning)
+ DeprecationWarning)
source_timestamp = self.source_mtime(fullname)
# Try to use bytecode if it is available.
bytecode_path = self.bytecode_path(fullname)
@@ -243,15 +252,17 @@ class PyPycLoader(PyLoader):
try:
magic = data[:4]
if len(magic) < 4:
- raise ImportError("bad magic number in {}".format(fullname))
+ raise ImportError(
+ "bad magic number in {}".format(fullname))
raw_timestamp = data[4:8]
if len(raw_timestamp) < 4:
raise EOFError("bad timestamp in {}".format(fullname))
- pyc_timestamp = marshal._r_long(raw_timestamp)
+ pyc_timestamp = _bootstrap._r_long(raw_timestamp)
bytecode = data[8:]
# Verify that the magic number is valid.
if imp.get_magic() != magic:
- raise ImportError("bad magic number in {}".format(fullname))
+ raise ImportError(
+ "bad magic number in {}".format(fullname))
# Verify that the bytecode is not stale (only matters when
# there is source to fall back on.
if source_timestamp:
@@ -279,7 +290,7 @@ class PyPycLoader(PyLoader):
# Generate bytecode and write it out.
if not sys.dont_write_bytecode:
data = bytearray(imp.get_magic())
- data.extend(marshal._w_long(source_timestamp))
+ data.extend(_bootstrap._w_long(source_timestamp))
data.extend(marshal.dumps(code_object))
self.write_bytecode(fullname, data)
return code_object
diff --git a/Lib/importlib/test/__main__.py b/Lib/importlib/test/__main__.py
index decc53d8c5a..92171b25caa 100644
--- a/Lib/importlib/test/__main__.py
+++ b/Lib/importlib/test/__main__.py
@@ -4,26 +4,27 @@ Specifying the ``--builtin`` flag will run tests, where applicable, with
builtins.__import__ instead of importlib.__import__.
"""
-import importlib
from importlib.test.import_ import util
import os.path
from test.support import run_unittest
-import sys
import unittest
def test_main():
- if '__pycache__' in __file__:
- parts = __file__.split(os.path.sep)
- start_dir = sep.join(parts[:-2])
- else:
- start_dir = os.path.dirname(__file__)
+ start_dir = os.path.dirname(__file__)
top_dir = os.path.dirname(os.path.dirname(start_dir))
test_loader = unittest.TestLoader()
- if '--builtin' in sys.argv:
- util.using___import__ = True
run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir))
if __name__ == '__main__':
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Execute the importlib test '
+ 'suite')
+ parser.add_argument('-b', '--builtin', action='store_true', default=False,
+ help='use builtins.__import__() instead of importlib')
+ args = parser.parse_args()
+ if args.builtin:
+ util.using___import__ = True
test_main()
diff --git a/Lib/importlib/test/benchmark.py b/Lib/importlib/test/benchmark.py
index b5de6c6b010..87b1775f66c 100644
--- a/Lib/importlib/test/benchmark.py
+++ b/Lib/importlib/test/benchmark.py
@@ -9,9 +9,11 @@ from .source import util as source_util
import decimal
import imp
import importlib
+import json
import os
import py_compile
import sys
+import tabnanny
import timeit
@@ -59,7 +61,7 @@ def builtin_mod(seconds, repeat):
def source_wo_bytecode(seconds, repeat):
- """Source w/o bytecode: simple"""
+ """Source w/o bytecode: small"""
sys.dont_write_bytecode = True
try:
name = '__importlib_test_benchmark__'
@@ -73,23 +75,30 @@ def source_wo_bytecode(seconds, repeat):
sys.dont_write_bytecode = False
-def decimal_wo_bytecode(seconds, repeat):
- """Source w/o bytecode: decimal"""
- name = 'decimal'
- decimal_bytecode = imp.cache_from_source(decimal.__file__)
- if os.path.exists(decimal_bytecode):
- os.unlink(decimal_bytecode)
- sys.dont_write_bytecode = True
- try:
- for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
- seconds=seconds):
- yield result
- finally:
- sys.dont_write_bytecode = False
+def _wo_bytecode(module):
+ name = module.__name__
+ def benchmark_wo_bytecode(seconds, repeat):
+ """Source w/o bytecode: {}"""
+ bytecode_path = imp.cache_from_source(module.__file__)
+ if os.path.exists(bytecode_path):
+ os.unlink(bytecode_path)
+ sys.dont_write_bytecode = True
+ try:
+ for result in bench(name, lambda: sys.modules.pop(name),
+ repeat=repeat, seconds=seconds):
+ yield result
+ finally:
+ sys.dont_write_bytecode = False
+
+ benchmark_wo_bytecode.__doc__ = benchmark_wo_bytecode.__doc__.format(name)
+ return benchmark_wo_bytecode
+
+tabnanny_wo_bytecode = _wo_bytecode(tabnanny)
+decimal_wo_bytecode = _wo_bytecode(decimal)
def source_writing_bytecode(seconds, repeat):
- """Source writing bytecode: simple"""
+ """Source writing bytecode: small"""
assert not sys.dont_write_bytecode
name = '__importlib_test_benchmark__'
with source_util.create_modules(name) as mapping:
@@ -101,19 +110,27 @@ def source_writing_bytecode(seconds, repeat):
yield result
-def decimal_writing_bytecode(seconds, repeat):
- """Source writing bytecode: decimal"""
- assert not sys.dont_write_bytecode
- name = 'decimal'
- def cleanup():
- sys.modules.pop(name)
- os.unlink(imp.cache_from_source(decimal.__file__))
- for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
- yield result
+def _writing_bytecode(module):
+ name = module.__name__
+ def writing_bytecode_benchmark(seconds, repeat):
+ """Source writing bytecode: {}"""
+ assert not sys.dont_write_bytecode
+ def cleanup():
+ sys.modules.pop(name)
+ os.unlink(imp.cache_from_source(module.__file__))
+ for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
+ yield result
+
+ writing_bytecode_benchmark.__doc__ = (
+ writing_bytecode_benchmark.__doc__.format(name))
+ return writing_bytecode_benchmark
+
+tabnanny_writing_bytecode = _writing_bytecode(tabnanny)
+decimal_writing_bytecode = _writing_bytecode(decimal)
def source_using_bytecode(seconds, repeat):
- """Bytecode w/ source: simple"""
+ """Source w/ bytecode: small"""
name = '__importlib_test_benchmark__'
with source_util.create_modules(name) as mapping:
py_compile.compile(mapping[name])
@@ -123,27 +140,56 @@ def source_using_bytecode(seconds, repeat):
yield result
-def decimal_using_bytecode(seconds, repeat):
- """Bytecode w/ source: decimal"""
- name = 'decimal'
- py_compile.compile(decimal.__file__)
- for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
- seconds=seconds):
- yield result
+def _using_bytecode(module):
+ name = module.__name__
+ def using_bytecode_benchmark(seconds, repeat):
+ """Source w/ bytecode: {}"""
+ py_compile.compile(module.__file__)
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+ using_bytecode_benchmark.__doc__ = (
+ using_bytecode_benchmark.__doc__.format(name))
+ return using_bytecode_benchmark
-def main(import_):
+tabnanny_using_bytecode = _using_bytecode(tabnanny)
+decimal_using_bytecode = _using_bytecode(decimal)
+
+
+def main(import_, options):
+ if options.source_file:
+ with options.source_file:
+ prev_results = json.load(options.source_file)
+ else:
+ prev_results = {}
__builtins__.__import__ = import_
benchmarks = (from_cache, builtin_mod,
- source_using_bytecode, source_wo_bytecode,
source_writing_bytecode,
- decimal_using_bytecode, decimal_writing_bytecode,
- decimal_wo_bytecode,)
+ source_wo_bytecode, source_using_bytecode,
+ tabnanny_writing_bytecode,
+ tabnanny_wo_bytecode, tabnanny_using_bytecode,
+ decimal_writing_bytecode,
+ decimal_wo_bytecode, decimal_using_bytecode,
+ )
+ if options.benchmark:
+ for b in benchmarks:
+ if b.__doc__ == options.benchmark:
+ benchmarks = [b]
+ break
+ else:
+ print('Unknown benchmark: {!r}'.format(options.benchmark,
+ file=sys.stderr))
+ sys.exit(1)
seconds = 1
seconds_plural = 's' if seconds > 1 else ''
repeat = 3
- header = "Measuring imports/second over {} second{}, best out of {}\n"
- print(header.format(seconds, seconds_plural, repeat))
+ header = ('Measuring imports/second over {} second{}, best out of {}\n'
+ 'Entire benchmark run should take about {} seconds\n'
+ 'Using {!r} as __import__\n')
+ print(header.format(seconds, seconds_plural, repeat,
+ len(benchmarks) * seconds * repeat, __import__))
+ new_results = {}
for benchmark in benchmarks:
print(benchmark.__doc__, "[", end=' ')
sys.stdout.flush()
@@ -154,19 +200,40 @@ def main(import_):
sys.stdout.flush()
assert not sys.dont_write_bytecode
print("]", "best is", format(max(results), ',d'))
+ new_results[benchmark.__doc__] = results
+ if prev_results:
+ print('\n\nComparing new vs. old\n')
+ for benchmark in benchmarks:
+ benchmark_name = benchmark.__doc__
+ old_result = max(prev_results[benchmark_name])
+ new_result = max(new_results[benchmark_name])
+ result = '{:,d} vs. {:,d} ({:%})'.format(new_result,
+ old_result,
+ new_result/old_result)
+ print(benchmark_name, ':', result)
+ if options.dest_file:
+ with options.dest_file:
+ json.dump(new_results, options.dest_file, indent=2)
if __name__ == '__main__':
- import optparse
+ import argparse
- parser = optparse.OptionParser()
- parser.add_option('-b', '--builtin', dest='builtin', action='store_true',
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-b', '--builtin', dest='builtin', action='store_true',
default=False, help="use the built-in __import__")
- options, args = parser.parse_args()
- if args:
- raise RuntimeError("unrecognized args: {}".format(args))
+ parser.add_argument('-r', '--read', dest='source_file',
+ type=argparse.FileType('r'),
+ help='file to read benchmark data from to compare '
+ 'against')
+ parser.add_argument('-w', '--write', dest='dest_file',
+ type=argparse.FileType('w'),
+ help='file to write benchmark data to')
+ parser.add_argument('--benchmark', dest='benchmark',
+ help='specific benchmark to run')
+ options = parser.parse_args()
import_ = __import__
if not options.builtin:
import_ = importlib.__import__
- main(import_)
+ main(import_, options)
diff --git a/Lib/importlib/test/extension/test_case_sensitivity.py b/Lib/importlib/test/extension/test_case_sensitivity.py
index e062fb6597d..add830deddb 100644
--- a/Lib/importlib/test/extension/test_case_sensitivity.py
+++ b/Lib/importlib/test/extension/test_case_sensitivity.py
@@ -20,12 +20,18 @@ class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
def test_case_sensitive(self):
with support.EnvironmentVarGuard() as env:
env.unset('PYTHONCASEOK')
+ if b'PYTHONCASEOK' in _bootstrap._os.environ:
+ self.skipTest('os.environ changes not reflected in '
+ '_os.environ')
loader = self.find_module()
self.assertIsNone(loader)
def test_case_insensitivity(self):
with support.EnvironmentVarGuard() as env:
env.set('PYTHONCASEOK', '1')
+ if b'PYTHONCASEOK' not in _bootstrap._os.environ:
+ self.skipTest('os.environ changes not reflected in '
+ '_os.environ')
loader = self.find_module()
self.assertTrue(hasattr(loader, 'load_module'))
diff --git a/Lib/importlib/test/import_/test___package__.py b/Lib/importlib/test/import_/test___package__.py
index 5056ae59cca..783cde17294 100644
--- a/Lib/importlib/test/import_/test___package__.py
+++ b/Lib/importlib/test/import_/test___package__.py
@@ -67,7 +67,7 @@ class Using__package__(unittest.TestCase):
def test_bunk__package__(self):
globals = {'__package__': 42}
- with self.assertRaises(ValueError):
+ with self.assertRaises(TypeError):
import_util.import_('', globals, {}, ['relimport'], 1)
diff --git a/Lib/importlib/test/import_/test_api.py b/Lib/importlib/test/import_/test_api.py
index 9075d427597..2fa1f909547 100644
--- a/Lib/importlib/test/import_/test_api.py
+++ b/Lib/importlib/test/import_/test_api.py
@@ -12,6 +12,13 @@ class APITest(unittest.TestCase):
with self.assertRaises(TypeError):
util.import_(42)
+ def test_negative_level(self):
+ # Raise ValueError when a negative level is specified.
+ # PEP 328 did away with sys.module None entries and the ambiguity of
+ # absolute/relative imports.
+ with self.assertRaises(ValueError):
+ util.import_('os', globals(), level=-1)
+
def test_main():
from test.support import run_unittest
diff --git a/Lib/importlib/test/import_/test_path.py b/Lib/importlib/test/import_/test_path.py
index 2faa23174b3..57133196126 100644
--- a/Lib/importlib/test/import_/test_path.py
+++ b/Lib/importlib/test/import_/test_path.py
@@ -73,6 +73,16 @@ class FinderTests(unittest.TestCase):
loader = machinery.PathFinder.find_module(module)
self.assertTrue(loader is importer)
+ def test_path_importer_cache_empty_string(self):
+ # The empty string should create a finder using the cwd.
+ path = ''
+ module = '<test module>'
+ importer = util.mock_modules(module)
+ hook = import_util.mock_path_hook(os.curdir, importer=importer)
+ with util.import_state(path=[path], path_hooks=[hook]):
+ loader = machinery.PathFinder.find_module(module)
+ self.assertIs(loader, importer)
+ self.assertIn(os.curdir, sys.path_importer_cache)
class DefaultPathFinderTests(unittest.TestCase):
diff --git a/Lib/importlib/test/regrtest.py b/Lib/importlib/test/regrtest.py
index b103ae7d0e9..dc0eb97022f 100644
--- a/Lib/importlib/test/regrtest.py
+++ b/Lib/importlib/test/regrtest.py
@@ -5,13 +5,6 @@ invalidates are automatically skipped if the entire test suite is run.
Otherwise all command-line options valid for test.regrtest are also valid for
this script.
-XXX FAILING
- * test_import
- - test_incorrect_code_name
- file name differing between __file__ and co_filename (r68360 on trunk)
- - test_import_by_filename
- exception for trying to import by file name does not match
-
"""
import importlib
import sys
diff --git a/Lib/importlib/test/source/test_abc_loader.py b/Lib/importlib/test/source/test_abc_loader.py
index 32459074a07..01acda4b9a6 100644
--- a/Lib/importlib/test/source/test_abc_loader.py
+++ b/Lib/importlib/test/source/test_abc_loader.py
@@ -40,8 +40,10 @@ class SourceLoaderMock(SourceOnlyLoaderMock):
def __init__(self, path, magic=imp.get_magic()):
super().__init__(path)
self.bytecode_path = imp.cache_from_source(self.path)
+ self.source_size = len(self.source)
data = bytearray(magic)
- data.extend(marshal._w_long(self.source_mtime))
+ data.extend(importlib._w_long(self.source_mtime))
+ data.extend(importlib._w_long(self.source_size))
code_object = compile(self.source, self.path, 'exec',
dont_inherit=True)
data.extend(marshal.dumps(code_object))
@@ -56,9 +58,9 @@ class SourceLoaderMock(SourceOnlyLoaderMock):
else:
raise IOError
- def path_mtime(self, path):
+ def path_stats(self, path):
assert path == self.path
- return self.source_mtime
+ return {'mtime': self.source_mtime, 'size': self.source_size}
def set_data(self, path, data):
self.written[path] = bytes(data)
@@ -102,7 +104,7 @@ class PyLoaderMock(abc.PyLoader):
warnings.simplefilter("always")
path = super().get_filename(name)
assert len(w) == 1
- assert issubclass(w[0].category, PendingDeprecationWarning)
+ assert issubclass(w[0].category, DeprecationWarning)
return path
@@ -198,7 +200,7 @@ class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
warnings.simplefilter("always")
code_object = super().get_code(name)
assert len(w) == 1
- assert issubclass(w[0].category, PendingDeprecationWarning)
+ assert issubclass(w[0].category, DeprecationWarning)
return code_object
class PyLoaderTests(testing_abc.LoaderTests):
@@ -656,7 +658,8 @@ class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
if bytecode_written:
self.assertIn(self.cached, self.loader.written)
data = bytearray(imp.get_magic())
- data.extend(marshal._w_long(self.loader.source_mtime))
+ data.extend(importlib._w_long(self.loader.source_mtime))
+ data.extend(importlib._w_long(self.loader.source_size))
data.extend(marshal.dumps(code_object))
self.assertEqual(self.loader.written[self.cached], bytes(data))
@@ -847,7 +850,7 @@ class AbstractMethodImplTests(unittest.TestCase):
# Required abstractmethods.
self.raises_NotImplementedError(ins, 'get_filename', 'get_data')
# Optional abstractmethods.
- self.raises_NotImplementedError(ins,'path_mtime', 'set_data')
+ self.raises_NotImplementedError(ins,'path_stats', 'set_data')
def test_PyLoader(self):
self.raises_NotImplementedError(self.PyLoader(), 'source_path',
diff --git a/Lib/importlib/test/source/test_case_sensitivity.py b/Lib/importlib/test/source/test_case_sensitivity.py
index 73777de4ba4..569f516d5a2 100644
--- a/Lib/importlib/test/source/test_case_sensitivity.py
+++ b/Lib/importlib/test/source/test_case_sensitivity.py
@@ -37,6 +37,9 @@ class CaseSensitivityTest(unittest.TestCase):
def test_sensitive(self):
with test_support.EnvironmentVarGuard() as env:
env.unset('PYTHONCASEOK')
+ if b'PYTHONCASEOK' in _bootstrap._os.environ:
+ self.skipTest('os.environ changes not reflected in '
+ '_os.environ')
sensitive, insensitive = self.sensitivity_test()
self.assertTrue(hasattr(sensitive, 'load_module'))
self.assertIn(self.name, sensitive.get_filename(self.name))
@@ -45,6 +48,9 @@ class CaseSensitivityTest(unittest.TestCase):
def test_insensitive(self):
with test_support.EnvironmentVarGuard() as env:
env.set('PYTHONCASEOK', '1')
+ if b'PYTHONCASEOK' not in _bootstrap._os.environ:
+ self.skipTest('os.environ changes not reflected in '
+ '_os.environ')
sensitive, insensitive = self.sensitivity_test()
self.assertTrue(hasattr(sensitive, 'load_module'))
self.assertIn(self.name, sensitive.get_filename(self.name))
diff --git a/Lib/importlib/test/source/test_file_loader.py b/Lib/importlib/test/source/test_file_loader.py
index c7a7d8fbcae..21e718f7d5d 100644
--- a/Lib/importlib/test/source/test_file_loader.py
+++ b/Lib/importlib/test/source/test_file_loader.py
@@ -71,11 +71,6 @@ class SimpleTest(unittest.TestCase):
module_dict_id = id(module.__dict__)
with open(mapping['_temp'], '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.path_mtime = self.fake_mtime(loader.path_mtime)
module = loader.load_module('_temp')
self.assertTrue('testing_var' in module.__dict__,
"'testing_var' not in "
@@ -215,10 +210,17 @@ class BadBytecodeTest(unittest.TestCase):
del_source=del_source)
test('_temp', mapping, bc_path)
+ def _test_partial_size(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:11],
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
def _test_no_marshal(self, *, del_source=False):
with source_util.create_modules('_temp') as mapping:
bc_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: bc[:8],
+ lambda bc: bc[:12],
del_source=del_source)
file_path = mapping['_temp'] if not del_source else bc_path
with self.assertRaises(EOFError):
@@ -227,7 +229,7 @@ class BadBytecodeTest(unittest.TestCase):
def _test_non_code_marshal(self, *, del_source=False):
with source_util.create_modules('_temp') as mapping:
bytecode_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: bc[:8] + marshal.dumps(b'abcd'),
+ lambda bc: bc[:12] + marshal.dumps(b'abcd'),
del_source=del_source)
file_path = mapping['_temp'] if not del_source else bytecode_path
with self.assertRaises(ImportError):
@@ -236,7 +238,7 @@ class BadBytecodeTest(unittest.TestCase):
def _test_bad_marshal(self, *, del_source=False):
with source_util.create_modules('_temp') as mapping:
bytecode_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: bc[:8] + b'<test>',
+ lambda bc: bc[:12] + b'<test>',
del_source=del_source)
file_path = mapping['_temp'] if not del_source else bytecode_path
with self.assertRaises(EOFError):
@@ -260,7 +262,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
def test(name, mapping, bytecode_path):
self.import_(mapping[name], name)
with open(bytecode_path, 'rb') as file:
- self.assertGreater(len(file.read()), 8)
+ self.assertGreater(len(file.read()), 12)
self._test_empty_file(test)
@@ -268,7 +270,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
def test(name, mapping, bytecode_path):
self.import_(mapping[name], name)
with open(bytecode_path, 'rb') as file:
- self.assertGreater(len(file.read()), 8)
+ self.assertGreater(len(file.read()), 12)
self._test_partial_magic(test)
@@ -279,7 +281,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
def test(name, mapping, bytecode_path):
self.import_(mapping[name], name)
with open(bytecode_path, 'rb') as file:
- self.assertGreater(len(file.read()), 8)
+ self.assertGreater(len(file.read()), 12)
self._test_magic_only(test)
@@ -301,11 +303,22 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest):
def test(name, mapping, bc_path):
self.import_(mapping[name], name)
with open(bc_path, 'rb') as file:
- self.assertGreater(len(file.read()), 8)
+ self.assertGreater(len(file.read()), 12)
self._test_partial_timestamp(test)
@source_util.writes_bytecode_files
+ def test_partial_size(self):
+ # When the size is partial, regenerate the .pyc, else
+ # raise EOFError.
+ def test(name, mapping, bc_path):
+ self.import_(mapping[name], name)
+ with open(bc_path, 'rb') as file:
+ self.assertGreater(len(file.read()), 12)
+
+ self._test_partial_size(test)
+
+ @source_util.writes_bytecode_files
def test_no_marshal(self):
# When there is only the magic number and timestamp, raise EOFError.
self._test_no_marshal()
@@ -400,6 +413,13 @@ class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
self._test_partial_timestamp(test, del_source=True)
+ def test_partial_size(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(EOFError):
+ self.import_(bytecode_path, name)
+
+ self._test_partial_size(test, del_source=True)
+
def test_no_marshal(self):
self._test_no_marshal(del_source=True)
diff --git a/Lib/importlib/test/source/test_finder.py b/Lib/importlib/test/source/test_finder.py
index 7b9088da0ce..68e9ae71a42 100644
--- a/Lib/importlib/test/source/test_finder.py
+++ b/Lib/importlib/test/source/test_finder.py
@@ -143,6 +143,13 @@ class FinderTests(abc.FinderTests):
finally:
os.unlink('mod.py')
+ def test_invalidate_caches(self):
+ # invalidate_caches() should reset the mtime.
+ finder = _bootstrap._FileFinder('', _bootstrap._SourceFinderDetails())
+ finder._path_mtime = 42
+ finder.invalidate_caches()
+ self.assertEqual(finder._path_mtime, -1)
+
def test_main():
from test.support import run_unittest
diff --git a/Lib/importlib/test/test_api.py b/Lib/importlib/test/test_api.py
index a151626de7d..cc147c200bd 100644
--- a/Lib/importlib/test/test_api.py
+++ b/Lib/importlib/test/test_api.py
@@ -84,6 +84,34 @@ class ImportModuleTests(unittest.TestCase):
importlib.import_module('a.b')
self.assertEqual(b_load_count, 1)
+
+class InvalidateCacheTests(unittest.TestCase):
+
+ def test_method_called(self):
+ # If defined the method should be called.
+ class InvalidatingNullFinder:
+ def __init__(self, *ignored):
+ self.called = False
+ def find_module(self, *args):
+ return None
+ def invalidate_caches(self):
+ self.called = True
+
+ key = 'gobledeegook'
+ ins = InvalidatingNullFinder()
+ sys.path_importer_cache[key] = ins
+ self.addCleanup(lambda: sys.path_importer_cache.__delitem__(key))
+ importlib.invalidate_caches()
+ self.assertTrue(ins.called)
+
+ def test_method_lacking(self):
+ # There should be no issues if the method is not defined.
+ key = 'gobbledeegook'
+ sys.path_importer_cache[key] = imp.NullImporter('abc')
+ self.addCleanup(lambda: sys.path_importer_cache.__delitem__(key))
+ importlib.invalidate_caches() # Shouldn't trigger an exception.
+
+
def test_main():
from test.support import run_unittest
run_unittest(ImportModuleTests)
diff --git a/Lib/importlib/test/test_util.py b/Lib/importlib/test/test_util.py
index 602447f09e4..c7cdad14d73 100644
--- a/Lib/importlib/test/test_util.py
+++ b/Lib/importlib/test/test_util.py
@@ -59,6 +59,11 @@ class ModuleForLoaderTests(unittest.TestCase):
self.raise_exception(name)
self.assertIs(module, sys.modules[name])
+ def test_decorator_attrs(self):
+ def fxn(self, module): pass
+ wrapped = util.module_for_loader(fxn)
+ self.assertEqual(wrapped.__name__, fxn.__name__)
+ self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
class SetPackageTests(unittest.TestCase):
@@ -108,6 +113,11 @@ class SetPackageTests(unittest.TestCase):
module.__package__ = value
self.verify(module, value)
+ def test_decorator_attrs(self):
+ def fxn(module): pass
+ wrapped = util.set_package(fxn)
+ self.assertEqual(wrapped.__name__, fxn.__name__)
+ self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
def test_main():
from test import support