summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2024-12-29 21:42:07 (GMT)
committerGitHub <noreply@github.com>2024-12-29 21:42:07 (GMT)
commitc78729f2df7c0e220f1080edb2a0d0cdd49e22df (patch)
tree8bbe6b09b6c4171463f46d03bbfb3e94fb29835d /Lib
parent7e819ce0f32068de7914cd1ba3b4b95e91ea9873 (diff)
downloadcpython-c78729f2df7c0e220f1080edb2a0d0cdd49e22df.zip
cpython-c78729f2df7c0e220f1080edb2a0d0cdd49e22df.tar.gz
cpython-c78729f2df7c0e220f1080edb2a0d0cdd49e22df.tar.bz2
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.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/pathlib/_abc.py31
-rw-r--r--Lib/pathlib/_local.py12
-rw-r--r--Lib/test/test_pathlib/test_pathlib.py25
-rw-r--r--Lib/test/test_pathlib/test_pathlib_abc.py77
4 files changed, 62 insertions, 83 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index 6acc29e..4d5f798 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -16,7 +16,6 @@ import operator
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 @@ class PathBase(PurePathBase):
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 915402e..4484d95 100644
--- a/Lib/pathlib/_local.py
+++ b/Lib/pathlib/_local.py
@@ -7,7 +7,7 @@ import sys
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 @@ class Path(PathBase, PurePath):
"""
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 @@ class Path(PathBase, PurePath):
"""
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 fac8cbd..0efa827 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -1835,6 +1835,31 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
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 787fb0f..81d9d34 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -2,7 +2,6 @@ import collections
import io
import os
import errno
-import stat
import unittest
from pathlib._abc import PurePathBase, PathBase
@@ -1294,11 +1293,6 @@ class DummyPathIO(io.BytesIO):
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 @@ class DummyPath(PathBase):
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 @@ class DummyPathTest(DummyPurePathTest):
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 @@ class DummyPathTest(DummyPurePathTest):
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')