GH-127381: pathlib ABCs: remove PathBase.resolve() and absolute() (#127707)

Remove our implementation of POSIX path resolution in `PathBase.resolve()`.
This functionality is rather fragile and isn't necessary in most cases. It
depends on `PathBase.stat()`, which we're looking to remove.

Also remove `PathBase.absolute()`. Many legitimate virtual filesystems lack
the notion of a 'current directory', so it's wrong to include in the basic
interface.
This commit is contained in:
Barney Gale 2024-12-06 21:39:45 +00:00 committed by GitHub
parent 0fc4063747
commit 31c9f3ced2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 599 additions and 731 deletions

View file

@ -13,7 +13,6 @@ resemble pathlib's PurePath and Path respectively.
import functools
import operator
import posixpath
from errno import EINVAL
from glob import _GlobberBase, _no_recurse_symlinks
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
@ -115,11 +114,6 @@ class PurePathBase:
# The `_raw_paths` slot stores unjoined string paths. This is set in
# the `__init__()` method.
'_raw_paths',
# The '_resolving' slot stores a boolean indicating whether the path
# is being processed by `PathBase.resolve()`. This prevents duplicate
# work from occurring when `resolve()` calls `stat()` or `readlink()`.
'_resolving',
)
parser = ParserBase()
_globber = PathGlobber
@ -130,7 +124,6 @@ class PurePathBase:
raise TypeError(
f"argument should be a str, not {type(arg).__name__!r}")
self._raw_paths = list(args)
self._resolving = False
def with_segments(self, *pathsegments):
"""Construct a new path object from any number of path-like objects.
@ -339,9 +332,7 @@ class PurePathBase:
path = str(self)
parent = self.parser.split(path)[0]
if path != parent:
parent = self.with_segments(parent)
parent._resolving = self._resolving
return parent
return self.with_segments(parent)
return self
@property
@ -424,9 +415,6 @@ class PathBase(PurePathBase):
"""
__slots__ = ()
# Maximum number of symlinks to follow in resolve()
_max_symlinks = 40
@classmethod
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported"
@ -720,20 +708,6 @@ class PathBase(PurePathBase):
yield path, dirnames, filenames
paths += [path.joinpath(d) for d in reversed(dirnames)]
def absolute(self):
"""Return an absolute version of this path
No normalization or symlink resolution is performed.
Use resolve() to resolve symlinks and remove '..' segments.
"""
if self.is_absolute():
return self
elif self.parser is not posixpath:
raise UnsupportedOperation(self._unsupported_msg('absolute()'))
else:
# Treat the root directory as the current working directory.
return self.with_segments('/', *self._raw_paths)
def expanduser(self):
""" Return a new path with expanded ~ and ~user constructs
(as returned by os.path.expanduser)
@ -745,42 +719,6 @@ class PathBase(PurePathBase):
Return the path to which the symbolic link points.
"""
raise UnsupportedOperation(self._unsupported_msg('readlink()'))
readlink._supported = False
def resolve(self, strict=False):
"""
Make the path absolute, resolving all symlinks on the way and also
normalizing it.
"""
if self._resolving:
return self
elif self.parser is not posixpath:
raise UnsupportedOperation(self._unsupported_msg('resolve()'))
def raise_error(*args):
raise OSError("Unsupported operation.")
getcwd = raise_error
if strict or getattr(self.readlink, '_supported', True):
def lstat(path_str):
path = self.with_segments(path_str)
path._resolving = True
return path.stat(follow_symlinks=False)
def readlink(path_str):
path = self.with_segments(path_str)
path._resolving = True
return str(path.readlink())
else:
# If the user has *not* overridden the `readlink()` method, then
# symlinks are unsupported and (in non-strict mode) we can improve
# performance by not calling `path.lstat()`.
lstat = readlink = raise_error
return self.with_segments(posixpath._realpath(
str(self.absolute()), strict, self.parser.sep,
getcwd=getcwd, lstat=lstat, readlink=readlink,
maxlinks=self._max_symlinks))
def symlink_to(self, target, target_is_directory=False):
"""