Skip to content

Commit 0b88e8b

Browse files
committed
pythonGH-139174: Prepare pathlib.Path.info for new methods
Merge `_PathInfoBase`, `_WindowsPathInfo`, `_PosixPathInfo` and `DirEntryInfo` classes into a single `LocalPathInfo` class. Abandon use of `os.path.exists()` etc on Windows, and switch to using `stat()` like the Posix implementation; this might be a little bit slower but there's value in making the code more easily maintainable by someone other than myself!
1 parent 3eec897 commit 0b88e8b

2 files changed

Lines changed: 82 additions & 149 deletions

File tree

Lib/pathlib/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
grp = None
2828

2929
from pathlib._os import (
30-
PathInfo, DirEntryInfo,
30+
LocalPathInfo,
3131
vfsopen, vfspath,
3232
ensure_different_files, ensure_distinct_paths,
3333
copyfile2, copyfileobj, copy_info,
@@ -637,7 +637,7 @@ def info(self):
637637
try:
638638
return self._info
639639
except AttributeError:
640-
self._info = PathInfo(self)
640+
self._info = LocalPathInfo(str(self))
641641
return self._info
642642

643643
def stat(self, *, follow_symlinks=True):
@@ -817,7 +817,7 @@ def _filter_trailing_slash(self, paths):
817817
def _from_dir_entry(self, dir_entry, path_str):
818818
path = self.with_segments(path_str)
819819
path._str = path_str
820-
path._info = DirEntryInfo(dir_entry)
820+
path._info = LocalPathInfo(path_str, dir_entry)
821821
return path
822822

823823
def iterdir(self):

Lib/pathlib/_os.py

Lines changed: 79 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -363,47 +363,46 @@ def copy_info(info, target, follow_symlinks=True):
363363
raise
364364

365365

366-
class _PathInfoBase:
367-
__slots__ = ('_path', '_stat_result', '_lstat_result')
366+
_STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed.
368367

369-
def __init__(self, path):
370-
self._path = str(path)
368+
369+
class LocalPathInfo:
370+
"""Implementation of pathlib.types.PathInfo that provides information
371+
about local filesystem paths. Don't try to construct it yourself."""
372+
__slots__ = ('_path', '_dir_entry', '_stat_result', '_lstat_result')
373+
374+
def __init__(self, path, dir_entry=None):
375+
self._path = path
376+
self._dir_entry = dir_entry
377+
self._stat_result = None
378+
self._lstat_result = None
371379

372380
def __repr__(self):
373381
path_type = "WindowsPath" if os.name == "nt" else "PosixPath"
374382
return f"<{path_type}.info>"
375383

376-
def _stat(self, *, follow_symlinks=True, ignore_errors=False):
377-
"""Return the status as an os.stat_result, or None if stat() fails and
378-
ignore_errors is true."""
384+
def _stat(self, *, follow_symlinks=True):
385+
"""Return the status as an os.stat_result."""
379386
if follow_symlinks:
380-
try:
381-
result = self._stat_result
382-
except AttributeError:
383-
pass
384-
else:
385-
if ignore_errors or result is not None:
386-
return result
387-
try:
388-
self._stat_result = os.stat(self._path)
389-
except (OSError, ValueError):
390-
self._stat_result = None
391-
if not ignore_errors:
387+
if not self._stat_result:
388+
try:
389+
if self._dir_entry:
390+
self._stat_result = self._dir_entry.stat()
391+
else:
392+
self._stat_result = os.stat(self._path)
393+
except (OSError, ValueError):
394+
self._stat_result = _STAT_RESULT_ERROR
392395
raise
393396
return self._stat_result
394397
else:
395-
try:
396-
result = self._lstat_result
397-
except AttributeError:
398-
pass
399-
else:
400-
if ignore_errors or result is not None:
401-
return result
402-
try:
403-
self._lstat_result = os.lstat(self._path)
404-
except (OSError, ValueError):
405-
self._lstat_result = None
406-
if not ignore_errors:
398+
if not self._lstat_result:
399+
try:
400+
if self._dir_entry:
401+
self._stat_result = self._dir_entry.stat(follow_symlinks=False)
402+
else:
403+
self._lstat_result = os.lstat(self._path)
404+
except (OSError, ValueError):
405+
self._lstat_result = _STAT_RESULT_ERROR
407406
raise
408407
return self._lstat_result
409408

@@ -442,141 +441,75 @@ def _xattrs(self, *, follow_symlinks=True):
442441
raise
443442
return []
444443

445-
446-
class _WindowsPathInfo(_PathInfoBase):
447-
"""Implementation of pathlib.types.PathInfo that provides status
448-
information for Windows paths. Don't try to construct it yourself."""
449-
__slots__ = ('_exists', '_is_dir', '_is_file', '_is_symlink')
450-
451444
def exists(self, *, follow_symlinks=True):
452445
"""Whether this path exists."""
453-
if not follow_symlinks and self.is_symlink():
454-
return True
455-
try:
456-
return self._exists
457-
except AttributeError:
458-
if os.path.exists(self._path):
459-
self._exists = True
460-
return True
461-
else:
462-
self._exists = self._is_dir = self._is_file = False
446+
if follow_symlinks:
447+
if self._stat_result is _STAT_RESULT_ERROR:
463448
return False
464-
465-
def is_dir(self, *, follow_symlinks=True):
466-
"""Whether this path is a directory."""
467-
if not follow_symlinks and self.is_symlink():
468-
return False
469-
try:
470-
return self._is_dir
471-
except AttributeError:
472-
if os.path.isdir(self._path):
473-
self._is_dir = self._exists = True
474-
return True
475-
else:
476-
self._is_dir = False
449+
else:
450+
if self._lstat_result is _STAT_RESULT_ERROR:
477451
return False
478-
479-
def is_file(self, *, follow_symlinks=True):
480-
"""Whether this path is a regular file."""
481-
if not follow_symlinks and self.is_symlink():
482-
return False
483-
try:
484-
return self._is_file
485-
except AttributeError:
486-
if os.path.isfile(self._path):
487-
self._is_file = self._exists = True
452+
elif self._dir_entry:
488453
return True
489-
else:
490-
self._is_file = False
491-
return False
492-
493-
def is_symlink(self):
494-
"""Whether this path is a symbolic link."""
495454
try:
496-
return self._is_symlink
497-
except AttributeError:
498-
self._is_symlink = os.path.islink(self._path)
499-
return self._is_symlink
500-
501-
502-
class _PosixPathInfo(_PathInfoBase):
503-
"""Implementation of pathlib.types.PathInfo that provides status
504-
information for POSIX paths. Don't try to construct it yourself."""
505-
__slots__ = ()
506-
507-
def exists(self, *, follow_symlinks=True):
508-
"""Whether this path exists."""
509-
st = self._stat(follow_symlinks=follow_symlinks, ignore_errors=True)
510-
if st is None:
511-
return False
512-
return True
513-
514-
def is_dir(self, *, follow_symlinks=True):
515-
"""Whether this path is a directory."""
516-
st = self._stat(follow_symlinks=follow_symlinks, ignore_errors=True)
517-
if st is None:
518-
return False
519-
return S_ISDIR(st.st_mode)
520-
521-
def is_file(self, *, follow_symlinks=True):
522-
"""Whether this path is a regular file."""
523-
st = self._stat(follow_symlinks=follow_symlinks, ignore_errors=True)
524-
if st is None:
525-
return False
526-
return S_ISREG(st.st_mode)
527-
528-
def is_symlink(self):
529-
"""Whether this path is a symbolic link."""
530-
st = self._stat(follow_symlinks=False, ignore_errors=True)
531-
if st is None:
455+
self._stat(follow_symlinks=follow_symlinks)
456+
except (OSError, ValueError):
532457
return False
533-
return S_ISLNK(st.st_mode)
534-
535-
536-
PathInfo = _WindowsPathInfo if os.name == 'nt' else _PosixPathInfo
537-
538-
539-
class DirEntryInfo(_PathInfoBase):
540-
"""Implementation of pathlib.types.PathInfo that provides status
541-
information by querying a wrapped os.DirEntry object. Don't try to
542-
construct it yourself."""
543-
__slots__ = ('_entry',)
544-
545-
def __init__(self, entry):
546-
super().__init__(entry.path)
547-
self._entry = entry
548-
549-
def _stat(self, *, follow_symlinks=True, ignore_errors=False):
550-
try:
551-
return self._entry.stat(follow_symlinks=follow_symlinks)
552-
except OSError:
553-
if not ignore_errors:
554-
raise
555-
return None
556-
557-
def exists(self, *, follow_symlinks=True):
558-
"""Whether this path exists."""
559-
if not follow_symlinks:
458+
else:
560459
return True
561-
return self._stat(ignore_errors=True) is not None
562460

563461
def is_dir(self, *, follow_symlinks=True):
564462
"""Whether this path is a directory."""
463+
if self._dir_entry:
464+
try:
465+
return self._dir_entry.is_dir(follow_symlinks=follow_symlinks)
466+
except OSError:
467+
return False
468+
elif follow_symlinks:
469+
if self._stat_result is _STAT_RESULT_ERROR:
470+
return False
471+
else:
472+
if self._lstat_result is _STAT_RESULT_ERROR:
473+
return False
565474
try:
566-
return self._entry.is_dir(follow_symlinks=follow_symlinks)
567-
except OSError:
475+
st = self._stat(follow_symlinks=follow_symlinks)
476+
except (OSError, ValueError):
568477
return False
478+
else:
479+
return S_ISDIR(st.st_mode)
569480

570481
def is_file(self, *, follow_symlinks=True):
571482
"""Whether this path is a regular file."""
483+
if self._dir_entry:
484+
try:
485+
return self._dir_entry.is_file(follow_symlinks=follow_symlinks)
486+
except OSError:
487+
return False
488+
elif follow_symlinks:
489+
if self._stat_result is _STAT_RESULT_ERROR:
490+
return False
491+
else:
492+
if self._lstat_result is _STAT_RESULT_ERROR:
493+
return False
572494
try:
573-
return self._entry.is_file(follow_symlinks=follow_symlinks)
574-
except OSError:
495+
st = self._stat(follow_symlinks=follow_symlinks)
496+
except (OSError, ValueError):
575497
return False
498+
else:
499+
return S_ISREG(st.st_mode)
576500

577501
def is_symlink(self):
578502
"""Whether this path is a symbolic link."""
503+
if self._dir_entry:
504+
try:
505+
return self._dir_entry.is_symlink()
506+
except OSError:
507+
return False
508+
elif self._lstat_result is _STAT_RESULT_ERROR:
509+
return False
579510
try:
580-
return self._entry.is_symlink()
581-
except OSError:
511+
st = self._stat(follow_symlinks=False)
512+
except (OSError, ValueError):
582513
return False
514+
else:
515+
return S_ISLNK(st.st_mode)

0 commit comments

Comments
 (0)