https://github.com/python/cpython/commit/1bfe86caaaaca3ec16351d69fa778f1212f1dd84
commit: 1bfe86caaaaca3ec16351d69fa778f1212f1dd84
branch: main
author: Barney Gale <[email protected]>
committer: barneygale <[email protected]>
date: 2025-10-18T02:13:25+01:00
summary:

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`.

files:
M Lib/pathlib/__init__.py

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):

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to