diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 9bbfe384ce54c4..338575404ff0ad 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -978,23 +978,35 @@ call fails (for example because the path doesn't exist). available. In previous versions, :exc:`NotImplementedError` was raised. -.. method:: Path.is_dir() +.. method:: Path.is_dir(*, follow_symlinks=True) - Return ``True`` if the path points to a directory (or a symbolic link - pointing to a directory), ``False`` if it points to another kind of file. + Return ``True`` if the path points to a directory, ``False`` if it points + to another kind of file. ``False`` is also returned if the path doesn't exist or is a broken symlink; other errors (such as permission errors) are propagated. + This method normally follows symlinks; to exclude symlinks to directories, + add the argument ``follow_symlinks=False``. -.. method:: Path.is_file() + .. versionchanged:: 3.13 + The *follow_symlinks* parameter was added. + + +.. method:: Path.is_file(*, follow_symlinks=True) - Return ``True`` if the path points to a regular file (or a symbolic link - pointing to a regular file), ``False`` if it points to another kind of file. + Return ``True`` if the path points to a regular file, ``False`` if it + points to another kind of file. ``False`` is also returned if the path doesn't exist or is a broken symlink; other errors (such as permission errors) are propagated. + This method normally follows symlinks; to exclude symlinks, add the + argument ``follow_symlinks=False``. + + .. versionchanged:: 3.13 + The *follow_symlinks* parameter was added. + .. method:: Path.is_junction() diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b6d13a8aac0f0e..f3460beeb16be6 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -113,9 +113,10 @@ pathlib * Add support for recursive wildcards in :meth:`pathlib.PurePath.match`. (Contributed by Barney Gale in :gh:`73435`.) -* Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob` and - :meth:`~pathlib.Path.rglob`. - (Contributed by Barney Gale in :gh:`77609`.) +* Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob`, + :meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`, and + :meth:`~pathlib.Path.is_dir`. + (Contributed by Barney Gale in :gh:`77609` and :gh:`105793`.) traceback --------- diff --git a/Lib/pathlib.py b/Lib/pathlib.py index a36ffdd73d8af1..e15718dc98d677 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -817,12 +817,12 @@ def exists(self, *, follow_symlinks=True): return False return True - def is_dir(self): + def is_dir(self, *, follow_symlinks=True): """ Whether this path is a directory. """ try: - return S_ISDIR(self.stat().st_mode) + return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode) except OSError as e: if not _ignore_error(e): raise @@ -833,13 +833,13 @@ def is_dir(self): # Non-encodable path return False - def is_file(self): + 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().st_mode) + return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode) except OSError as e: if not _ignore_error(e): raise diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index f9356909cb0982..eeb522bb160523 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2191,9 +2191,22 @@ def test_is_dir(self): if os_helper.can_symlink(): self.assertFalse((P / 'linkA').is_dir()) self.assertTrue((P / 'linkB').is_dir()) - self.assertFalse((P/ 'brokenLink').is_dir(), False) - self.assertIs((P / 'dirA\udfff').is_dir(), False) - self.assertIs((P / 'dirA\x00').is_dir(), False) + self.assertFalse((P/ 'brokenLink').is_dir()) + self.assertFalse((P / 'dirA\udfff').is_dir()) + self.assertFalse((P / 'dirA\x00').is_dir()) + + def test_is_dir_no_follow_symlinks(self): + P = self.cls(BASE) + self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False)) + if os_helper.can_symlink(): + self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) def test_is_file(self): P = self.cls(BASE) @@ -2205,8 +2218,21 @@ def test_is_file(self): self.assertTrue((P / 'linkA').is_file()) self.assertFalse((P / 'linkB').is_file()) self.assertFalse((P/ 'brokenLink').is_file()) - self.assertIs((P / 'fileA\udfff').is_file(), False) - self.assertIs((P / 'fileA\x00').is_file(), False) + self.assertFalse((P / 'fileA\udfff').is_file()) + self.assertFalse((P / 'fileA\x00').is_file()) + + def test_is_file_no_follow_symlinks(self): + P = self.cls(BASE) + self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False)) + if os_helper.can_symlink(): + self.assertFalse((P / 'linkA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_file(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False)) + 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(BASE) diff --git a/Misc/NEWS.d/next/Library/2023-06-14-18-41-18.gh-issue-105793.YSoykM.rst b/Misc/NEWS.d/next/Library/2023-06-14-18-41-18.gh-issue-105793.YSoykM.rst new file mode 100644 index 00000000000000..0e4090ea7eabb9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-14-18-41-18.gh-issue-105793.YSoykM.rst @@ -0,0 +1,2 @@ +Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.is_dir` and +:meth:`~pathlib.Path.is_file`, defaulting to ``True``.