diff -r 17d730d37b2f Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py Fri Nov 01 10:37:57 2013 -0400 +++ b/Lib/importlib/_bootstrap.py Fri Nov 01 13:43:39 2013 -0400 @@ -37,6 +37,78 @@ return _relax_case +class _FileSystem: + + @property + def case_insensitive(self): + """Is the file system case-insensitive for import purposes?""" + return _relax_case() + + def split(self, path): + """Replacement for os.path.split().""" + if len(path_separators) == 1: + front, _, tail = path.rpartition(path_sep) + return front, tail + for x in reversed(path): + if x in path_separators: + front, tail = path.rsplit(x, maxsplit=1) + return front, tail + return '', path + + def join(self, *path_parts): + return path_sep.join([part.rstrip(path_separators) + for part in path_parts if part]) + + def stat(self, path): + return _os.stat(path) + + def listdir(self, path): + return _os.listdir(path) + + def _path_is_mode_type(self, path, mode): + """Test whether the path is the specified mode type.""" + try: + stat_info = self.stat(path) + except OSError: + return False + return (stat_info.st_mode & 0o170000) == mode + + def is_file(self, path): + return self._path_is_mode_type(path, 0o100000) + + def is_dir(self, path): + if not path: + path = _os.getcwd() + return self._path_is_mode_type(path, 0o040000) + + def open(self, *args, **kwargs): + return _os.open(*args, **kwargs) + + def unlink(self, path): + _os.unlink(path) + + def replace(self, old, new): + return _os.replace(old, new) + + def mkdir(self, path): + _os.mkdir(path) + + +class _HackedFileSystem(_FileSystem): + + _path_stats = {} + + def stat(self, path): + try: + stat = self._path_stats[path] + except KeyError: + stat = super().stat(path) + self._path_stats[path] = stat + return stat + +_FS = _HackedFileSystem() + + def _w_long(x): """Convert a 32-bit integer to little-endian.""" return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little') @@ -47,64 +119,23 @@ return int.from_bytes(int_bytes, 'little') -def _path_join(*path_parts): - """Replacement for os.path.join().""" - return path_sep.join([part.rstrip(path_separators) - for part in path_parts if part]) - - -def _path_split(path): - """Replacement for os.path.split().""" - if len(path_separators) == 1: - front, _, tail = path.rpartition(path_sep) - return front, tail - for x in reversed(path): - if x in path_separators: - front, tail = path.rsplit(x, maxsplit=1) - return front, tail - return '', path - - -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.""" - if not path: - path = _os.getcwd() - return _path_is_mode_type(path, 0o040000) - - def _write_atomic(path, data, mode=0o666): """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, + fd = _FS.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, mode & 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) + _FS.replace(path_tmp, path) except OSError: try: - _os.unlink(path_tmp) + _FS.unlink(path_tmp) except OSError: pass raise @@ -404,13 +435,13 @@ suffixes = DEBUG_BYTECODE_SUFFIXES else: suffixes = OPTIMIZED_BYTECODE_SUFFIXES - head, tail = _path_split(path) + head, tail = _FS.split(path) base_filename, sep, _ = tail.partition('.') tag = sys.implementation.cache_tag if tag is None: raise NotImplementedError('sys.implementation.cache_tag is None') filename = ''.join([base_filename, sep, tag, suffixes[0]]) - return _path_join(head, _PYCACHE, filename) + return _FS.join(head, _PYCACHE, filename) def source_from_cache(path): @@ -424,8 +455,8 @@ """ if sys.implementation.cache_tag is None: raise NotImplementedError('sys.implementation.cache_tag is None') - head, pycache_filename = _path_split(path) - head, pycache = _path_split(head) + head, pycache_filename = _FS.split(path) + head, pycache = _FS.split(head) if pycache != _PYCACHE: raise ValueError('{} not bottom-level directory in ' '{!r}'.format(_PYCACHE, path)) @@ -433,7 +464,7 @@ raise ValueError('expected only 2 dots in ' '{!r}'.format(pycache_filename)) base_filename = pycache_filename.partition('.')[0] - return _path_join(head, base_filename + SOURCE_SUFFIXES[0]) + return _FS.join(head, base_filename + SOURCE_SUFFIXES[0]) def _get_sourcefile(bytecode_path): @@ -452,13 +483,13 @@ source_path = source_from_cache(bytecode_path) except (NotImplementedError, ValueError): source_path = bytecode_path[:-1] - return source_path if _path_isfile(source_path) else bytecode_path + return source_path if _FS.is_file(source_path) else bytecode_path def _calc_mode(path): """Calculate the mode permissions for a bytecode file.""" try: - mode = _os.stat(path).st_mode + mode = _FS.stat(path).st_mode except OSError: mode = 0o666 # We always ensure write access so we can update cached files @@ -566,7 +597,7 @@ pass else: if module.__name__ == module.__package__: - module.__path__.append(_path_split(module.__file__)[0]) + module.__path__.append(_FS.split(module.__file__)[0]) def set_package(fxn): @@ -880,7 +911,7 @@ if filepath is None: return None try: - _os.stat(filepath) + _FS.stat(filepath) except OSError: return None for loader, suffixes in _get_supported_file_loaders(): @@ -896,7 +927,7 @@ def is_package(self, fullname): """Concrete implementation of InspectLoader.is_package by checking if the path returned by get_filename has a filename of '__init__.py'.""" - filename = _path_split(self.get_filename(fullname))[1] + filename = _FS.split(self.get_filename(fullname))[1] filename_base = filename.rsplit('.', 1)[0] tail_name = fullname.rpartition('.')[2] return filename_base == '__init__' and tail_name != '__init__' @@ -1074,7 +1105,7 @@ def path_stats(self, path): """Return the metadata for the path.""" - st = _os.stat(path) + st = _FS.stat(path) return {'mtime': st.st_mtime, 'size': st.st_size} def _cache_bytecode(self, source_path, bytecode_path, data): @@ -1084,17 +1115,17 @@ def set_data(self, path, data, *, _mode=0o666): """Write bytes data to a file.""" - parent, filename = _path_split(path) + parent, filename = _FS.split(path) path_parts = [] # Figure out what directories are missing. - while parent and not _path_isdir(parent): - parent, part = _path_split(parent) + while parent and not _FS.is_dir(parent): + parent, part = _FS.split(parent) path_parts.append(part) # Create needed directories. for part in reversed(path_parts): - parent = _path_join(parent, part) + parent = _FS.join(parent, part) try: - _os.mkdir(parent) + _FS.mkdir(parent) except FileExistsError: # Probably another Python process already created the dir. continue @@ -1161,7 +1192,7 @@ def is_package(self, fullname): """Return True if the extension module is a package.""" - file_name = _path_split(self.path)[1] + file_name = _FS.split(self.path)[1] return any(file_name == '__init__' + suffix for suffix in EXTENSION_SUFFIXES) @@ -1392,14 +1423,14 @@ is_namespace = False tail_module = fullname.rpartition('.')[2] try: - mtime = _os.stat(self.path or _os.getcwd()).st_mtime + mtime = _FS.stat(self.path or _os.getcwd()).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(): + if _FS.case_insensitive: cache = self._relaxed_path_cache cache_module = tail_module.lower() else: @@ -1407,22 +1438,22 @@ cache_module = tail_module # Check if the module is the name of a directory (and thus a package). if cache_module in cache: - base_path = _path_join(self.path, tail_module) + base_path = _FS.join(self.path, tail_module) for suffix, loader in self._loaders: init_filename = '__init__' + suffix - full_path = _path_join(base_path, init_filename) - if _path_isfile(full_path): + full_path = _FS.join(base_path, init_filename) + if _FS.is_file(full_path): return (loader(fullname, full_path), [base_path]) else: # If a namespace package, return the path if we don't # find a module in the next section. - is_namespace = _path_isdir(base_path) + is_namespace = _FS.is_dir(base_path) # Check for a file w/ a proper suffix exists. for suffix, loader in self._loaders: - full_path = _path_join(self.path, tail_module + suffix) + full_path = _FS.join(self.path, tail_module + suffix) _verbose_message('trying {}'.format(full_path), verbosity=2) if cache_module + suffix in cache: - if _path_isfile(full_path): + if _FS.is_file(full_path): return (loader(fullname, full_path), []) if is_namespace: _verbose_message('possible namespace for {}'.format(base_path)) @@ -1433,7 +1464,7 @@ """Fill the cache of potential modules and packages for this directory.""" path = self.path try: - contents = _os.listdir(path or _os.getcwd()) + contents = _FS.listdir(path or _os.getcwd()) except (FileNotFoundError, PermissionError, NotADirectoryError): # Directory has either been removed, turned into a file, or made # unreadable. @@ -1472,7 +1503,7 @@ """ def path_hook_for_FileFinder(path): """Path hook for importlib.machinery.FileFinder.""" - if not _path_isdir(path): + if not _FS.is_dir(path): raise ImportError('only directories are supported', path=path) return cls(path, *loader_details) @@ -1760,7 +1791,7 @@ os_details = ('posix', ['/']), ('nt', ['\\', '/']) for builtin_os, path_separators in os_details: - # Assumption made in _path_join() + # Assumption made in _FS.join() assert all(len(sep) == 1 for sep in path_separators) path_sep = path_separators[0] if builtin_os in sys.modules: