Skip to content

Commit fa8931b

Browse files
committed
Add Status.exists()
1 parent 764b8ae commit fa8931b

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

Doc/library/pathlib.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,6 +1953,14 @@ The :mod:`pathlib.types` module provides types for static type checking.
19531953
:attr:`Path.status <pathlib.Path.status>` attribute. Implementations may
19541954
return cached results from their methods.
19551955

1956+
.. method:: exists(*, follow_symlinks=True)
1957+
1958+
Return ``True`` if this path status is for an existing file or
1959+
directory, or any other kind of file.
1960+
1961+
If *follow_symlinks* is ``False``, return ``True`` for symlinks without
1962+
checking if their targets exist.
1963+
19561964
.. method:: is_dir(*, follow_symlinks=True)
19571965

19581966
Return ``True`` if this status is a directory or a symbolic link

Lib/pathlib/_local.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ def _get_mode(self, *, follow_symlinks=True):
8989
self._mode = [mode, mode]
9090
return mode
9191

92+
def exists(self, *, follow_symlinks=True):
93+
"""
94+
Whether this path exists
95+
"""
96+
return self._get_mode(follow_symlinks=follow_symlinks) > 0
97+
9298
def is_dir(self, *, follow_symlinks=True):
9399
"""
94100
Whether this path is a directory.
@@ -118,6 +124,17 @@ def __init__(self, path, entry):
118124
def __repr__(self):
119125
return self._repr
120126

127+
def exists(self, *, follow_symlinks=True):
128+
"""
129+
Whether this path exists
130+
"""
131+
if follow_symlinks:
132+
try:
133+
self._entry.stat()
134+
except FileNotFoundError:
135+
return False
136+
return True
137+
121138
def is_dir(self, *, follow_symlinks=True):
122139
"""
123140
Whether this path is a directory.

Lib/pathlib/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Status(Protocol):
2727
"""Protocol for path statuses, which support querying the file type.
2828
Methods may return cached results.
2929
"""
30+
def exists(self, *, follow_symlinks: bool = True) -> bool: ...
3031
def is_dir(self, *, follow_symlinks: bool = True) -> bool: ...
3132
def is_file(self, *, follow_symlinks: bool = True) -> bool: ...
3233
def is_symlink(self) -> bool: ...

Lib/test/test_pathlib/test_pathlib.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,6 +1769,22 @@ def test_symlink_to_unsupported(self):
17691769
with self.assertRaises(pathlib.UnsupportedOperation):
17701770
q.symlink_to(p)
17711771

1772+
def test_status_exists_caching(self):
1773+
p = self.cls(self.base)
1774+
q = p / 'myfile'
1775+
self.assertFalse(q.status.exists())
1776+
self.assertFalse(q.status.exists(follow_symlinks=False))
1777+
q.write_text('hullo')
1778+
self.assertFalse(q.status.exists())
1779+
self.assertFalse(q.status.exists(follow_symlinks=False))
1780+
1781+
q = p / 'myfile' # same path, new instance.
1782+
self.assertTrue(q.status.exists())
1783+
self.assertTrue(q.status.exists(follow_symlinks=False))
1784+
q.unlink()
1785+
self.assertTrue(q.status.exists())
1786+
self.assertTrue(q.status.exists(follow_symlinks=False))
1787+
17721788
def test_status_is_dir_caching(self):
17731789
p = self.cls(self.base)
17741790
q = p / 'mydir'

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1876,13 +1876,23 @@ def test_iterdir_status(self):
18761876
for child in p.iterdir():
18771877
entry = child.status
18781878
self.assertIsInstance(entry, Status)
1879+
self.assertTrue(entry.exists(follow_symlinks=False))
18791880
self.assertEqual(entry.is_dir(follow_symlinks=False),
18801881
child.is_dir(follow_symlinks=False))
18811882
self.assertEqual(entry.is_file(follow_symlinks=False),
18821883
child.is_file(follow_symlinks=False))
18831884
self.assertEqual(entry.is_symlink(),
18841885
child.is_symlink())
1885-
if child.name != 'brokenLinkLoop':
1886+
if child.name == 'brokenLink':
1887+
self.assertFalse(entry.exists())
1888+
self.assertFalse(entry.is_dir())
1889+
self.assertFalse(entry.is_file())
1890+
elif child.name == 'brokenLinkLoop':
1891+
self.assertRaises(OSError, entry.exists)
1892+
self.assertRaises(OSError, entry.is_dir)
1893+
self.assertRaises(OSError, entry.is_file)
1894+
else:
1895+
self.assertTrue(entry.exists())
18861896
self.assertEqual(entry.is_dir(), child.is_dir())
18871897
self.assertEqual(entry.is_file(), child.is_file())
18881898

@@ -2010,6 +2020,27 @@ def test_rglob_windows(self):
20102020
self.assertEqual(set(p.rglob("FILEd")), { P(self.base, "dirC/dirD/fileD") })
20112021
self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") })
20122022

2023+
def test_exists(self):
2024+
p = self.cls(self.base)
2025+
self.assertTrue(p.status.exists())
2026+
self.assertTrue((p / 'dirA').status.exists())
2027+
self.assertTrue((p / 'dirA').status.exists(follow_symlinks=False))
2028+
self.assertTrue((p / 'fileA').status.exists())
2029+
self.assertTrue((p / 'fileA').status.exists(follow_symlinks=False))
2030+
self.assertFalse((p / 'non-existing').status.exists())
2031+
self.assertFalse((p / 'non-existing').status.exists(follow_symlinks=False))
2032+
if self.can_symlink:
2033+
self.assertTrue((p / 'linkA').status.exists())
2034+
self.assertTrue((p / 'linkA').status.exists(follow_symlinks=False))
2035+
self.assertTrue((p / 'linkB').status.exists())
2036+
self.assertTrue((p / 'linkB').status.exists(follow_symlinks=True))
2037+
self.assertFalse((p / 'brokenLink').status.exists())
2038+
self.assertTrue((p / 'brokenLink').status.exists(follow_symlinks=False))
2039+
self.assertFalse((p / 'fileA\udfff').status.exists())
2040+
self.assertFalse((p / 'fileA\udfff').status.exists(follow_symlinks=False))
2041+
self.assertFalse((p / 'fileA\x00').status.exists())
2042+
self.assertFalse((p / 'fileA\x00').status.exists(follow_symlinks=False))
2043+
20132044
def test_status_is_dir(self):
20142045
p = self.cls(self.base)
20152046
self.assertTrue((p / 'dirA').status.is_dir())

0 commit comments

Comments
 (0)