Skip to content

Commit

Permalink
pythonGH-89769: pathlib.Path.glob(): do not follow symlinks when ch…
Browse files Browse the repository at this point in the history
…ecking for precise match (pythonGH-29655)

Co-authored-by: Barney Gale <barney.gale@gmail.com>
  • Loading branch information
akulakov and barneygale committed May 3, 2023
1 parent c7c3a60 commit af886ff
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 9 deletions.
15 changes: 9 additions & 6 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -819,9 +819,14 @@ 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
Expand All @@ -832,10 +837,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.12
The *follow_symlinks* parameter was added.

.. method:: Path.expanduser()

Expand Down
10 changes: 7 additions & 3 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,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):
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:
Expand Down Expand Up @@ -1122,12 +1123,15 @@ def hardlink_to(self, target):

# Convenience functions for querying the stat results

def exists(self):
def exists(self, *, follow_symlinks=True):
"""
Whether this path exists.
This method normally follows symlinks; to check whether a symlink exists,
add the argument follow_symlinks=False.
"""
try:
self.stat()
self.stat(follow_symlinks=follow_symlinks)
except OSError as e:
if not _ignore_error(e):
raise
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,8 @@ 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())
Expand Down Expand Up @@ -1806,6 +1808,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'])

if not os_helper.can_symlink():
_check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE"])
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit af886ff

Please sign in to comment.