]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-105793: Add follow_symlinks argument to `pathlib.Path.is_dir()` and `is_file(...
authorBarney Gale <barney.gale@gmail.com>
Mon, 26 Jun 2023 16:58:17 +0000 (17:58 +0100)
committerGitHub <noreply@github.com>
Mon, 26 Jun 2023 16:58:17 +0000 (17:58 +0100)
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()`.

Doc/library/pathlib.rst
Doc/whatsnew/3.13.rst
Lib/pathlib.py
Lib/test/test_pathlib.py
Misc/NEWS.d/next/Library/2023-06-14-18-41-18.gh-issue-105793.YSoykM.rst [new file with mode: 0644]

index 9bbfe384ce54c42d186292b96b6cf8e639a413a4..338575404ff0ad5fa415235daf790c7a5a4e51df 100644 (file)
@@ -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()
 
index b6d13a8aac0f0e091574cacd4cea6234955c3c76..f3460beeb16be678352db98c84891462652533a6 100644 (file)
@@ -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
 ---------
index a36ffdd73d8af1191c00e8f08daf973239bc54d5..e15718dc98d677c9c6daebb54dff44a9ffad0544 100644 (file)
@@ -817,12 +817,12 @@ class Path(PurePath):
             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 @@ class Path(PurePath):
             # 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
index f9356909cb09826a72cb5112977506a100368ea1..eeb522bb160523b04e7734c904eaf2400cb05175 100644 (file)
@@ -2191,9 +2191,22 @@ class PathTest(unittest.TestCase):
         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 @@ class PathTest(unittest.TestCase):
             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 (file)
index 0000000..0e4090e
--- /dev/null
@@ -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``.