diff options
Diffstat (limited to 'Lib/os.py')
-rw-r--r-- | Lib/os.py | 251 |
1 files changed, 121 insertions, 130 deletions
@@ -1,12 +1,12 @@ r"""OS routines for NT or Posix depending on what system we're on. This exports: - - all functions from posix, nt or ce, e.g. unlink, stat, etc. + - all functions from posix or nt, e.g. unlink, stat, etc. - os.path is either posixpath or ntpath - - os.name is either 'posix', 'nt' or 'ce'. - - os.curdir is a string representing the current directory ('.' or ':') - - os.pardir is a string representing the parent directory ('..' or '::') - - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\') + - os.name is either 'posix' or 'nt' + - os.curdir is a string representing the current directory (always '.') + - os.pardir is a string representing the parent directory (always '..') + - os.sep is the (or a most common) pathname separator ('/' or '\\') - os.extsep is the extension separator (always '.') - os.altsep is the alternate pathname separator (None or '/') - os.pathsep is the component separator used in $PATH etc @@ -22,7 +22,7 @@ and opendir), and leave all pathname manipulation to os.path """ #' - +import abc import sys, errno import stat as st @@ -85,27 +85,6 @@ elif 'nt' in _names: except ImportError: pass -elif 'ce' in _names: - name = 'ce' - linesep = '\r\n' - from ce import * - try: - from ce import _exit - __all__.append('_exit') - except ImportError: - pass - # We can use the standard Windows path. - import ntpath as path - - import ce - __all__.extend(_get_exports_list(ce)) - del ce - - try: - from ce import _have_functions - except ImportError: - pass - else: raise ImportError('no os specific module found') @@ -353,9 +332,10 @@ def walk(top, topdown=True, onerror=None, followlinks=False): dirs.remove('CVS') # don't visit CVS directories """ - + top = fspath(top) dirs = [] nondirs = [] + walk_dirs = [] # We may not have read permission for top, in which case we can't # get a list of the files the directory contains. os.walk @@ -363,48 +343,55 @@ def walk(top, topdown=True, onerror=None, followlinks=False): # minor reason when (say) a thousand readable directories are still # left to visit. That logic is copied here. try: - if name == 'nt' and isinstance(top, bytes): - scandir_it = _dummy_scandir(top) - else: - # Note that scandir is global in this module due - # to earlier import-*. - scandir_it = scandir(top) - entries = list(scandir_it) + # Note that scandir is global in this module due + # to earlier import-*. + scandir_it = scandir(top) except OSError as error: if onerror is not None: onerror(error) return - for entry in entries: - try: - is_dir = entry.is_dir() - except OSError: - # If is_dir() raises an OSError, consider that the entry is not - # a directory, same behaviour than os.path.isdir(). - is_dir = False - - if is_dir: - dirs.append(entry.name) - else: - nondirs.append(entry.name) + with scandir_it: + while True: + try: + try: + entry = next(scandir_it) + except StopIteration: + break + except OSError as error: + if onerror is not None: + onerror(error) + return - if not topdown and is_dir: - # Bottom-up: recurse into sub-directory, but exclude symlinks to - # directories if followlinks is False - if followlinks: - walk_into = True + try: + is_dir = entry.is_dir() + except OSError: + # If is_dir() raises an OSError, consider that the entry is not + # a directory, same behaviour than os.path.isdir(). + is_dir = False + + if is_dir: + dirs.append(entry.name) else: - try: - is_symlink = entry.is_symlink() - except OSError: - # If is_symlink() raises an OSError, consider that the - # entry is not a symbolic link, same behaviour than - # os.path.islink(). - is_symlink = False - walk_into = not is_symlink + nondirs.append(entry.name) - if walk_into: - yield from walk(entry.path, topdown, onerror, followlinks) + if not topdown and is_dir: + # Bottom-up: recurse into sub-directory, but exclude symlinks to + # directories if followlinks is False + if followlinks: + walk_into = True + else: + try: + is_symlink = entry.is_symlink() + except OSError: + # If is_symlink() raises an OSError, consider that the + # entry is not a symbolic link, same behaviour than + # os.path.islink(). + is_symlink = False + walk_into = not is_symlink + + if walk_into: + walk_dirs.append(entry.path) # Yield before recursion if going top down if topdown: @@ -421,57 +408,12 @@ def walk(top, topdown=True, onerror=None, followlinks=False): if followlinks or not islink(new_path): yield from walk(new_path, topdown, onerror, followlinks) else: + # Recurse into sub-directories + for new_path in walk_dirs: + yield from walk(new_path, topdown, onerror, followlinks) # Yield after recursion if going bottom up yield top, dirs, nondirs -class _DummyDirEntry: - """Dummy implementation of DirEntry - - Only used internally by os.walk(bytes). Since os.walk() doesn't need the - follow_symlinks parameter: don't implement it, always follow symbolic - links. - """ - - def __init__(self, dir, name): - self.name = name - self.path = path.join(dir, name) - # Mimick FindFirstFile/FindNextFile: we should get file attributes - # while iterating on a directory - self._stat = None - self._lstat = None - try: - self.stat(follow_symlinks=False) - except OSError: - pass - - def stat(self, *, follow_symlinks=True): - if follow_symlinks: - if self._stat is None: - self._stat = stat(self.path) - return self._stat - else: - if self._lstat is None: - self._lstat = stat(self.path, follow_symlinks=False) - return self._lstat - - def is_dir(self): - if self._lstat is not None and not self.is_symlink(): - # use the cache lstat - stat = self.stat(follow_symlinks=False) - return st.S_ISDIR(stat.st_mode) - - stat = self.stat() - return st.S_ISDIR(stat.st_mode) - - def is_symlink(self): - stat = self.stat(follow_symlinks=False) - return st.S_ISLNK(stat.st_mode) - -def _dummy_scandir(dir): - # listdir-based implementation for bytes patches on Windows - for name in listdir(dir): - yield _DummyDirEntry(dir, name) - __all__.append("walk") if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd: @@ -509,6 +451,8 @@ if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd: if 'CVS' in dirs: dirs.remove('CVS') # don't visit CVS directories """ + if not isinstance(top, int) or not hasattr(top, '__index__'): + top = fspath(top) # Note: To guard against symlink races, we use the standard # lstat()/open()/fstat() trick. orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd) @@ -843,36 +787,31 @@ if supports_bytes_environ: def _fscodec(): encoding = sys.getfilesystemencoding() - if encoding == 'mbcs': - errors = 'strict' - else: - errors = 'surrogateescape' + errors = sys.getfilesystemencodeerrors() def fsencode(filename): + """Encode filename (an os.PathLike, bytes, or str) to the filesystem + encoding with 'surrogateescape' error handler, return bytes unchanged. + On Windows, use 'strict' error handler if the file system encoding is + 'mbcs' (which is the default encoding). """ - Encode filename to the filesystem encoding with 'surrogateescape' error - handler, return bytes unchanged. On Windows, use 'strict' error handler if - the file system encoding is 'mbcs' (which is the default encoding). - """ - if isinstance(filename, bytes): - return filename - elif isinstance(filename, str): + filename = fspath(filename) # Does type-checking of `filename`. + if isinstance(filename, str): return filename.encode(encoding, errors) else: - raise TypeError("expect bytes or str, not %s" % type(filename).__name__) + return filename def fsdecode(filename): + """Decode filename (an os.PathLike, bytes, or str) from the filesystem + encoding with 'surrogateescape' error handler, return str unchanged. On + Windows, use 'strict' error handler if the file system encoding is + 'mbcs' (which is the default encoding). """ - Decode filename from the filesystem encoding with 'surrogateescape' error - handler, return str unchanged. On Windows, use 'strict' error handler if - the file system encoding is 'mbcs' (which is the default encoding). - """ - if isinstance(filename, str): - return filename - elif isinstance(filename, bytes): + filename = fspath(filename) # Does type-checking of `filename`. + if isinstance(filename, bytes): return filename.decode(encoding, errors) else: - raise TypeError("expect bytes or str, not %s" % type(filename).__name__) + return filename return fsencode, fsdecode @@ -1070,3 +1009,55 @@ def fdopen(fd, *args, **kwargs): raise TypeError("invalid fd type (%s, expected integer)" % type(fd)) import io return io.open(fd, *args, **kwargs) + + +# For testing purposes, make sure the function is available when the C +# implementation exists. +def _fspath(path): + """Return the path representation of a path-like object. + + If str or bytes is passed in, it is returned unchanged. Otherwise the + os.PathLike interface is used to get the path representation. If the + path representation is not str or bytes, TypeError is raised. If the + provided path is not str, bytes, or os.PathLike, TypeError is raised. + """ + if isinstance(path, (str, bytes)): + return path + + # Work from the object's type to match method resolution of other magic + # methods. + path_type = type(path) + try: + path_repr = path_type.__fspath__(path) + except AttributeError: + if hasattr(path_type, '__fspath__'): + raise + else: + raise TypeError("expected str, bytes or os.PathLike object, " + "not " + path_type.__name__) + if isinstance(path_repr, (str, bytes)): + return path_repr + else: + raise TypeError("expected {}.__fspath__() to return str or bytes, " + "not {}".format(path_type.__name__, + type(path_repr).__name__)) + +# If there is no C implementation, make the pure Python version the +# implementation as transparently as possible. +if not _exists('fspath'): + fspath = _fspath + fspath.__name__ = "fspath" + + +class PathLike(abc.ABC): + + """Abstract base class for implementing the file system path protocol.""" + + @abc.abstractmethod + def __fspath__(self): + """Return the file system path representation of the object.""" + raise NotImplementedError + + @classmethod + def __subclasshook__(cls, subclass): + return hasattr(subclass, '__fspath__') |