https://github.com/python/cpython/commit/c78729f2df7c0e220f1080edb2a0d0cdd49e22df
commit: c78729f2df7c0e220f1080edb2a0d0cdd49e22df
branch: main
author: Barney Gale <[email protected]>
committer: barneygale <[email protected]>
date: 2024-12-29T21:42:07Z
summary:

GH-127381: pathlib ABCs: remove `PathBase.stat()` (#128334)

Remove the `PathBase.stat()` method. Its use of the `os.stat_result` API,
with its 10 mandatory fields and low-level types, makes it an awkward fit
for virtual filesystems.

We'll look to add a `PathBase.info` attribute later - see GH-125413.

files:
M Lib/pathlib/_abc.py
M Lib/pathlib/_local.py
M Lib/test/test_pathlib/test_pathlib.py
M Lib/test/test_pathlib/test_pathlib_abc.py

diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index 6acc29ebab2bc5..4d5f7980fd30d2 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -16,7 +16,6 @@
 import posixpath
 from errno import EINVAL
 from glob import _GlobberBase, _no_recurse_symlinks
-from stat import S_ISDIR, S_ISLNK, S_ISREG
 from pathlib._os import copyfileobj
 
 
@@ -450,15 +449,6 @@ class PathBase(PurePathBase):
     """
     __slots__ = ()
 
-    def stat(self, *, follow_symlinks=True):
-        """
-        Return the result of the stat() system call on this path, like
-        os.stat() does.
-        """
-        raise NotImplementedError
-
-    # Convenience functions for querying the stat results
-
     def exists(self, *, follow_symlinks=True):
         """
         Whether this path exists.
@@ -466,39 +456,26 @@ def exists(self, *, follow_symlinks=True):
         This method normally follows symlinks; to check whether a symlink 
exists,
         add the argument follow_symlinks=False.
         """
-        try:
-            self.stat(follow_symlinks=follow_symlinks)
-        except (OSError, ValueError):
-            return False
-        return True
+        raise NotImplementedError
 
     def is_dir(self, *, follow_symlinks=True):
         """
         Whether this path is a directory.
         """
-        try:
-            return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
-        except (OSError, ValueError):
-            return False
+        raise NotImplementedError
 
     def is_file(self, *, follow_symlinks=True):
         """
         Whether this path is a regular file (also True for symlinks pointing
         to regular files).
         """
-        try:
-            return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
-        except (OSError, ValueError):
-            return False
+        raise NotImplementedError
 
     def is_symlink(self):
         """
         Whether this path is a symbolic link.
         """
-        try:
-            return S_ISLNK(self.stat(follow_symlinks=False).st_mode)
-        except (OSError, ValueError):
-            return False
+        raise NotImplementedError
 
     def open(self, mode='r', buffering=-1, encoding=None,
              errors=None, newline=None):
diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py
index 915402e6c65b29..4484d9553c164f 100644
--- a/Lib/pathlib/_local.py
+++ b/Lib/pathlib/_local.py
@@ -7,7 +7,7 @@
 from errno import *
 from glob import _StringGlobber, _no_recurse_symlinks
 from itertools import chain
-from stat import S_IMODE, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+from stat import S_IMODE, S_ISDIR, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, 
S_ISFIFO
 from _collections_abc import Sequence
 
 try:
@@ -725,7 +725,10 @@ def is_dir(self, *, follow_symlinks=True):
         """
         if follow_symlinks:
             return os.path.isdir(self)
-        return PathBase.is_dir(self, follow_symlinks=follow_symlinks)
+        try:
+            return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
+        except (OSError, ValueError):
+            return False
 
     def is_file(self, *, follow_symlinks=True):
         """
@@ -734,7 +737,10 @@ def is_file(self, *, follow_symlinks=True):
         """
         if follow_symlinks:
             return os.path.isfile(self)
-        return PathBase.is_file(self, follow_symlinks=follow_symlinks)
+        try:
+            return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
+        except (OSError, ValueError):
+            return False
 
     def is_mount(self):
         """
diff --git a/Lib/test/test_pathlib/test_pathlib.py 
b/Lib/test/test_pathlib/test_pathlib.py
index fac8cbdf65a122..0efa8270210aef 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -1835,6 +1835,31 @@ def test_symlink_to_unsupported(self):
         with self.assertRaises(pathlib.UnsupportedOperation):
             q.symlink_to(p)
 
+    def test_stat(self):
+        statA = self.cls(self.base).joinpath('fileA').stat()
+        statB = self.cls(self.base).joinpath('dirB', 'fileB').stat()
+        statC = self.cls(self.base).joinpath('dirC').stat()
+        # st_mode: files are the same, directory differs.
+        self.assertIsInstance(statA.st_mode, int)
+        self.assertEqual(statA.st_mode, statB.st_mode)
+        self.assertNotEqual(statA.st_mode, statC.st_mode)
+        self.assertNotEqual(statB.st_mode, statC.st_mode)
+        # st_ino: all different,
+        self.assertIsInstance(statA.st_ino, int)
+        self.assertNotEqual(statA.st_ino, statB.st_ino)
+        self.assertNotEqual(statA.st_ino, statC.st_ino)
+        self.assertNotEqual(statB.st_ino, statC.st_ino)
+        # st_dev: all the same.
+        self.assertIsInstance(statA.st_dev, int)
+        self.assertEqual(statA.st_dev, statB.st_dev)
+        self.assertEqual(statA.st_dev, statC.st_dev)
+        # other attributes not used by pathlib.
+
+    def test_stat_no_follow_symlinks_nosymlink(self):
+        p = self.cls(self.base) / 'fileA'
+        st = p.stat()
+        self.assertEqual(st, p.stat(follow_symlinks=False))
+
     @needs_symlinks
     def test_stat_no_follow_symlinks(self):
         p = self.cls(self.base) / 'linkA'
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py 
b/Lib/test/test_pathlib/test_pathlib_abc.py
index 787fb0f82257e6..81d9d34506f04b 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -2,7 +2,6 @@
 import io
 import os
 import errno
-import stat
 import unittest
 
 from pathlib._abc import PurePathBase, PathBase
@@ -1294,11 +1293,6 @@ def close(self):
         super().close()
 
 
-DummyPathStatResult = collections.namedtuple(
-    'DummyPathStatResult',
-    'st_mode st_ino st_dev st_nlink st_uid st_gid st_size st_atime st_mtime 
st_ctime')
-
-
 class DummyPath(PathBase):
     """
     Simple implementation of PathBase that keeps files and directories in
@@ -1331,15 +1325,17 @@ def __repr__(self):
     def with_segments(self, *pathsegments):
         return type(self)(*pathsegments)
 
-    def stat(self, *, follow_symlinks=True):
-        path = str(self).rstrip('/')
-        if path in self._files:
-            st_mode = stat.S_IFREG
-        elif path in self._directories:
-            st_mode = stat.S_IFDIR
-        else:
-            raise FileNotFoundError(errno.ENOENT, "Not found", str(self))
-        return DummyPathStatResult(st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 
0, 0)
+    def exists(self, *, follow_symlinks=True):
+        return self.is_dir() or self.is_file()
+
+    def is_dir(self, *, follow_symlinks=True):
+        return str(self).rstrip('/') in self._directories
+
+    def is_file(self, *, follow_symlinks=True):
+        return str(self) in self._files
+
+    def is_symlink(self):
+        return False
 
     def open(self, mode='r', buffering=-1, encoding=None,
              errors=None, newline=None):
@@ -1958,31 +1954,6 @@ def test_rglob_windows(self):
         self.assertEqual(set(p.rglob("FILEd")), { P(self.base, 
"dirC/dirD/fileD") })
         self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") })
 
-    def test_stat(self):
-        statA = self.cls(self.base).joinpath('fileA').stat()
-        statB = self.cls(self.base).joinpath('dirB', 'fileB').stat()
-        statC = self.cls(self.base).joinpath('dirC').stat()
-        # st_mode: files are the same, directory differs.
-        self.assertIsInstance(statA.st_mode, int)
-        self.assertEqual(statA.st_mode, statB.st_mode)
-        self.assertNotEqual(statA.st_mode, statC.st_mode)
-        self.assertNotEqual(statB.st_mode, statC.st_mode)
-        # st_ino: all different,
-        self.assertIsInstance(statA.st_ino, int)
-        self.assertNotEqual(statA.st_ino, statB.st_ino)
-        self.assertNotEqual(statA.st_ino, statC.st_ino)
-        self.assertNotEqual(statB.st_ino, statC.st_ino)
-        # st_dev: all the same.
-        self.assertIsInstance(statA.st_dev, int)
-        self.assertEqual(statA.st_dev, statB.st_dev)
-        self.assertEqual(statA.st_dev, statC.st_dev)
-        # other attributes not used by pathlib.
-
-    def test_stat_no_follow_symlinks_nosymlink(self):
-        p = self.cls(self.base) / 'fileA'
-        st = p.stat()
-        self.assertEqual(st, p.stat(follow_symlinks=False))
-
     def test_is_dir(self):
         P = self.cls(self.base)
         self.assertTrue((P / 'dirA').is_dir())
@@ -2054,26 +2025,26 @@ def test_is_symlink(self):
     def test_delete_file(self):
         p = self.cls(self.base) / 'fileA'
         p._delete()
-        self.assertFileNotFound(p.stat)
+        self.assertFalse(p.exists())
         self.assertFileNotFound(p._delete)
 
     def test_delete_dir(self):
         base = self.cls(self.base)
         base.joinpath('dirA')._delete()
-        self.assertRaises(FileNotFoundError, base.joinpath('dirA').stat)
-        self.assertRaises(FileNotFoundError, base.joinpath('dirA', 
'linkC').stat,
-                          follow_symlinks=False)
+        self.assertFalse(base.joinpath('dirA').exists())
+        self.assertFalse(base.joinpath('dirA', 'linkC').exists(
+            follow_symlinks=False))
         base.joinpath('dirB')._delete()
-        self.assertRaises(FileNotFoundError, base.joinpath('dirB').stat)
-        self.assertRaises(FileNotFoundError, base.joinpath('dirB', 
'fileB').stat)
-        self.assertRaises(FileNotFoundError, base.joinpath('dirB', 
'linkD').stat,
-                          follow_symlinks=False)
+        self.assertFalse(base.joinpath('dirB').exists())
+        self.assertFalse(base.joinpath('dirB', 'fileB').exists())
+        self.assertFalse(base.joinpath('dirB', 'linkD').exists(
+            follow_symlinks=False))
         base.joinpath('dirC')._delete()
-        self.assertRaises(FileNotFoundError, base.joinpath('dirC').stat)
-        self.assertRaises(FileNotFoundError, base.joinpath('dirC', 
'dirD').stat)
-        self.assertRaises(FileNotFoundError, base.joinpath('dirC', 'dirD', 
'fileD').stat)
-        self.assertRaises(FileNotFoundError, base.joinpath('dirC', 
'fileC').stat)
-        self.assertRaises(FileNotFoundError, base.joinpath('dirC', 
'novel.txt').stat)
+        self.assertFalse(base.joinpath('dirC').exists())
+        self.assertFalse(base.joinpath('dirC', 'dirD').exists())
+        self.assertFalse(base.joinpath('dirC', 'dirD', 'fileD').exists())
+        self.assertFalse(base.joinpath('dirC', 'fileC').exists())
+        self.assertFalse(base.joinpath('dirC', 'novel.txt').exists())
 
     def test_delete_missing(self):
         tmp = self.cls(self.base, 'delete')

_______________________________________________
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