GH-139174: Prepare pathlib.Path.info for new methods (part 2) (#140155)

Merge `_Info`, `_StatResultInfo` and `_DirEntryInfo` into a single `_Info`
class. No other changes.

This will allow us to use a cached `os.stat()` result from our upcoming
`_Info.stat()` method even when we have a backing `os.DirEntry`.
This commit is contained in:
Barney Gale 2025-10-18 02:13:25 +01:00 committed by GitHub
parent f4e51f253a
commit 1bfe86caaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -611,11 +611,20 @@ class PureWindowsPath(PurePath):
__slots__ = () __slots__ = ()
class _Info: _STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed.
__slots__ = ('_path',)
def __init__(self, path):
class _Info:
"""Implementation of pathlib.types.PathInfo that provides status
information by querying a wrapped os.stat_result object. Don't try to
construct it yourself."""
__slots__ = ('_path', '_entry', '_stat_result', '_lstat_result')
def __init__(self, path, entry=None):
self._path = path self._path = path
self._entry = entry
self._stat_result = None
self._lstat_result = None
def __repr__(self): def __repr__(self):
path_type = "WindowsPath" if os.name == "nt" else "PosixPath" path_type = "WindowsPath" if os.name == "nt" else "PosixPath"
@ -623,7 +632,94 @@ def __repr__(self):
def _stat(self, *, follow_symlinks=True): def _stat(self, *, follow_symlinks=True):
"""Return the status as an os.stat_result.""" """Return the status as an os.stat_result."""
raise NotImplementedError if self._entry:
return self._entry.stat(follow_symlinks=follow_symlinks)
if follow_symlinks:
if not self._stat_result:
try:
self._stat_result = os.stat(self._path)
except (OSError, ValueError):
self._stat_result = _STAT_RESULT_ERROR
raise
return self._stat_result
else:
if not self._lstat_result:
try:
self._lstat_result = os.lstat(self._path)
except (OSError, ValueError):
self._lstat_result = _STAT_RESULT_ERROR
raise
return self._lstat_result
def exists(self, *, follow_symlinks=True):
"""Whether this path exists."""
if self._entry:
if not follow_symlinks:
return True
if follow_symlinks:
if self._stat_result is _STAT_RESULT_ERROR:
return False
else:
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
self._stat(follow_symlinks=follow_symlinks)
except (OSError, ValueError):
return False
return True
def is_dir(self, *, follow_symlinks=True):
"""Whether this path is a directory."""
if self._entry:
try:
return self._entry.is_dir(follow_symlinks=follow_symlinks)
except OSError:
return False
if follow_symlinks:
if self._stat_result is _STAT_RESULT_ERROR:
return False
else:
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
st = self._stat(follow_symlinks=follow_symlinks)
except (OSError, ValueError):
return False
return S_ISDIR(st.st_mode)
def is_file(self, *, follow_symlinks=True):
"""Whether this path is a regular file."""
if self._entry:
try:
return self._entry.is_file(follow_symlinks=follow_symlinks)
except OSError:
return False
if follow_symlinks:
if self._stat_result is _STAT_RESULT_ERROR:
return False
else:
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
st = self._stat(follow_symlinks=follow_symlinks)
except (OSError, ValueError):
return False
return S_ISREG(st.st_mode)
def is_symlink(self):
"""Whether this path is a symbolic link."""
if self._entry:
try:
return self._entry.is_symlink()
except OSError:
return False
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
st = self._stat(follow_symlinks=False)
except (OSError, ValueError):
return False
return S_ISLNK(st.st_mode)
def _posix_permissions(self, *, follow_symlinks=True): def _posix_permissions(self, *, follow_symlinks=True):
"""Return the POSIX file permissions.""" """Return the POSIX file permissions."""
@ -661,138 +757,6 @@ def _xattrs(self, *, follow_symlinks=True):
return [] return []
_STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed.
class _StatResultInfo(_Info):
"""Implementation of pathlib.types.PathInfo that provides status
information by querying a wrapped os.stat_result object. Don't try to
construct it yourself."""
__slots__ = ('_stat_result', '_lstat_result')
def __init__(self, path):
super().__init__(path)
self._stat_result = None
self._lstat_result = None
def _stat(self, *, follow_symlinks=True):
"""Return the status as an os.stat_result."""
if follow_symlinks:
if not self._stat_result:
try:
self._stat_result = os.stat(self._path)
except (OSError, ValueError):
self._stat_result = _STAT_RESULT_ERROR
raise
return self._stat_result
else:
if not self._lstat_result:
try:
self._lstat_result = os.lstat(self._path)
except (OSError, ValueError):
self._lstat_result = _STAT_RESULT_ERROR
raise
return self._lstat_result
def exists(self, *, follow_symlinks=True):
"""Whether this path exists."""
if follow_symlinks:
if self._stat_result is _STAT_RESULT_ERROR:
return False
else:
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
self._stat(follow_symlinks=follow_symlinks)
except (OSError, ValueError):
return False
return True
def is_dir(self, *, follow_symlinks=True):
"""Whether this path is a directory."""
if follow_symlinks:
if self._stat_result is _STAT_RESULT_ERROR:
return False
else:
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
st = self._stat(follow_symlinks=follow_symlinks)
except (OSError, ValueError):
return False
return S_ISDIR(st.st_mode)
def is_file(self, *, follow_symlinks=True):
"""Whether this path is a regular file."""
if follow_symlinks:
if self._stat_result is _STAT_RESULT_ERROR:
return False
else:
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
st = self._stat(follow_symlinks=follow_symlinks)
except (OSError, ValueError):
return False
return S_ISREG(st.st_mode)
def is_symlink(self):
"""Whether this path is a symbolic link."""
if self._lstat_result is _STAT_RESULT_ERROR:
return False
try:
st = self._stat(follow_symlinks=False)
except (OSError, ValueError):
return False
return S_ISLNK(st.st_mode)
class _DirEntryInfo(_Info):
"""Implementation of pathlib.types.PathInfo that provides status
information by querying a wrapped os.DirEntry object. Don't try to
construct it yourself."""
__slots__ = ('_entry',)
def __init__(self, entry):
super().__init__(entry.path)
self._entry = entry
def _stat(self, *, follow_symlinks=True):
"""Return the status as an os.stat_result."""
return self._entry.stat(follow_symlinks=follow_symlinks)
def exists(self, *, follow_symlinks=True):
"""Whether this path exists."""
if not follow_symlinks:
return True
try:
self._stat(follow_symlinks=follow_symlinks)
except OSError:
return False
return True
def is_dir(self, *, follow_symlinks=True):
"""Whether this path is a directory."""
try:
return self._entry.is_dir(follow_symlinks=follow_symlinks)
except OSError:
return False
def is_file(self, *, follow_symlinks=True):
"""Whether this path is a regular file."""
try:
return self._entry.is_file(follow_symlinks=follow_symlinks)
except OSError:
return False
def is_symlink(self):
"""Whether this path is a symbolic link."""
try:
return self._entry.is_symlink()
except OSError:
return False
def _copy_info(info, target, follow_symlinks=True): def _copy_info(info, target, follow_symlinks=True):
"""Copy metadata from the given PathInfo to the given local path.""" """Copy metadata from the given PathInfo to the given local path."""
copy_times_ns = ( copy_times_ns = (
@ -877,7 +841,7 @@ def info(self):
try: try:
return self._info return self._info
except AttributeError: except AttributeError:
self._info = _StatResultInfo(str(self)) self._info = _Info(str(self))
return self._info return self._info
def stat(self, *, follow_symlinks=True): def stat(self, *, follow_symlinks=True):
@ -1057,7 +1021,7 @@ def _filter_trailing_slash(self, paths):
def _from_dir_entry(self, dir_entry, path_str): def _from_dir_entry(self, dir_entry, path_str):
path = self.with_segments(path_str) path = self.with_segments(path_str)
path._str = path_str path._str = path_str
path._info = _DirEntryInfo(dir_entry) path._info = _Info(dir_entry.path, dir_entry)
return path return path
def iterdir(self): def iterdir(self):