summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2024-12-11 00:09:55 (GMT)
committerGitHub <noreply@github.com>2024-12-11 00:09:55 (GMT)
commit12b4f1a5a175d4dcec27631fce2883038f0917ae (patch)
tree3fc1e27178bb57eb9a8f24d0f9713828771d28b4
parent51216857ca8283f5b41c8cf9874238da56da4968 (diff)
downloadcpython-12b4f1a5a175d4dcec27631fce2883038f0917ae.zip
cpython-12b4f1a5a175d4dcec27631fce2883038f0917ae.tar.gz
cpython-12b4f1a5a175d4dcec27631fce2883038f0917ae.tar.bz2
GH-127381: pathlib ABCs: remove `PathBase.samefile()` and rarer `is_*()` (#127709)
Remove `PathBase.samefile()`, which is fairly specific to the local FS, and relies on `stat()`, which we're aiming to remove from `PathBase`. Also remove `PathBase.is_mount()`, `is_junction()`, `is_block_device()`, `is_char_device()`, `is_fifo()` and `is_socket()`. These rely on POSIX file type numbers that we're aiming to remove from the `PathBase` API.
-rw-r--r--Lib/pathlib/_abc.py89
-rw-r--r--Lib/pathlib/_local.py65
-rw-r--r--Lib/test/test_pathlib/test_pathlib.py77
-rw-r--r--Lib/test/test_pathlib/test_pathlib_abc.py81
4 files changed, 141 insertions, 171 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index f68685f..02c6e05 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -16,7 +16,7 @@ import operator
import posixpath
from errno import EINVAL
from glob import _GlobberBase, _no_recurse_symlinks
-from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
+from stat import S_ISDIR, S_ISLNK, S_ISREG
from pathlib._os import copyfileobj
@@ -408,26 +408,6 @@ class PathBase(PurePathBase):
except (OSError, ValueError):
return False
- def is_mount(self):
- """
- Check if this path is a mount point
- """
- # Need to exist and be a dir
- if not self.exists() or not self.is_dir():
- return False
-
- try:
- parent_dev = self.parent.stat().st_dev
- except OSError:
- return False
-
- dev = self.stat().st_dev
- if dev != parent_dev:
- return True
- ino = self.stat().st_ino
- parent_ino = self.parent.stat().st_ino
- return ino == parent_ino
-
def is_symlink(self):
"""
Whether this path is a symbolic link.
@@ -437,76 +417,11 @@ class PathBase(PurePathBase):
except (OSError, ValueError):
return False
- def is_junction(self):
- """
- Whether this path is a junction.
- """
- # Junctions are a Windows-only feature, not present in POSIX nor the
- # majority of virtual filesystems. There is no cross-platform idiom
- # to check for junctions (using stat().st_mode).
- return False
-
- def is_block_device(self):
- """
- Whether this path is a block device.
- """
- try:
- return S_ISBLK(self.stat().st_mode)
- except (OSError, ValueError):
- return False
-
- def is_char_device(self):
- """
- Whether this path is a character device.
- """
- try:
- return S_ISCHR(self.stat().st_mode)
- except (OSError, ValueError):
- return False
-
- def is_fifo(self):
- """
- Whether this path is a FIFO.
- """
- try:
- return S_ISFIFO(self.stat().st_mode)
- except (OSError, ValueError):
- return False
-
- def is_socket(self):
- """
- Whether this path is a socket.
- """
- try:
- return S_ISSOCK(self.stat().st_mode)
- except (OSError, ValueError):
- return False
-
- def samefile(self, other_path):
- """Return whether other_path is the same or not as this file
- (as returned by os.path.samefile()).
- """
- st = self.stat()
- try:
- other_st = other_path.stat()
- except AttributeError:
- other_st = self.with_segments(other_path).stat()
- return (st.st_ino == other_st.st_ino and
- st.st_dev == other_st.st_dev)
-
def _ensure_different_file(self, other_path):
"""
Raise OSError(EINVAL) if both paths refer to the same file.
"""
- try:
- if not self.samefile(other_path):
- return
- except (OSError, ValueError):
- return
- err = OSError(EINVAL, "Source and target are the same file")
- err.filename = str(self)
- err.filename2 = str(other_path)
- raise err
+ pass
def _ensure_distinct_path(self, other_path):
"""
diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py
index f87069c..85437ec 100644
--- a/Lib/pathlib/_local.py
+++ b/Lib/pathlib/_local.py
@@ -4,9 +4,10 @@ import operator
import os
import posixpath
import sys
-from errno import EXDEV
+from errno import EINVAL, EXDEV
from glob import _StringGlobber
from itertools import chain
+from stat import S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
from _collections_abc import Sequence
try:
@@ -596,6 +597,68 @@ class Path(PathBase, PurePath):
"""
return os.path.isjunction(self)
+ def is_block_device(self):
+ """
+ Whether this path is a block device.
+ """
+ try:
+ return S_ISBLK(self.stat().st_mode)
+ except (OSError, ValueError):
+ return False
+
+ def is_char_device(self):
+ """
+ Whether this path is a character device.
+ """
+ try:
+ return S_ISCHR(self.stat().st_mode)
+ except (OSError, ValueError):
+ return False
+
+ def is_fifo(self):
+ """
+ Whether this path is a FIFO.
+ """
+ try:
+ return S_ISFIFO(self.stat().st_mode)
+ except (OSError, ValueError):
+ return False
+
+ def is_socket(self):
+ """
+ Whether this path is a socket.
+ """
+ try:
+ return S_ISSOCK(self.stat().st_mode)
+ except (OSError, ValueError):
+ return False
+
+ def samefile(self, other_path):
+ """Return whether other_path is the same or not as this file
+ (as returned by os.path.samefile()).
+ """
+ st = self.stat()
+ try:
+ other_st = other_path.stat()
+ except AttributeError:
+ other_st = self.with_segments(other_path).stat()
+ return (st.st_ino == other_st.st_ino and
+ st.st_dev == other_st.st_dev)
+
+ def _ensure_different_file(self, other_path):
+ """
+ Raise OSError(EINVAL) if both paths refer to the same file.
+ """
+ try:
+ if not self.samefile(other_path):
+ return
+ except (OSError, ValueError):
+ return
+ err = OSError(EINVAL, "Source and target are the same file")
+ err.filename = str(self)
+ err.filename2 = str(other_path)
+ raise err
+
def open(self, mode='r', buffering=-1, encoding=None,
errors=None, newline=None):
"""
diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py
index ce0f474..b57ef42 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -1786,13 +1786,31 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
st = p.stat()
self.assertEqual(st, p.lstat())
- def test_is_junction(self):
+ def test_is_junction_false(self):
+ P = self.cls(self.base)
+ self.assertFalse((P / 'fileA').is_junction())
+ self.assertFalse((P / 'dirA').is_junction())
+ self.assertFalse((P / 'non-existing').is_junction())
+ self.assertFalse((P / 'fileA' / 'bah').is_junction())
+ self.assertFalse((P / 'fileA\udfff').is_junction())
+ self.assertFalse((P / 'fileA\x00').is_junction())
+
+ def test_is_junction_true(self):
P = self.cls(self.base)
with mock.patch.object(P.parser, 'isjunction'):
self.assertEqual(P.is_junction(), P.parser.isjunction.return_value)
P.parser.isjunction.assert_called_once_with(P)
+ def test_is_fifo_false(self):
+ P = self.cls(self.base)
+ self.assertFalse((P / 'fileA').is_fifo())
+ self.assertFalse((P / 'dirA').is_fifo())
+ self.assertFalse((P / 'non-existing').is_fifo())
+ self.assertFalse((P / 'fileA' / 'bah').is_fifo())
+ self.assertIs((P / 'fileA\udfff').is_fifo(), False)
+ self.assertIs((P / 'fileA\x00').is_fifo(), False)
+
@unittest.skipUnless(hasattr(os, "mkfifo"), "os.mkfifo() required")
@unittest.skipIf(sys.platform == "vxworks",
"fifo requires special path on VxWorks")
@@ -1808,6 +1826,15 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertIs(self.cls(self.base, 'myfifo\udfff').is_fifo(), False)
self.assertIs(self.cls(self.base, 'myfifo\x00').is_fifo(), False)
+ def test_is_socket_false(self):
+ P = self.cls(self.base)
+ self.assertFalse((P / 'fileA').is_socket())
+ self.assertFalse((P / 'dirA').is_socket())
+ self.assertFalse((P / 'non-existing').is_socket())
+ self.assertFalse((P / 'fileA' / 'bah').is_socket())
+ self.assertIs((P / 'fileA\udfff').is_socket(), False)
+ self.assertIs((P / 'fileA\x00').is_socket(), False)
+
@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required")
@unittest.skipIf(
is_emscripten, "Unix sockets are not implemented on Emscripten."
@@ -1831,6 +1858,24 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False)
self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False)
+ def test_is_block_device_false(self):
+ P = self.cls(self.base)
+ self.assertFalse((P / 'fileA').is_block_device())
+ self.assertFalse((P / 'dirA').is_block_device())
+ self.assertFalse((P / 'non-existing').is_block_device())
+ self.assertFalse((P / 'fileA' / 'bah').is_block_device())
+ self.assertIs((P / 'fileA\udfff').is_block_device(), False)
+ self.assertIs((P / 'fileA\x00').is_block_device(), False)
+
+ def test_is_char_device_false(self):
+ P = self.cls(self.base)
+ self.assertFalse((P / 'fileA').is_char_device())
+ self.assertFalse((P / 'dirA').is_char_device())
+ self.assertFalse((P / 'non-existing').is_char_device())
+ self.assertFalse((P / 'fileA' / 'bah').is_char_device())
+ self.assertIs((P / 'fileA\udfff').is_char_device(), False)
+ self.assertIs((P / 'fileA\x00').is_char_device(), False)
+
def test_is_char_device_true(self):
# os.devnull should generally be a char device.
P = self.cls(os.devnull)
@@ -1842,7 +1887,14 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertIs(self.cls(f'{os.devnull}\udfff').is_char_device(), False)
self.assertIs(self.cls(f'{os.devnull}\x00').is_char_device(), False)
- def test_is_mount_root(self):
+ def test_is_mount(self):
+ P = self.cls(self.base)
+ self.assertFalse((P / 'fileA').is_mount())
+ self.assertFalse((P / 'dirA').is_mount())
+ self.assertFalse((P / 'non-existing').is_mount())
+ self.assertFalse((P / 'fileA' / 'bah').is_mount())
+ if self.can_symlink:
+ self.assertFalse((P / 'linkA').is_mount())
if os.name == 'nt':
R = self.cls('c:\\')
else:
@@ -1850,6 +1902,27 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertTrue(R.is_mount())
self.assertFalse((R / '\udfff').is_mount())
+ def test_samefile(self):
+ parser = self.parser
+ fileA_path = parser.join(self.base, 'fileA')
+ fileB_path = parser.join(self.base, 'dirB', 'fileB')
+ p = self.cls(fileA_path)
+ pp = self.cls(fileA_path)
+ q = self.cls(fileB_path)
+ self.assertTrue(p.samefile(fileA_path))
+ self.assertTrue(p.samefile(pp))
+ self.assertFalse(p.samefile(fileB_path))
+ self.assertFalse(p.samefile(q))
+ # Test the non-existent file case
+ non_existent = parser.join(self.base, 'foo')
+ r = self.cls(non_existent)
+ self.assertRaises(FileNotFoundError, p.samefile, r)
+ self.assertRaises(FileNotFoundError, p.samefile, non_existent)
+ self.assertRaises(FileNotFoundError, r.samefile, p)
+ self.assertRaises(FileNotFoundError, r.samefile, non_existent)
+ self.assertRaises(FileNotFoundError, r.samefile, r)
+ self.assertRaises(FileNotFoundError, r.samefile, non_existent)
+
def test_passing_kwargs_errors(self):
with self.assertRaises(TypeError):
self.cls(foo="bar")
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py
index dd9425c..f4c364c 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -1300,15 +1300,9 @@ class PathBaseTest(PurePathBaseTest):
e = UnsupportedOperation
self.assertRaises(e, p.stat)
self.assertRaises(e, p.exists)
- self.assertRaises(e, p.samefile, 'foo')
self.assertRaises(e, p.is_dir)
self.assertRaises(e, p.is_file)
- self.assertRaises(e, p.is_mount)
self.assertRaises(e, p.is_symlink)
- self.assertRaises(e, p.is_block_device)
- self.assertRaises(e, p.is_char_device)
- self.assertRaises(e, p.is_fifo)
- self.assertRaises(e, p.is_socket)
self.assertRaises(e, p.open)
self.assertRaises(e, p.read_bytes)
self.assertRaises(e, p.read_text)
@@ -1535,27 +1529,6 @@ class DummyPathTest(DummyPurePathTest):
normcase = self.parser.normcase
self.assertEqual(normcase(path_a), normcase(path_b))
- def test_samefile(self):
- parser = self.parser
- fileA_path = parser.join(self.base, 'fileA')
- fileB_path = parser.join(self.base, 'dirB', 'fileB')
- p = self.cls(fileA_path)
- pp = self.cls(fileA_path)
- q = self.cls(fileB_path)
- self.assertTrue(p.samefile(fileA_path))
- self.assertTrue(p.samefile(pp))
- self.assertFalse(p.samefile(fileB_path))
- self.assertFalse(p.samefile(q))
- # Test the non-existent file case
- non_existent = parser.join(self.base, 'foo')
- r = self.cls(non_existent)
- self.assertRaises(FileNotFoundError, p.samefile, r)
- self.assertRaises(FileNotFoundError, p.samefile, non_existent)
- self.assertRaises(FileNotFoundError, r.samefile, p)
- self.assertRaises(FileNotFoundError, r.samefile, non_existent)
- self.assertRaises(FileNotFoundError, r.samefile, r)
- self.assertRaises(FileNotFoundError, r.samefile, non_existent)
-
def test_exists(self):
P = self.cls
p = P(self.base)
@@ -2115,15 +2088,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False))
self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False))
- def test_is_mount(self):
- P = self.cls(self.base)
- self.assertFalse((P / 'fileA').is_mount())
- self.assertFalse((P / 'dirA').is_mount())
- self.assertFalse((P / 'non-existing').is_mount())
- self.assertFalse((P / 'fileA' / 'bah').is_mount())
- if self.can_symlink:
- self.assertFalse((P / 'linkA').is_mount())
-
def test_is_symlink(self):
P = self.cls(self.base)
self.assertFalse((P / 'fileA').is_symlink())
@@ -2140,51 +2104,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertIs((P / 'linkA\udfff').is_file(), False)
self.assertIs((P / 'linkA\x00').is_file(), False)
- def test_is_junction_false(self):
- P = self.cls(self.base)
- self.assertFalse((P / 'fileA').is_junction())
- self.assertFalse((P / 'dirA').is_junction())
- self.assertFalse((P / 'non-existing').is_junction())
- self.assertFalse((P / 'fileA' / 'bah').is_junction())
- self.assertFalse((P / 'fileA\udfff').is_junction())
- self.assertFalse((P / 'fileA\x00').is_junction())
-
- def test_is_fifo_false(self):
- P = self.cls(self.base)
- self.assertFalse((P / 'fileA').is_fifo())
- self.assertFalse((P / 'dirA').is_fifo())
- self.assertFalse((P / 'non-existing').is_fifo())
- self.assertFalse((P / 'fileA' / 'bah').is_fifo())
- self.assertIs((P / 'fileA\udfff').is_fifo(), False)
- self.assertIs((P / 'fileA\x00').is_fifo(), False)
-
- def test_is_socket_false(self):
- P = self.cls(self.base)
- self.assertFalse((P / 'fileA').is_socket())
- self.assertFalse((P / 'dirA').is_socket())
- self.assertFalse((P / 'non-existing').is_socket())
- self.assertFalse((P / 'fileA' / 'bah').is_socket())
- self.assertIs((P / 'fileA\udfff').is_socket(), False)
- self.assertIs((P / 'fileA\x00').is_socket(), False)
-
- def test_is_block_device_false(self):
- P = self.cls(self.base)
- self.assertFalse((P / 'fileA').is_block_device())
- self.assertFalse((P / 'dirA').is_block_device())
- self.assertFalse((P / 'non-existing').is_block_device())
- self.assertFalse((P / 'fileA' / 'bah').is_block_device())
- self.assertIs((P / 'fileA\udfff').is_block_device(), False)
- self.assertIs((P / 'fileA\x00').is_block_device(), False)
-
- def test_is_char_device_false(self):
- P = self.cls(self.base)
- self.assertFalse((P / 'fileA').is_char_device())
- self.assertFalse((P / 'dirA').is_char_device())
- self.assertFalse((P / 'non-existing').is_char_device())
- self.assertFalse((P / 'fileA' / 'bah').is_char_device())
- self.assertIs((P / 'fileA\udfff').is_char_device(), False)
- self.assertIs((P / 'fileA\x00').is_char_device(), False)
-
def test_delete_file(self):
p = self.cls(self.base) / 'fileA'
p._delete()