From 7c2c51adab68448ef1eba4118b669bbf9cbad483 Mon Sep 17 00:00:00 2001 From: barneygale Date: Wed, 14 Jun 2023 18:44:53 +0100 Subject: [PATCH 1/4] GH-105793: Add follow_symlinks argument to pathlib.Path.is_dir() Brings `pathlib.Path.is_dir()` in line with `os.DirEntry.is_dir()`, which will be important for implementing generic path walking and globbing. --- Doc/library/pathlib.rst | 12 +++++++++--- Lib/pathlib.py | 4 ++-- Lib/test/test_pathlib.py | 19 ++++++++++++++++--- ...-06-14-18-41-18.gh-issue-105793.YSoykM.rst | 2 ++ 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-06-14-18-41-18.gh-issue-105793.YSoykM.rst diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index ad801d5d7cdc4b..24a5d401e7aae5 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -953,14 +953,20 @@ call fails (for example because the path doesn't exist). if the file's gid isn't found in the system database. -.. 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. + + This method normally follows symlinks; to exclude symlinks to directories, + add the argument ``follow_symlinks=False``. ``False`` is also returned if the path doesn't exist or is a broken symlink; other errors (such as permission errors) are propagated. + .. versionchanged:: 3.13 + The *follow_symlinks* parameter was added. + .. method:: Path.is_file() diff --git a/Lib/pathlib.py b/Lib/pathlib.py index d8c597f1027f30..1088b886d7a825 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -809,12 +809,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 diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 46ac30e91e32aa..eead05b3438584 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2623,9 +2623,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) 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..847da661234041 --- /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`, +defaulting to ``True``. From 1f666709768e4c2de75995ad5ab391143306d95c Mon Sep 17 00:00:00 2001 From: barneygale Date: Thu, 15 Jun 2023 05:12:44 +0100 Subject: [PATCH 2/4] Also add argument to `is_file()`. --- Doc/library/pathlib.rst | 18 ++++++++++++------ Lib/pathlib.py | 4 ++-- Lib/test/test_pathlib.py | 17 +++++++++++++++-- ...3-06-14-18-41-18.gh-issue-105793.YSoykM.rst | 4 ++-- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 24a5d401e7aae5..8c7701897916b7 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -958,24 +958,30 @@ call fails (for example because the path doesn't exist). Return ``True`` if the path points to a directory, ``False`` if it points to another kind of file. - This method normally follows symlinks; to exclude symlinks to directories, - add the argument ``follow_symlinks=False``. - ``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``. + .. versionchanged:: 3.13 The *follow_symlinks* parameter was added. -.. method:: Path.is_file() +.. 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/Lib/pathlib.py b/Lib/pathlib.py index 1088b886d7a825..f0ace3cfbb653e 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -825,13 +825,13 @@ def is_dir(self, *, follow_symlinks=True): # 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 78443451d01463..e7a15836fe9a2c 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2205,8 +2205,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 index 847da661234041..0e4090ea7eabb9 100644 --- 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 @@ -1,2 +1,2 @@ -Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.is_dir`, -defaulting to ``True``. +Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.is_dir` and +:meth:`~pathlib.Path.is_file`, defaulting to ``True``. From 235def1e9c806b8fb39cf581e2a45167c59125a5 Mon Sep 17 00:00:00 2001 From: barneygale Date: Sun, 18 Jun 2023 18:20:37 +0100 Subject: [PATCH 3/4] Add whatsnew entry --- Doc/whatsnew/3.13.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 7f61ade808cce5..97422220322afa 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -109,9 +109,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 --------- From 8dc751373b09a1803a8bf9b9467ea0fba34bc76e Mon Sep 17 00:00:00 2001 From: barneygale Date: Sun, 18 Jun 2023 18:29:02 +0100 Subject: [PATCH 4/4] Fix typo --- Doc/whatsnew/3.13.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 97422220322afa..f4abb9aae338e6 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -111,8 +111,8 @@ pathlib * 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`.) + :meth:`~pathlib.Path.is_dir`. + (Contributed by Barney Gale in :gh:`77609` and :gh:`105793`.) traceback ---------