aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/pathlib/_abc.py
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2025-02-08 01:16:45 +0000
committerGitHub <noreply@github.com>2025-02-08 01:16:45 +0000
commit718ab662991214039626db432d60310e0e19a0ac (patch)
treefba4538dd72f0eadf6d1a9a93896df18ca73f43a /Lib/pathlib/_abc.py
parenta1417b211f0bb9582b00f7b82d0a43a3bcc9ed05 (diff)
downloadcpython-718ab662991214039626db432d60310e0e19a0ac.tar.gz
cpython-718ab662991214039626db432d60310e0e19a0ac.zip
GH-125413: Add `pathlib.Path.info` attribute (#127730)
Add `pathlib.Path.info` attribute, which stores an object implementing the `pathlib.types.PathInfo` protocol (also new). The object supports querying the file type and internally caching `os.stat()` results. Path objects generated by `Path.iterdir()` are initialised with status information from `os.DirEntry` objects, which is gleaned from scanning the parent directory. The `PathInfo` protocol has four methods: `exists()`, `is_dir()`, `is_file()` and `is_symlink()`.
Diffstat (limited to 'Lib/pathlib/_abc.py')
-rw-r--r--Lib/pathlib/_abc.py73
1 files changed, 29 insertions, 44 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index e498dc78e83..d20f04fc5b6 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -13,10 +13,9 @@ WritablePath.
import functools
import io
-import operator
import posixpath
from errno import EINVAL
-from glob import _GlobberBase, _no_recurse_symlinks
+from glob import _PathGlobber, _no_recurse_symlinks
from pathlib._os import copyfileobj
@@ -76,21 +75,6 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None,
raise TypeError(f"{cls.__name__} can't be opened with mode {mode!r}")
-class PathGlobber(_GlobberBase):
- """
- Class providing shell-style globbing for path objects.
- """
-
- lexists = operator.methodcaller('exists', follow_symlinks=False)
- add_slash = operator.methodcaller('joinpath', '')
- scandir = operator.methodcaller('_scandir')
-
- @staticmethod
- def concat_path(path, text):
- """Appends text to the given path."""
- return path.with_segments(str(path) + text)
-
-
class CopyReader:
"""
Class that implements the "read" part of copying between path objects.
@@ -367,7 +351,7 @@ class JoinablePath:
pattern = self.with_segments(pattern)
if case_sensitive is None:
case_sensitive = _is_case_sensitive(self.parser)
- globber = PathGlobber(pattern.parser.sep, case_sensitive, recursive=True)
+ globber = _PathGlobber(pattern.parser.sep, case_sensitive, recursive=True)
match = globber.compile(str(pattern))
return match(str(self)) is not None
@@ -388,6 +372,14 @@ class ReadablePath(JoinablePath):
"""
__slots__ = ()
+ @property
+ def info(self):
+ """
+ A PathInfo object that exposes the file type and other file attributes
+ of this path.
+ """
+ raise NotImplementedError
+
def exists(self, *, follow_symlinks=True):
"""
Whether this path exists.
@@ -395,26 +387,30 @@ class ReadablePath(JoinablePath):
This method normally follows symlinks; to check whether a symlink exists,
add the argument follow_symlinks=False.
"""
- raise NotImplementedError
+ info = self.joinpath().info
+ return info.exists(follow_symlinks=follow_symlinks)
def is_dir(self, *, follow_symlinks=True):
"""
Whether this path is a directory.
"""
- raise NotImplementedError
+ info = self.joinpath().info
+ return info.is_dir(follow_symlinks=follow_symlinks)
def is_file(self, *, follow_symlinks=True):
"""
Whether this path is a regular file (also True for symlinks pointing
to regular files).
"""
- raise NotImplementedError
+ info = self.joinpath().info
+ return info.is_file(follow_symlinks=follow_symlinks)
def is_symlink(self):
"""
Whether this path is a symbolic link.
"""
- raise NotImplementedError
+ info = self.joinpath().info
+ return info.is_symlink()
def __open_rb__(self, buffering=-1):
"""
@@ -437,15 +433,6 @@ class ReadablePath(JoinablePath):
with magic_open(self, mode='r', encoding=encoding, errors=errors, newline=newline) as f:
return f.read()
- def _scandir(self):
- """Yield os.DirEntry-like objects of the directory contents.
-
- The children are yielded in arbitrary order, and the
- special entries '.' and '..' are not included.
- """
- import contextlib
- return contextlib.nullcontext(self.iterdir())
-
def iterdir(self):
"""Yield path objects of the directory contents.
@@ -471,7 +458,7 @@ class ReadablePath(JoinablePath):
else:
case_pedantic = True
recursive = True if recurse_symlinks else _no_recurse_symlinks
- globber = PathGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
+ globber = _PathGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
select = globber.selector(parts)
return select(self)
@@ -498,18 +485,16 @@ class ReadablePath(JoinablePath):
if not top_down:
paths.append((path, dirnames, filenames))
try:
- with path._scandir() as entries:
- for entry in entries:
- name = entry.name
- try:
- if entry.is_dir(follow_symlinks=follow_symlinks):
- if not top_down:
- paths.append(path.joinpath(name))
- dirnames.append(name)
- else:
- filenames.append(name)
- except OSError:
- filenames.append(name)
+ for child in path.iterdir():
+ try:
+ if child.info.is_dir(follow_symlinks=follow_symlinks):
+ if not top_down:
+ paths.append(child)
+ dirnames.append(child.name)
+ else:
+ filenames.append(child.name)
+ except OSError:
+ filenames.append(child.name)
except OSError as error:
if on_error is not None:
on_error(error)