diff options
author | Barney Gale <barney.gale@gmail.com> | 2024-12-29 21:42:07 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-29 21:42:07 (GMT) |
commit | c78729f2df7c0e220f1080edb2a0d0cdd49e22df (patch) | |
tree | 8bbe6b09b6c4171463f46d03bbfb3e94fb29835d /Lib | |
parent | 7e819ce0f32068de7914cd1ba3b4b95e91ea9873 (diff) | |
download | cpython-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.py | 31 | ||||
-rw-r--r-- | Lib/pathlib/_local.py | 12 | ||||
-rw-r--r-- | Lib/test/test_pathlib/test_pathlib.py | 25 | ||||
-rw-r--r-- | Lib/test/test_pathlib/test_pathlib_abc.py | 77 |
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') |