diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -21,6 +21,13 @@ work. One should use importlib as the pu CASE_INSENSITIVE_PLATFORMS = 'win', 'cygwin', 'darwin' + +def _relax_case(): + """True if filenames must be checked case-insensitively.""" + return (any(map(sys.platform.startswith, CASE_INSENSITIVE_PLATFORMS)) and + b'PYTHONCASEOK' in _os.environ) + + def _case_insensitive_ok(directory, check): """Check if the directory contains something matching 'check' exists in the directory. @@ -760,29 +767,53 @@ 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() 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) + if _relax_case: + tail_module = tail_module.lower() + try: + mtime = _os.stat(self.path).st_mtime + except OSError: + mtime = -1 + if mtime != self._path_mtime: + self._fill_cache() + self._path_mtime = mtime + cache = self._path_cache + if tail_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 mod_filename in cache: + full_path = _path_join(self.path, mod_filename) + 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 + l = _os.listdir(path) + if _relax_case: + self._path_cache = set(fn.lower() for fn in l) + else: + self._path_cache = set(l) + + class _SourceFinderDetails: loader = _SourceFileLoader @@ -1093,6 +1124,8 @@ def _setup(sys_module, imp_module): 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', _relax_case()) if sys_module.platform in CASE_INSENSITIVE_PLATFORMS: _case_ok = _case_insensitive_ok