diff options
author | Barney Gale <barney.gale@gmail.com> | 2025-02-08 01:16:45 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-08 01:16:45 +0000 |
commit | 718ab662991214039626db432d60310e0e19a0ac (patch) | |
tree | fba4538dd72f0eadf6d1a9a93896df18ca73f43a /Lib/pathlib/_abc.py | |
parent | a1417b211f0bb9582b00f7b82d0a43a3bcc9ed05 (diff) | |
download | cpython-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.py | 73 |
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) |