Skip to content

Commit f1eb06c

Browse files
committed
pythonGH-101357: Suppress OSError from pathlib.Path.exists() and is_*()
Suppress all `OSError` exceptions from `pathlib.Path.exists()` and `is_*()` rather than a selection of more common errors as we do presently. Also adjust the implementations to call `os.path.exists()` etc, which are much faster on Windows thanks to pythonGH-101196.
1 parent 5865fa5 commit f1eb06c

File tree

4 files changed

+66
-103
lines changed

4 files changed

+66
-103
lines changed

Doc/library/pathlib.rst

+19-13
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ Methods
874874
^^^^^^^
875875

876876
Concrete paths provide the following methods in addition to pure paths
877-
methods. Many of these methods can raise an :exc:`OSError` if a system
877+
methods. Some of these methods can raise an :exc:`OSError` if a system
878878
call fails (for example because the path doesn't exist).
879879

880880
.. versionchanged:: 3.8
@@ -886,6 +886,12 @@ call fails (for example because the path doesn't exist).
886886
instead of raising an exception for paths that contain characters
887887
unrepresentable at the OS level.
888888

889+
.. versionchanged:: 3.14
890+
891+
The methods given above now return ``False`` instead of raising an
892+
:exc:`OSError` exception from the operating system. In previous versions,
893+
some kinds of :exc:`OSError` exception are raised, and others suppressed.
894+
889895

890896
.. classmethod:: Path.cwd()
891897

@@ -1071,8 +1077,8 @@ call fails (for example because the path doesn't exist).
10711077
Return ``True`` if the path points to a directory, ``False`` if it points
10721078
to another kind of file.
10731079

1074-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1075-
other errors (such as permission errors) are propagated.
1080+
``False`` is also returned if the path doesn't exist, or is a broken
1081+
symlink, or is inaccessible for any other reason.
10761082

10771083
This method normally follows symlinks; to exclude symlinks to directories,
10781084
add the argument ``follow_symlinks=False``.
@@ -1086,8 +1092,8 @@ call fails (for example because the path doesn't exist).
10861092
Return ``True`` if the path points to a regular file, ``False`` if it
10871093
points to another kind of file.
10881094

1089-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1090-
other errors (such as permission errors) are propagated.
1095+
``False`` is also returned if the path doesn't exist, or is a broken
1096+
symlink, or is inaccessible for any other reason.
10911097

10921098
This method normally follows symlinks; to exclude symlinks, add the
10931099
argument ``follow_symlinks=False``.
@@ -1125,8 +1131,8 @@ call fails (for example because the path doesn't exist).
11251131

11261132
Return ``True`` if the path points to a symbolic link, ``False`` otherwise.
11271133

1128-
``False`` is also returned if the path doesn't exist; other errors (such
1129-
as permission errors) are propagated.
1134+
``False`` is also returned if the path doesn't exist or is inaccessible for
1135+
any other reason.
11301136

11311137

11321138
.. method:: Path.is_socket()
@@ -1143,26 +1149,26 @@ call fails (for example because the path doesn't exist).
11431149
Return ``True`` if the path points to a FIFO (or a symbolic link
11441150
pointing to a FIFO), ``False`` if it points to another kind of file.
11451151

1146-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1147-
other errors (such as permission errors) are propagated.
1152+
``False`` is also returned if the path doesn't exist, or is a broken
1153+
symlink, or is inaccessible for any other reason.
11481154

11491155

11501156
.. method:: Path.is_block_device()
11511157

11521158
Return ``True`` if the path points to a block device (or a symbolic link
11531159
pointing to a block device), ``False`` if it points to another kind of file.
11541160

1155-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1156-
other errors (such as permission errors) are propagated.
1161+
``False`` is also returned if the path doesn't exist, or is a broken
1162+
symlink, or is inaccessible for any other reason.
11571163

11581164

11591165
.. method:: Path.is_char_device()
11601166

11611167
Return ``True`` if the path points to a character device (or a symbolic link
11621168
pointing to a character device), ``False`` if it points to another kind of file.
11631169

1164-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1165-
other errors (such as permission errors) are propagated.
1170+
``False`` is also returned if the path doesn't exist, or is a broken
1171+
symlink, or is inaccessible for any other reason.
11661172

11671173

11681174
.. method:: Path.iterdir()

Lib/glob.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def __init__(self, sep, case_sensitive, case_pedantic=False, recursive=False):
339339

340340
# Low-level methods
341341

342-
lstat = staticmethod(os.lstat)
342+
lexists = staticmethod(os.path.lexists)
343343
scandir = staticmethod(os.scandir)
344344
parse_entry = operator.attrgetter('path')
345345
concat_path = operator.add
@@ -512,12 +512,8 @@ def select_exists(self, path, exists=False):
512512
# Optimization: this path is already known to exist, e.g. because
513513
# it was returned from os.scandir(), so we skip calling lstat().
514514
yield path
515-
else:
516-
try:
517-
self.lstat(path)
518-
yield path
519-
except OSError:
520-
pass
515+
elif self.lexists(path):
516+
yield path
521517

522518
@classmethod
523519
def walk(cls, root, top_down, on_error, follow_symlinks):

Lib/pathlib/__init__.py

+34
Original file line numberDiff line numberDiff line change
@@ -525,12 +525,46 @@ def stat(self, *, follow_symlinks=True):
525525
"""
526526
return os.stat(self, follow_symlinks=follow_symlinks)
527527

528+
def exists(self, *, follow_symlinks=True):
529+
"""
530+
Whether this path exists.
531+
532+
This method normally follows symlinks; to check whether a symlink exists,
533+
add the argument follow_symlinks=False.
534+
"""
535+
if follow_symlinks:
536+
return os.path.exists(self)
537+
return os.path.lexists(self)
538+
539+
def is_dir(self, *, follow_symlinks=True):
540+
"""
541+
Whether this path is a directory.
542+
"""
543+
if follow_symlinks:
544+
return os.path.isdir(self)
545+
return _abc.PathBase.is_dir(self, follow_symlinks=follow_symlinks)
546+
547+
def is_file(self, *, follow_symlinks=True):
548+
"""
549+
Whether this path is a regular file (also True for symlinks pointing
550+
to regular files).
551+
"""
552+
if follow_symlinks:
553+
return os.path.isfile(self)
554+
return _abc.PathBase.is_file(self, follow_symlinks=follow_symlinks)
555+
528556
def is_mount(self):
529557
"""
530558
Check if this path is a mount point
531559
"""
532560
return os.path.ismount(self)
533561

562+
def is_symlink(self):
563+
"""
564+
Whether this path is a symbolic link.
565+
"""
566+
return os.path.islink(self)
567+
534568
def is_junction(self):
535569
"""
536570
Whether this path is a junction.

Lib/pathlib/_abc.py

+10-83
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,17 @@
1414
import functools
1515
import glob
1616
import operator
17-
from errno import ENOENT, ENOTDIR, EBADF, ELOOP, EINVAL
17+
from errno import ENOTDIR, ELOOP
1818
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
1919

20-
#
21-
# Internals
22-
#
23-
24-
_WINERROR_NOT_READY = 21 # drive exists but is not accessible
25-
_WINERROR_INVALID_NAME = 123 # fix for bpo-35306
26-
_WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself
27-
28-
# EBADF - guard against macOS `stat` throwing EBADF
29-
_IGNORED_ERRNOS = (ENOENT, ENOTDIR, EBADF, ELOOP)
30-
31-
_IGNORED_WINERRORS = (
32-
_WINERROR_NOT_READY,
33-
_WINERROR_INVALID_NAME,
34-
_WINERROR_CANT_RESOLVE_FILENAME)
35-
36-
def _ignore_error(exception):
37-
return (getattr(exception, 'errno', None) in _IGNORED_ERRNOS or
38-
getattr(exception, 'winerror', None) in _IGNORED_WINERRORS)
39-
4020

4121
@functools.cache
4222
def _is_case_sensitive(parser):
4323
return parser.normcase('Aa') == 'Aa'
4424

4525

4626
class Globber(glob._Globber):
47-
lstat = operator.methodcaller('lstat')
27+
lexists = operator.methodcaller('exists', follow_symlinks=False)
4828
add_slash = operator.methodcaller('joinpath', '')
4929

5030
@staticmethod
@@ -472,12 +452,7 @@ def exists(self, *, follow_symlinks=True):
472452
"""
473453
try:
474454
self.stat(follow_symlinks=follow_symlinks)
475-
except OSError as e:
476-
if not _ignore_error(e):
477-
raise
478-
return False
479-
except ValueError:
480-
# Non-encodable path
455+
except (OSError, ValueError):
481456
return False
482457
return True
483458

@@ -487,14 +462,7 @@ def is_dir(self, *, follow_symlinks=True):
487462
"""
488463
try:
489464
return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
490-
except OSError as e:
491-
if not _ignore_error(e):
492-
raise
493-
# Path doesn't exist or is a broken symlink
494-
# (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ )
495-
return False
496-
except ValueError:
497-
# Non-encodable path
465+
except (OSError, ValueError):
498466
return False
499467

500468
def is_file(self, *, follow_symlinks=True):
@@ -504,14 +472,7 @@ def is_file(self, *, follow_symlinks=True):
504472
"""
505473
try:
506474
return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
507-
except OSError as e:
508-
if not _ignore_error(e):
509-
raise
510-
# Path doesn't exist or is a broken symlink
511-
# (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ )
512-
return False
513-
except ValueError:
514-
# Non-encodable path
475+
except (OSError, ValueError):
515476
return False
516477

517478
def is_mount(self):
@@ -540,13 +501,7 @@ def is_symlink(self):
540501
"""
541502
try:
542503
return S_ISLNK(self.lstat().st_mode)
543-
except OSError as e:
544-
if not _ignore_error(e):
545-
raise
546-
# Path doesn't exist
547-
return False
548-
except ValueError:
549-
# Non-encodable path
504+
except (OSError, ValueError):
550505
return False
551506

552507
def is_junction(self):
@@ -564,14 +519,7 @@ def is_block_device(self):
564519
"""
565520
try:
566521
return S_ISBLK(self.stat().st_mode)
567-
except OSError as e:
568-
if not _ignore_error(e):
569-
raise
570-
# Path doesn't exist or is a broken symlink
571-
# (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ )
572-
return False
573-
except ValueError:
574-
# Non-encodable path
522+
except (OSError, ValueError):
575523
return False
576524

577525
def is_char_device(self):
@@ -580,14 +528,7 @@ def is_char_device(self):
580528
"""
581529
try:
582530
return S_ISCHR(self.stat().st_mode)
583-
except OSError as e:
584-
if not _ignore_error(e):
585-
raise
586-
# Path doesn't exist or is a broken symlink
587-
# (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ )
588-
return False
589-
except ValueError:
590-
# Non-encodable path
531+
except (OSError, ValueError):
591532
return False
592533

593534
def is_fifo(self):
@@ -596,14 +537,7 @@ def is_fifo(self):
596537
"""
597538
try:
598539
return S_ISFIFO(self.stat().st_mode)
599-
except OSError as e:
600-
if not _ignore_error(e):
601-
raise
602-
# Path doesn't exist or is a broken symlink
603-
# (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ )
604-
return False
605-
except ValueError:
606-
# Non-encodable path
540+
except (OSError, ValueError):
607541
return False
608542

609543
def is_socket(self):
@@ -612,14 +546,7 @@ def is_socket(self):
612546
"""
613547
try:
614548
return S_ISSOCK(self.stat().st_mode)
615-
except OSError as e:
616-
if not _ignore_error(e):
617-
raise
618-
# Path doesn't exist or is a broken symlink
619-
# (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ )
620-
return False
621-
except ValueError:
622-
# Non-encodable path
549+
except (OSError, ValueError):
623550
return False
624551

625552
def samefile(self, other_path):

0 commit comments

Comments
 (0)