From 68c9f6f99db5422ef2c45184df726367e223004f Mon Sep 17 00:00:00 2001 From: barneygale Date: Wed, 15 Oct 2025 14:23:02 +0100 Subject: [PATCH] GH-139174: Prepare `pathlib.Path.info` for new methods (part 2) 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`. --- Lib/pathlib/__init__.py | 156 ++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 96 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 51359cec8b0f9e..44f967eb12dd4f 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -611,72 +611,29 @@ class PureWindowsPath(PurePath): __slots__ = () -class _Info: - __slots__ = ('_path',) - - def __init__(self, path): - self._path = path - - def __repr__(self): - path_type = "WindowsPath" if os.name == "nt" else "PosixPath" - return f"<{path_type}.info>" - - def _stat(self, *, follow_symlinks=True): - """Return the status as an os.stat_result.""" - raise NotImplementedError - - def _posix_permissions(self, *, follow_symlinks=True): - """Return the POSIX file permissions.""" - return S_IMODE(self._stat(follow_symlinks=follow_symlinks).st_mode) - - def _file_id(self, *, follow_symlinks=True): - """Returns the identifier of the file.""" - st = self._stat(follow_symlinks=follow_symlinks) - return st.st_dev, st.st_ino - - def _access_time_ns(self, *, follow_symlinks=True): - """Return the access time in nanoseconds.""" - return self._stat(follow_symlinks=follow_symlinks).st_atime_ns - - def _mod_time_ns(self, *, follow_symlinks=True): - """Return the modify time in nanoseconds.""" - return self._stat(follow_symlinks=follow_symlinks).st_mtime_ns - - if hasattr(os.stat_result, 'st_flags'): - def _bsd_flags(self, *, follow_symlinks=True): - """Return the flags.""" - return self._stat(follow_symlinks=follow_symlinks).st_flags - - if hasattr(os, 'listxattr'): - def _xattrs(self, *, follow_symlinks=True): - """Return the xattrs as a list of (attr, value) pairs, or an empty - list if extended attributes aren't supported.""" - try: - return [ - (attr, os.getxattr(self._path, attr, follow_symlinks=follow_symlinks)) - for attr in os.listxattr(self._path, follow_symlinks=follow_symlinks)] - except OSError as err: - if err.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): - raise - return [] - - _STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed. -class _StatResultInfo(_Info): +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__ = ('_stat_result', '_lstat_result') + __slots__ = ('_path', '_entry', '_stat_result', '_lstat_result') - def __init__(self, path): - super().__init__(path) + def __init__(self, path, entry=None): + self._path = path + self._entry = entry self._stat_result = None self._lstat_result = None + def __repr__(self): + path_type = "WindowsPath" if os.name == "nt" else "PosixPath" + return f"<{path_type}.info>" + def _stat(self, *, follow_symlinks=True): """Return the status as an os.stat_result.""" + if self._entry: + return self._entry.stat(follow_symlinks=follow_symlinks) if follow_symlinks: if not self._stat_result: try: @@ -696,6 +653,9 @@ def _stat(self, *, follow_symlinks=True): 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 @@ -710,6 +670,11 @@ def exists(self, *, follow_symlinks=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 @@ -724,6 +689,11 @@ def is_dir(self, *, follow_symlinks=True): 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 @@ -738,6 +708,11 @@ def is_file(self, *, follow_symlinks=True): 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: @@ -746,51 +721,40 @@ def is_symlink(self): return False return S_ISLNK(st.st_mode) + def _posix_permissions(self, *, follow_symlinks=True): + """Return the POSIX file permissions.""" + return S_IMODE(self._stat(follow_symlinks=follow_symlinks).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 _file_id(self, *, follow_symlinks=True): + """Returns the identifier of the file.""" + st = self._stat(follow_symlinks=follow_symlinks) + return st.st_dev, st.st_ino - 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 _access_time_ns(self, *, follow_symlinks=True): + """Return the access time in nanoseconds.""" + return self._stat(follow_symlinks=follow_symlinks).st_atime_ns - 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 _mod_time_ns(self, *, follow_symlinks=True): + """Return the modify time in nanoseconds.""" + return self._stat(follow_symlinks=follow_symlinks).st_mtime_ns - 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 + if hasattr(os.stat_result, 'st_flags'): + def _bsd_flags(self, *, follow_symlinks=True): + """Return the flags.""" + return self._stat(follow_symlinks=follow_symlinks).st_flags - def is_symlink(self): - """Whether this path is a symbolic link.""" - try: - return self._entry.is_symlink() - except OSError: - return False + if hasattr(os, 'listxattr'): + def _xattrs(self, *, follow_symlinks=True): + """Return the xattrs as a list of (attr, value) pairs, or an empty + list if extended attributes aren't supported.""" + try: + return [ + (attr, os.getxattr(self._path, attr, follow_symlinks=follow_symlinks)) + for attr in os.listxattr(self._path, follow_symlinks=follow_symlinks)] + except OSError as err: + if err.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): + raise + return [] def _copy_info(info, target, follow_symlinks=True): @@ -877,7 +841,7 @@ def info(self): try: return self._info except AttributeError: - self._info = _StatResultInfo(str(self)) + self._info = _Info(str(self)) return self._info 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): path = self.with_segments(path_str) path._str = path_str - path._info = _DirEntryInfo(dir_entry) + path._info = _Info(dir_entry.path, dir_entry) return path def iterdir(self):