summaryrefslogtreecommitdiff
path: root/Lib/os.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/os.py')
-rw-r--r--Lib/os.py251
1 files changed, 121 insertions, 130 deletions
diff --git a/Lib/os.py b/Lib/os.py
index b4c651d24d..0c9107ca00 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -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__')