Skip to content

Commit 219effa

Browse files
authored
GH-105793: Add follow_symlinks argument to pathlib.Path.is_dir() and is_file() (GH-105794)
Brings `pathlib.Path.is_dir()` and `in line with `os.DirEntry.is_dir()`, which will be important for implementing generic path walking and globbing. Likewise `is_file()`.
1 parent 5d4dbf0 commit 219effa

File tree

5 files changed

+59
-18
lines changed

5 files changed

+59
-18
lines changed

Doc/library/pathlib.rst

+18-6
Original file line numberDiff line numberDiff line change
@@ -978,23 +978,35 @@ call fails (for example because the path doesn't exist).
978978
available. In previous versions, :exc:`NotImplementedError` was raised.
979979

980980

981-
.. method:: Path.is_dir()
981+
.. method:: Path.is_dir(*, follow_symlinks=True)
982982

983-
Return ``True`` if the path points to a directory (or a symbolic link
984-
pointing to a directory), ``False`` if it points to another kind of file.
983+
Return ``True`` if the path points to a directory, ``False`` if it points
984+
to another kind of file.
985985

986986
``False`` is also returned if the path doesn't exist or is a broken symlink;
987987
other errors (such as permission errors) are propagated.
988988

989+
This method normally follows symlinks; to exclude symlinks to directories,
990+
add the argument ``follow_symlinks=False``.
989991

990-
.. method:: Path.is_file()
992+
.. versionchanged:: 3.13
993+
The *follow_symlinks* parameter was added.
994+
995+
996+
.. method:: Path.is_file(*, follow_symlinks=True)
991997

992-
Return ``True`` if the path points to a regular file (or a symbolic link
993-
pointing to a regular file), ``False`` if it points to another kind of file.
998+
Return ``True`` if the path points to a regular file, ``False`` if it
999+
points to another kind of file.
9941000

9951001
``False`` is also returned if the path doesn't exist or is a broken symlink;
9961002
other errors (such as permission errors) are propagated.
9971003

1004+
This method normally follows symlinks; to exclude symlinks, add the
1005+
argument ``follow_symlinks=False``.
1006+
1007+
.. versionchanged:: 3.13
1008+
The *follow_symlinks* parameter was added.
1009+
9981010

9991011
.. method:: Path.is_junction()
10001012

Doc/whatsnew/3.13.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,10 @@ pathlib
113113
* Add support for recursive wildcards in :meth:`pathlib.PurePath.match`.
114114
(Contributed by Barney Gale in :gh:`73435`.)
115115

116-
* Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob` and
117-
:meth:`~pathlib.Path.rglob`.
118-
(Contributed by Barney Gale in :gh:`77609`.)
116+
* Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob`,
117+
:meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`, and
118+
:meth:`~pathlib.Path.is_dir`.
119+
(Contributed by Barney Gale in :gh:`77609` and :gh:`105793`.)
119120

120121
traceback
121122
---------

Lib/pathlib.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,12 @@ def exists(self, *, follow_symlinks=True):
817817
return False
818818
return True
819819

820-
def is_dir(self):
820+
def is_dir(self, *, follow_symlinks=True):
821821
"""
822822
Whether this path is a directory.
823823
"""
824824
try:
825-
return S_ISDIR(self.stat().st_mode)
825+
return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
826826
except OSError as e:
827827
if not _ignore_error(e):
828828
raise
@@ -833,13 +833,13 @@ def is_dir(self):
833833
# Non-encodable path
834834
return False
835835

836-
def is_file(self):
836+
def is_file(self, *, follow_symlinks=True):
837837
"""
838838
Whether this path is a regular file (also True for symlinks pointing
839839
to regular files).
840840
"""
841841
try:
842-
return S_ISREG(self.stat().st_mode)
842+
return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
843843
except OSError as e:
844844
if not _ignore_error(e):
845845
raise

Lib/test/test_pathlib.py

+31-5
Original file line numberDiff line numberDiff line change
@@ -2191,9 +2191,22 @@ def test_is_dir(self):
21912191
if os_helper.can_symlink():
21922192
self.assertFalse((P / 'linkA').is_dir())
21932193
self.assertTrue((P / 'linkB').is_dir())
2194-
self.assertFalse((P/ 'brokenLink').is_dir(), False)
2195-
self.assertIs((P / 'dirA\udfff').is_dir(), False)
2196-
self.assertIs((P / 'dirA\x00').is_dir(), False)
2194+
self.assertFalse((P/ 'brokenLink').is_dir())
2195+
self.assertFalse((P / 'dirA\udfff').is_dir())
2196+
self.assertFalse((P / 'dirA\x00').is_dir())
2197+
2198+
def test_is_dir_no_follow_symlinks(self):
2199+
P = self.cls(BASE)
2200+
self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False))
2201+
self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False))
2202+
self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False))
2203+
self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False))
2204+
if os_helper.can_symlink():
2205+
self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False))
2206+
self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False))
2207+
self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False))
2208+
self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False))
2209+
self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False))
21972210

21982211
def test_is_file(self):
21992212
P = self.cls(BASE)
@@ -2205,8 +2218,21 @@ def test_is_file(self):
22052218
self.assertTrue((P / 'linkA').is_file())
22062219
self.assertFalse((P / 'linkB').is_file())
22072220
self.assertFalse((P/ 'brokenLink').is_file())
2208-
self.assertIs((P / 'fileA\udfff').is_file(), False)
2209-
self.assertIs((P / 'fileA\x00').is_file(), False)
2221+
self.assertFalse((P / 'fileA\udfff').is_file())
2222+
self.assertFalse((P / 'fileA\x00').is_file())
2223+
2224+
def test_is_file_no_follow_symlinks(self):
2225+
P = self.cls(BASE)
2226+
self.assertTrue((P / 'fileA').is_file(follow_symlinks=False))
2227+
self.assertFalse((P / 'dirA').is_file(follow_symlinks=False))
2228+
self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False))
2229+
self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False))
2230+
if os_helper.can_symlink():
2231+
self.assertFalse((P / 'linkA').is_file(follow_symlinks=False))
2232+
self.assertFalse((P / 'linkB').is_file(follow_symlinks=False))
2233+
self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False))
2234+
self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False))
2235+
self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False))
22102236

22112237
def test_is_mount(self):
22122238
P = self.cls(BASE)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.is_dir` and
2+
:meth:`~pathlib.Path.is_file`, defaulting to ``True``.

0 commit comments

Comments
 (0)