summaryrefslogtreecommitdiff
path: root/Lib/pathlib.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/pathlib.py')
-rw-r--r--Lib/pathlib.py147
1 files changed, 80 insertions, 67 deletions
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index c0d858ef56..9f347216b1 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -178,12 +178,28 @@ class _WindowsFlavour(_Flavour):
def casefold_parts(self, parts):
return [p.lower() for p in parts]
- def resolve(self, path):
+ def resolve(self, path, strict=False):
s = str(path)
if not s:
return os.getcwd()
+ previous_s = None
if _getfinalpathname is not None:
- return self._ext_to_normal(_getfinalpathname(s))
+ if strict:
+ return self._ext_to_normal(_getfinalpathname(s))
+ else:
+ while True:
+ try:
+ s = self._ext_to_normal(_getfinalpathname(s))
+ except FileNotFoundError:
+ previous_s = s
+ s = os.path.dirname(s)
+ if previous_s == s:
+ return path
+ else:
+ if previous_s is None:
+ return s
+ else:
+ return s + os.path.sep + os.path.basename(previous_s)
# Means fallback on absolute
return None
@@ -285,7 +301,7 @@ class _PosixFlavour(_Flavour):
def casefold_parts(self, parts):
return parts
- def resolve(self, path):
+ def resolve(self, path, strict=False):
sep = self.sep
accessor = path._accessor
seen = {}
@@ -315,7 +331,10 @@ class _PosixFlavour(_Flavour):
target = accessor.readlink(newpath)
except OSError as e:
if e.errno != EINVAL:
- raise
+ if strict:
+ raise
+ else:
+ return newpath
# Not a symlink
path = newpath
else:
@@ -385,6 +404,8 @@ class _NormalAccessor(_Accessor):
listdir = _wrap_strfunc(os.listdir)
+ scandir = _wrap_strfunc(os.scandir)
+
chmod = _wrap_strfunc(os.chmod)
if hasattr(os, "lchmod"):
@@ -429,25 +450,6 @@ _normal_accessor = _NormalAccessor()
# Globbing helpers
#
-@contextmanager
-def _cached(func):
- try:
- func.__cached__
- yield func
- except AttributeError:
- cache = {}
- def wrapper(*args):
- try:
- return cache[args]
- except KeyError:
- value = cache[args] = func(*args)
- return value
- wrapper.__cached__ = True
- try:
- yield wrapper
- finally:
- cache.clear()
-
def _make_selector(pattern_parts):
pat = pattern_parts[0]
child_parts = pattern_parts[1:]
@@ -473,8 +475,10 @@ class _Selector:
self.child_parts = child_parts
if child_parts:
self.successor = _make_selector(child_parts)
+ self.dironly = True
else:
self.successor = _TerminatingSelector()
+ self.dironly = False
def select_from(self, parent_path):
"""Iterate over all child paths of `parent_path` matched by this
@@ -482,13 +486,15 @@ class _Selector:
path_cls = type(parent_path)
is_dir = path_cls.is_dir
exists = path_cls.exists
- listdir = parent_path._accessor.listdir
- return self._select_from(parent_path, is_dir, exists, listdir)
+ scandir = parent_path._accessor.scandir
+ if not is_dir(parent_path):
+ return iter([])
+ return self._select_from(parent_path, is_dir, exists, scandir)
class _TerminatingSelector:
- def _select_from(self, parent_path, is_dir, exists, listdir):
+ def _select_from(self, parent_path, is_dir, exists, scandir):
yield parent_path
@@ -498,13 +504,11 @@ class _PreciseSelector(_Selector):
self.name = name
_Selector.__init__(self, child_parts)
- def _select_from(self, parent_path, is_dir, exists, listdir):
+ def _select_from(self, parent_path, is_dir, exists, scandir):
try:
- if not is_dir(parent_path):
- return
path = parent_path._make_child_relpath(self.name)
- if exists(path):
- for p in self.successor._select_from(path, is_dir, exists, listdir):
+ if (is_dir if self.dironly else exists)(path):
+ for p in self.successor._select_from(path, is_dir, exists, scandir):
yield p
except PermissionError:
return
@@ -516,17 +520,18 @@ class _WildcardSelector(_Selector):
self.pat = re.compile(fnmatch.translate(pat))
_Selector.__init__(self, child_parts)
- def _select_from(self, parent_path, is_dir, exists, listdir):
+ def _select_from(self, parent_path, is_dir, exists, scandir):
try:
- if not is_dir(parent_path):
- return
cf = parent_path._flavour.casefold
- for name in listdir(parent_path):
- casefolded = cf(name)
- if self.pat.match(casefolded):
- path = parent_path._make_child_relpath(name)
- for p in self.successor._select_from(path, is_dir, exists, listdir):
- yield p
+ entries = list(scandir(parent_path))
+ for entry in entries:
+ if not self.dironly or entry.is_dir():
+ name = entry.name
+ casefolded = cf(name)
+ if self.pat.match(casefolded):
+ path = parent_path._make_child_relpath(name)
+ for p in self.successor._select_from(path, is_dir, exists, scandir):
+ yield p
except PermissionError:
return
@@ -537,32 +542,30 @@ class _RecursiveWildcardSelector(_Selector):
def __init__(self, pat, child_parts):
_Selector.__init__(self, child_parts)
- def _iterate_directories(self, parent_path, is_dir, listdir):
+ def _iterate_directories(self, parent_path, is_dir, scandir):
yield parent_path
try:
- for name in listdir(parent_path):
- path = parent_path._make_child_relpath(name)
- if is_dir(path) and not path.is_symlink():
- for p in self._iterate_directories(path, is_dir, listdir):
+ entries = list(scandir(parent_path))
+ for entry in entries:
+ if entry.is_dir() and not entry.is_symlink():
+ path = parent_path._make_child_relpath(entry.name)
+ for p in self._iterate_directories(path, is_dir, scandir):
yield p
except PermissionError:
return
- def _select_from(self, parent_path, is_dir, exists, listdir):
+ def _select_from(self, parent_path, is_dir, exists, scandir):
try:
- if not is_dir(parent_path):
- return
- with _cached(listdir) as listdir:
- yielded = set()
- try:
- successor_select = self.successor._select_from
- for starting_point in self._iterate_directories(parent_path, is_dir, listdir):
- for p in successor_select(starting_point, is_dir, exists, listdir):
- if p not in yielded:
- yield p
- yielded.add(p)
- finally:
- yielded.clear()
+ yielded = set()
+ try:
+ successor_select = self.successor._select_from
+ for starting_point in self._iterate_directories(parent_path, is_dir, scandir):
+ for p in successor_select(starting_point, is_dir, exists, scandir):
+ if p not in yielded:
+ yield p
+ yielded.add(p)
+ finally:
+ yielded.clear()
except PermissionError:
return
@@ -634,13 +637,16 @@ class PurePath(object):
for a in args:
if isinstance(a, PurePath):
parts += a._parts
- elif isinstance(a, str):
- # Force-cast str subclasses to str (issue #21127)
- parts.append(str(a))
else:
- raise TypeError(
- "argument should be a path or str object, not %r"
- % type(a))
+ a = os.fspath(a)
+ if isinstance(a, str):
+ # Force-cast str subclasses to str (issue #21127)
+ parts.append(str(a))
+ else:
+ raise TypeError(
+ "argument should be a str object or an os.PathLike "
+ "object returning str, not %r"
+ % type(a))
return cls._flavour.parse_parts(parts)
@classmethod
@@ -693,6 +699,9 @@ class PurePath(object):
self._parts) or '.'
return self._str
+ def __fspath__(self):
+ return str(self)
+
def as_posix(self):
"""Return the string representation of the path with forward (/)
slashes."""
@@ -943,6 +952,10 @@ class PurePath(object):
return False
return True
+# Can't subclass os.PathLike from PurePath and keep the constructor
+# optimizations in PurePath._parse_args().
+os.PathLike.register(PurePath)
+
class PurePosixPath(PurePath):
_flavour = _posix_flavour
@@ -1098,7 +1111,7 @@ class Path(PurePath):
obj._init(template=self)
return obj
- def resolve(self):
+ def resolve(self, strict=False):
"""
Make the path absolute, resolving all symlinks on the way and also
normalizing it (for example turning slashes into backslashes under
@@ -1106,7 +1119,7 @@ class Path(PurePath):
"""
if self._closed:
self._raise_closed()
- s = self._flavour.resolve(self)
+ s = self._flavour.resolve(self, strict=strict)
if s is None:
# No symlink resolution => for consistency, raise an error if
# the path doesn't exist or is forbidden