From 272c1b41b52dbfc769c58a9c4970a4ec204763ea Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Fri, 19 Nov 2021 22:48:36 -0500 Subject: [PATCH 01/14] do not follow links when checking for precise glob match --- Lib/pathlib.py | 7 ++++--- Lib/test/test_pathlib.py | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 621fba0e75c0f7..7abcce1b50a445 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -422,7 +422,8 @@ def __init__(self, name, child_parts, flavour): def _select_from(self, parent_path, is_dir, exists, scandir): try: path = parent_path._make_child_relpath(self.name) - if (is_dir if self.dironly else exists)(path): + if ((self.dironly and is_dir(path)) or \ + (not self.dironly and exists(path, follow_symlinks=False))): for p in self.successor._select_from(path, is_dir, exists, scandir): yield p except PermissionError: @@ -1280,12 +1281,12 @@ def link_to(self, target): # Convenience functions for querying the stat results - def exists(self): + def exists(self, follow_symlinks=True): """ Whether this path exists. """ try: - self.stat() + self.stat(follow_symlinks=follow_symlinks) 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 5f6d9f47d13d75..ff4468e6ef0dfa 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1529,6 +1529,7 @@ def test_exists(self): self.assertIs(False, P('/xyzzy').exists()) self.assertIs(False, P(BASE + '\udfff').exists()) self.assertIs(False, P(BASE + '\x00').exists()) + self.assertIs(False, (p / 'brokenLink').exists()) def test_open_common(self): p = self.cls(BASE) @@ -1631,6 +1632,8 @@ def _check(glob, expected): _check(p.glob("*/fileB"), ['dirB/fileB']) else: _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB']) + if os_helper.can_symlink(): + _check(p.glob("brokenLink"), ['brokenLink']) def test_rglob_common(self): def _check(glob, expected): From 70db4c0ff5a5dde54d900c669cc55b529c0d7fae Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Fri, 19 Nov 2021 23:22:48 -0500 Subject: [PATCH 02/14] update docs for `exists()` --- Doc/library/pathlib.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index b6507eb4d6fa2c..15a4a15767d337 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -755,9 +755,12 @@ call fails (for example because the path doesn't exist). .. versionchanged:: 3.10 The *follow_symlinks* parameter was added. -.. method:: Path.exists() +.. method:: Path.exists(follow_symlinks=True) - Whether the path points to an existing file or directory:: + Return ``True`` if the path points to an existing file or directory. + + This method normally follows symlinks; to check if a symlink exists, add + the argument ``follow_symlinks=False``. >>> Path('.').exists() True @@ -768,10 +771,8 @@ call fails (for example because the path doesn't exist). >>> Path('nonexistentfile').exists() False - .. note:: - If the path points to a symlink, :meth:`exists` returns whether the - symlink *points to* an existing file or directory. - + .. versionchanged:: 3.11 + The *follow_symlinks* parameter was added. .. method:: Path.expanduser() From cf0f8236659ac3d8958a54c436ed1f46a0d4b6be Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Fri, 19 Nov 2021 23:37:26 -0500 Subject: [PATCH 03/14] add news entry --- .../next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst diff --git a/Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst b/Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst new file mode 100644 index 00000000000000..0f4c3cd4ad0b94 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst @@ -0,0 +1,5 @@ +Fixed the bug in :meth:`~pathlib.Path.glob` -- previously a dangling symlink +would not be found by this method when the pattern is an exact match, but +would be found when the pattern contains a wildcard or the recursive +wildcard (``**``). With this change, a dangling symlink will be found in +both cases. From 9107f3d7f13987c933caa0e41b09316619c17958 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Sat, 20 Nov 2021 00:49:56 -0500 Subject: [PATCH 04/14] add docstring; fix the doctest error in the documentation --- Doc/library/pathlib.rst | 2 ++ Lib/pathlib.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 15a4a15767d337..2a946d6c159877 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -762,6 +762,8 @@ call fails (for example because the path doesn't exist). This method normally follows symlinks; to check if a symlink exists, add the argument ``follow_symlinks=False``. + :: + >>> Path('.').exists() True >>> Path('setup.py').exists() diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 7abcce1b50a445..93022cdbaddd67 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1284,6 +1284,10 @@ def link_to(self, target): def exists(self, follow_symlinks=True): """ Whether this path exists. + + If path is a symlink: with `follow_symlinks=True`, return True if path + pointed to by the symlink exists; with follow_symlinks=False, return + True if the symlink itself exists. """ try: self.stat(follow_symlinks=follow_symlinks) From 4c40ea371bdb52499bcc519215dee93b7134b7a9 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Sat, 20 Nov 2021 09:52:37 -0500 Subject: [PATCH 05/14] expand test_exists --- Lib/test/test_pathlib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index ff4468e6ef0dfa..2166e1f2b27cf5 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1530,6 +1530,7 @@ def test_exists(self): self.assertIs(False, P(BASE + '\udfff').exists()) self.assertIs(False, P(BASE + '\x00').exists()) self.assertIs(False, (p / 'brokenLink').exists()) + self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) def test_open_common(self): p = self.cls(BASE) From 686cd34c42eedce170870950f3c690f195546cf3 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Sat, 20 Nov 2021 09:58:34 -0500 Subject: [PATCH 06/14] fix typo in var name --- Lib/pathlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 93022cdbaddd67..e5b265597b51a9 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -28,7 +28,7 @@ _WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself # EBADF - guard against macOS `stat` throwing EBADF -_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP) +_IGNORED_ERRORS = (ENOENT, ENOTDIR, EBADF, ELOOP) _IGNORED_WINERRORS = ( _WINERROR_NOT_READY, @@ -36,7 +36,7 @@ _WINERROR_CANT_RESOLVE_FILENAME) def _ignore_error(exception): - return (getattr(exception, 'errno', None) in _IGNORED_ERROS or + return (getattr(exception, 'errno', None) in _IGNORED_ERRORS or getattr(exception, 'winerror', None) in _IGNORED_WINERRORS) From 01ec5af1434708e7155a33e137df95d2de7a7248 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Sat, 20 Nov 2021 10:05:04 -0500 Subject: [PATCH 07/14] revert typo fix --- Lib/pathlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index e5b265597b51a9..93022cdbaddd67 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -28,7 +28,7 @@ _WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself # EBADF - guard against macOS `stat` throwing EBADF -_IGNORED_ERRORS = (ENOENT, ENOTDIR, EBADF, ELOOP) +_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP) _IGNORED_WINERRORS = ( _WINERROR_NOT_READY, @@ -36,7 +36,7 @@ _WINERROR_CANT_RESOLVE_FILENAME) def _ignore_error(exception): - return (getattr(exception, 'errno', None) in _IGNORED_ERRORS or + return (getattr(exception, 'errno', None) in _IGNORED_ERROS or getattr(exception, 'winerror', None) in _IGNORED_WINERRORS) From 1d03f90e8a93255da4f14db3d9928babe5aecfef Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Sat, 20 Nov 2021 12:22:19 -0500 Subject: [PATCH 08/14] move symlink tests to conditional --- Lib/test/test_pathlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 2166e1f2b27cf5..3fb0745b298def 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1525,12 +1525,12 @@ def test_exists(self): self.assertIs(True, (p / 'linkB').exists()) self.assertIs(True, (p / 'linkB' / 'fileB').exists()) self.assertIs(False, (p / 'linkA' / 'bah').exists()) + self.assertIs(False, (p / 'brokenLink').exists()) + self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) self.assertIs(False, (p / 'foo').exists()) self.assertIs(False, P('/xyzzy').exists()) self.assertIs(False, P(BASE + '\udfff').exists()) self.assertIs(False, P(BASE + '\x00').exists()) - self.assertIs(False, (p / 'brokenLink').exists()) - self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) def test_open_common(self): p = self.cls(BASE) From 66700bfcd307c5223be95ce5b89a7ca6c9f11694 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 2 May 2023 21:53:15 -0400 Subject: [PATCH 09/14] Update Lib/pathlib.py Co-authored-by: Barney Gale --- Lib/pathlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 93022cdbaddd67..2b3253ce0fea9e 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -422,8 +422,8 @@ def __init__(self, name, child_parts, flavour): def _select_from(self, parent_path, is_dir, exists, scandir): try: path = parent_path._make_child_relpath(self.name) - if ((self.dironly and is_dir(path)) or \ - (not self.dironly and exists(path, follow_symlinks=False))): + follow = is_dir(path) if self.dironly else exists(path, follow_symlinks=False) + if follow: for p in self.successor._select_from(path, is_dir, exists, scandir): yield p except PermissionError: From 0e07f8839730c1eda2d4b82cbfda6a7563faade7 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 2 May 2023 21:53:26 -0400 Subject: [PATCH 10/14] Update Doc/library/pathlib.rst Co-authored-by: Barney Gale --- Doc/library/pathlib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 2a946d6c159877..de545fe84e688f 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -755,7 +755,7 @@ call fails (for example because the path doesn't exist). .. versionchanged:: 3.10 The *follow_symlinks* parameter was added. -.. method:: Path.exists(follow_symlinks=True) +.. method:: Path.exists(*, follow_symlinks=True) Return ``True`` if the path points to an existing file or directory. From 071f0f3b2820bd34798a40d7ddf033773bea4725 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 2 May 2023 21:53:38 -0400 Subject: [PATCH 11/14] Update Lib/pathlib.py Co-authored-by: Barney Gale --- Lib/pathlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 2b3253ce0fea9e..b6880c2d591e4a 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1281,7 +1281,7 @@ def link_to(self, target): # Convenience functions for querying the stat results - def exists(self, follow_symlinks=True): + def exists(self, *, follow_symlinks=True): """ Whether this path exists. From 6cff634dd061d1d25b66c17ea329d259da6f89e1 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 2 May 2023 21:53:57 -0400 Subject: [PATCH 12/14] Update Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst Co-authored-by: Barney Gale --- .../next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst b/Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst index 0f4c3cd4ad0b94..531f4729220036 100644 --- a/Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst +++ b/Misc/NEWS.d/next/Library/2021-11-19-23-37-18.bpo-45606.UW5XE1.rst @@ -1,4 +1,4 @@ -Fixed the bug in :meth:`~pathlib.Path.glob` -- previously a dangling symlink +Fixed the bug in :meth:`pathlib.Path.glob` -- previously a dangling symlink would not be found by this method when the pattern is an exact match, but would be found when the pattern contains a wildcard or the recursive wildcard (``**``). With this change, a dangling symlink will be found in From 3818c3152d2104f34931131595e88abeb6aaad2f Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 2 May 2023 22:23:41 -0400 Subject: [PATCH 13/14] update docstring --- Lib/pathlib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index b6880c2d591e4a..92ac086d66b8ef 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1285,9 +1285,8 @@ def exists(self, *, follow_symlinks=True): """ Whether this path exists. - If path is a symlink: with `follow_symlinks=True`, return True if path - pointed to by the symlink exists; with follow_symlinks=False, return - True if the symlink itself exists. + This method normally follows symlinks; to check whether a symlink exists, + add the argument follow_symlinks=False. """ try: self.stat(follow_symlinks=follow_symlinks) From 0093d52eb1d0ad92793c997fa169873f3cb64388 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 2 May 2023 22:42:51 -0400 Subject: [PATCH 14/14] Update Doc/library/pathlib.rst Co-authored-by: Barney Gale --- Doc/library/pathlib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index b472ddef5bc779..4847ac24c77513 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -837,7 +837,7 @@ call fails (for example because the path doesn't exist). >>> Path('nonexistentfile').exists() False - .. versionchanged:: 3.11 + .. versionchanged:: 3.12 The *follow_symlinks* parameter was added. .. method:: Path.expanduser()