Skip to content

Commit 12b4f1a

Browse files
authored
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.
1 parent 5121685 commit 12b4f1a

File tree

4 files changed

+141
-171
lines changed

4 files changed

+141
-171
lines changed

Lib/pathlib/_abc.py

+2-87
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import posixpath
1717
from errno import EINVAL
1818
from glob import _GlobberBase, _no_recurse_symlinks
19-
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
19+
from stat import S_ISDIR, S_ISLNK, S_ISREG
2020
from pathlib._os import copyfileobj
2121

2222

@@ -408,26 +408,6 @@ def is_file(self, *, follow_symlinks=True):
408408
except (OSError, ValueError):
409409
return False
410410

411-
def is_mount(self):
412-
"""
413-
Check if this path is a mount point
414-
"""
415-
# Need to exist and be a dir
416-
if not self.exists() or not self.is_dir():
417-
return False
418-
419-
try:
420-
parent_dev = self.parent.stat().st_dev
421-
except OSError:
422-
return False
423-
424-
dev = self.stat().st_dev
425-
if dev != parent_dev:
426-
return True
427-
ino = self.stat().st_ino
428-
parent_ino = self.parent.stat().st_ino
429-
return ino == parent_ino
430-
431411
def is_symlink(self):
432412
"""
433413
Whether this path is a symbolic link.
@@ -437,76 +417,11 @@ def is_symlink(self):
437417
except (OSError, ValueError):
438418
return False
439419

440-
def is_junction(self):
441-
"""
442-
Whether this path is a junction.
443-
"""
444-
# Junctions are a Windows-only feature, not present in POSIX nor the
445-
# majority of virtual filesystems. There is no cross-platform idiom
446-
# to check for junctions (using stat().st_mode).
447-
return False
448-
449-
def is_block_device(self):
450-
"""
451-
Whether this path is a block device.
452-
"""
453-
try:
454-
return S_ISBLK(self.stat().st_mode)
455-
except (OSError, ValueError):
456-
return False
457-
458-
def is_char_device(self):
459-
"""
460-
Whether this path is a character device.
461-
"""
462-
try:
463-
return S_ISCHR(self.stat().st_mode)
464-
except (OSError, ValueError):
465-
return False
466-
467-
def is_fifo(self):
468-
"""
469-
Whether this path is a FIFO.
470-
"""
471-
try:
472-
return S_ISFIFO(self.stat().st_mode)
473-
except (OSError, ValueError):
474-
return False
475-
476-
def is_socket(self):
477-
"""
478-
Whether this path is a socket.
479-
"""
480-
try:
481-
return S_ISSOCK(self.stat().st_mode)
482-
except (OSError, ValueError):
483-
return False
484-
485-
def samefile(self, other_path):
486-
"""Return whether other_path is the same or not as this file
487-
(as returned by os.path.samefile()).
488-
"""
489-
st = self.stat()
490-
try:
491-
other_st = other_path.stat()
492-
except AttributeError:
493-
other_st = self.with_segments(other_path).stat()
494-
return (st.st_ino == other_st.st_ino and
495-
st.st_dev == other_st.st_dev)
496-
497420
def _ensure_different_file(self, other_path):
498421
"""
499422
Raise OSError(EINVAL) if both paths refer to the same file.
500423
"""
501-
try:
502-
if not self.samefile(other_path):
503-
return
504-
except (OSError, ValueError):
505-
return
506-
err = OSError(EINVAL, "Source and target are the same file")
507-
err.filename = str(self)
508-
err.filename2 = str(other_path)
509-
raise err
424+
pass
510425

511426
def _ensure_distinct_path(self, other_path):
512427
"""

Lib/pathlib/_local.py

+64-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import os
55
import posixpath
66
import sys
7-
from errno import EXDEV
7+
from errno import EINVAL, EXDEV
88
from glob import _StringGlobber
99
from itertools import chain
10+
from stat import S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
1011
from _collections_abc import Sequence
1112

1213
try:
@@ -596,6 +597,68 @@ def is_junction(self):
596597
"""
597598
return os.path.isjunction(self)
598599

600+
def is_block_device(self):
601+
"""
602+
Whether this path is a block device.
603+
"""
604+
try:
605+
return S_ISBLK(self.stat().st_mode)
606+
except (OSError, ValueError):
607+
return False
608+
609+
def is_char_device(self):
610+
"""
611+
Whether this path is a character device.
612+
"""
613+
try:
614+
return S_ISCHR(self.stat().st_mode)
615+
except (OSError, ValueError):
616+
return False
617+
618+
def is_fifo(self):
619+
"""
620+
Whether this path is a FIFO.
621+
"""
622+
try:
623+
return S_ISFIFO(self.stat().st_mode)
624+
except (OSError, ValueError):
625+
return False
626+
627+
def is_socket(self):
628+
"""
629+
Whether this path is a socket.
630+
"""
631+
try:
632+
return S_ISSOCK(self.stat().st_mode)
633+
except (OSError, ValueError):
634+
return False
635+
636+
def samefile(self, other_path):
637+
"""Return whether other_path is the same or not as this file
638+
(as returned by os.path.samefile()).
639+
"""
640+
st = self.stat()
641+
try:
642+
other_st = other_path.stat()
643+
except AttributeError:
644+
other_st = self.with_segments(other_path).stat()
645+
return (st.st_ino == other_st.st_ino and
646+
st.st_dev == other_st.st_dev)
647+
648+
def _ensure_different_file(self, other_path):
649+
"""
650+
Raise OSError(EINVAL) if both paths refer to the same file.
651+
"""
652+
try:
653+
if not self.samefile(other_path):
654+
return
655+
except (OSError, ValueError):
656+
return
657+
err = OSError(EINVAL, "Source and target are the same file")
658+
err.filename = str(self)
659+
err.filename2 = str(other_path)
660+
raise err
661+
599662
def open(self, mode='r', buffering=-1, encoding=None,
600663
errors=None, newline=None):
601664
"""

Lib/test/test_pathlib/test_pathlib.py

+75-2
Original file line numberDiff line numberDiff line change
@@ -1786,13 +1786,31 @@ def test_lstat_nosymlink(self):
17861786
st = p.stat()
17871787
self.assertEqual(st, p.lstat())
17881788

1789-
def test_is_junction(self):
1789+
def test_is_junction_false(self):
1790+
P = self.cls(self.base)
1791+
self.assertFalse((P / 'fileA').is_junction())
1792+
self.assertFalse((P / 'dirA').is_junction())
1793+
self.assertFalse((P / 'non-existing').is_junction())
1794+
self.assertFalse((P / 'fileA' / 'bah').is_junction())
1795+
self.assertFalse((P / 'fileA\udfff').is_junction())
1796+
self.assertFalse((P / 'fileA\x00').is_junction())
1797+
1798+
def test_is_junction_true(self):
17901799
P = self.cls(self.base)
17911800

17921801
with mock.patch.object(P.parser, 'isjunction'):
17931802
self.assertEqual(P.is_junction(), P.parser.isjunction.return_value)
17941803
P.parser.isjunction.assert_called_once_with(P)
17951804

1805+
def test_is_fifo_false(self):
1806+
P = self.cls(self.base)
1807+
self.assertFalse((P / 'fileA').is_fifo())
1808+
self.assertFalse((P / 'dirA').is_fifo())
1809+
self.assertFalse((P / 'non-existing').is_fifo())
1810+
self.assertFalse((P / 'fileA' / 'bah').is_fifo())
1811+
self.assertIs((P / 'fileA\udfff').is_fifo(), False)
1812+
self.assertIs((P / 'fileA\x00').is_fifo(), False)
1813+
17961814
@unittest.skipUnless(hasattr(os, "mkfifo"), "os.mkfifo() required")
17971815
@unittest.skipIf(sys.platform == "vxworks",
17981816
"fifo requires special path on VxWorks")
@@ -1808,6 +1826,15 @@ def test_is_fifo_true(self):
18081826
self.assertIs(self.cls(self.base, 'myfifo\udfff').is_fifo(), False)
18091827
self.assertIs(self.cls(self.base, 'myfifo\x00').is_fifo(), False)
18101828

1829+
def test_is_socket_false(self):
1830+
P = self.cls(self.base)
1831+
self.assertFalse((P / 'fileA').is_socket())
1832+
self.assertFalse((P / 'dirA').is_socket())
1833+
self.assertFalse((P / 'non-existing').is_socket())
1834+
self.assertFalse((P / 'fileA' / 'bah').is_socket())
1835+
self.assertIs((P / 'fileA\udfff').is_socket(), False)
1836+
self.assertIs((P / 'fileA\x00').is_socket(), False)
1837+
18111838
@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required")
18121839
@unittest.skipIf(
18131840
is_emscripten, "Unix sockets are not implemented on Emscripten."
@@ -1831,6 +1858,24 @@ def test_is_socket_true(self):
18311858
self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False)
18321859
self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False)
18331860

1861+
def test_is_block_device_false(self):
1862+
P = self.cls(self.base)
1863+
self.assertFalse((P / 'fileA').is_block_device())
1864+
self.assertFalse((P / 'dirA').is_block_device())
1865+
self.assertFalse((P / 'non-existing').is_block_device())
1866+
self.assertFalse((P / 'fileA' / 'bah').is_block_device())
1867+
self.assertIs((P / 'fileA\udfff').is_block_device(), False)
1868+
self.assertIs((P / 'fileA\x00').is_block_device(), False)
1869+
1870+
def test_is_char_device_false(self):
1871+
P = self.cls(self.base)
1872+
self.assertFalse((P / 'fileA').is_char_device())
1873+
self.assertFalse((P / 'dirA').is_char_device())
1874+
self.assertFalse((P / 'non-existing').is_char_device())
1875+
self.assertFalse((P / 'fileA' / 'bah').is_char_device())
1876+
self.assertIs((P / 'fileA\udfff').is_char_device(), False)
1877+
self.assertIs((P / 'fileA\x00').is_char_device(), False)
1878+
18341879
def test_is_char_device_true(self):
18351880
# os.devnull should generally be a char device.
18361881
P = self.cls(os.devnull)
@@ -1842,14 +1887,42 @@ def test_is_char_device_true(self):
18421887
self.assertIs(self.cls(f'{os.devnull}\udfff').is_char_device(), False)
18431888
self.assertIs(self.cls(f'{os.devnull}\x00').is_char_device(), False)
18441889

1845-
def test_is_mount_root(self):
1890+
def test_is_mount(self):
1891+
P = self.cls(self.base)
1892+
self.assertFalse((P / 'fileA').is_mount())
1893+
self.assertFalse((P / 'dirA').is_mount())
1894+
self.assertFalse((P / 'non-existing').is_mount())
1895+
self.assertFalse((P / 'fileA' / 'bah').is_mount())
1896+
if self.can_symlink:
1897+
self.assertFalse((P / 'linkA').is_mount())
18461898
if os.name == 'nt':
18471899
R = self.cls('c:\\')
18481900
else:
18491901
R = self.cls('/')
18501902
self.assertTrue(R.is_mount())
18511903
self.assertFalse((R / '\udfff').is_mount())
18521904

1905+
def test_samefile(self):
1906+
parser = self.parser
1907+
fileA_path = parser.join(self.base, 'fileA')
1908+
fileB_path = parser.join(self.base, 'dirB', 'fileB')
1909+
p = self.cls(fileA_path)
1910+
pp = self.cls(fileA_path)
1911+
q = self.cls(fileB_path)
1912+
self.assertTrue(p.samefile(fileA_path))
1913+
self.assertTrue(p.samefile(pp))
1914+
self.assertFalse(p.samefile(fileB_path))
1915+
self.assertFalse(p.samefile(q))
1916+
# Test the non-existent file case
1917+
non_existent = parser.join(self.base, 'foo')
1918+
r = self.cls(non_existent)
1919+
self.assertRaises(FileNotFoundError, p.samefile, r)
1920+
self.assertRaises(FileNotFoundError, p.samefile, non_existent)
1921+
self.assertRaises(FileNotFoundError, r.samefile, p)
1922+
self.assertRaises(FileNotFoundError, r.samefile, non_existent)
1923+
self.assertRaises(FileNotFoundError, r.samefile, r)
1924+
self.assertRaises(FileNotFoundError, r.samefile, non_existent)
1925+
18531926
def test_passing_kwargs_errors(self):
18541927
with self.assertRaises(TypeError):
18551928
self.cls(foo="bar")

0 commit comments

Comments
 (0)