diff options
author | Barney Gale <barney.gale@gmail.com> | 2025-01-11 19:27:47 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-11 19:27:47 +0000 |
commit | 22a442181d5f1ac496dac08e24fd852de404882f (patch) | |
tree | eba5ef2eb665ab76911750ac5286a035ff29d729 /Lib/pathlib/_abc.py | |
parent | 0946ed25b53dddfa4eb040513720353b7214d71b (diff) | |
download | cpython-22a442181d5f1ac496dac08e24fd852de404882f.tar.gz cpython-22a442181d5f1ac496dac08e24fd852de404882f.zip |
GH-128520: Divide pathlib ABCs into three classes (#128523)
In the private pathlib ABCs, rename `PurePathBase` to `JoinablePath`, and
split `PathBase` into `ReadablePath` and `WritablePath`. This improves the
API fit for read-only virtual filesystems.
The split of `PathBase` entails a similar split of `CopyWorker` (implements
copying) and the test cases in `test_pathlib_abc`.
In a later patch, we'll make `WritablePath` inherit directly from
`JoinablePath` rather than `ReadablePath`. For a couple of reasons,
this isn't quite possible yet.
Diffstat (limited to 'Lib/pathlib/_abc.py')
-rw-r--r-- | Lib/pathlib/_abc.py | 110 |
1 files changed, 62 insertions, 48 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 7de2bb066f8..38bc660e0ae 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -7,8 +7,8 @@ This module is also a *PRIVATE* part of the Python standard library, where it's developed alongside pathlib. If it finds success and maturity as a PyPI package, it could become a public part of the standard library. -Two base classes are defined here -- PurePathBase and PathBase -- that -resemble pathlib's PurePath and Path respectively. +Three base classes are defined here -- JoinablePath, ReadablePath and +WritablePath. """ import functools @@ -56,13 +56,13 @@ class PathGlobber(_GlobberBase): return path.with_segments(str(path) + text) -class CopyWorker: +class CopyReader: """ Class that implements copying between path objects. An instance of this - class is available from the PathBase.copy property; it's made callable so - that PathBase.copy() can be treated as a method. + class is available from the ReadablePath.copy property; it's made callable + so that ReadablePath.copy() can be treated as a method. - The target path's CopyWorker drives the process from its _create() method. + The target path's CopyWriter drives the process from its _create() method. Files and directories are exchanged by calling methods on the source and target paths, and metadata is exchanged by calling source.copy._read_metadata() and target.copy._write_metadata(). @@ -77,11 +77,15 @@ class CopyWorker: """ Recursively copy this file or directory tree to the given destination. """ - if not isinstance(target, PathBase): + if not isinstance(target, ReadablePath): target = self._path.with_segments(target) - # Delegate to the target path's CopyWorker object. - return target.copy._create(self._path, follow_symlinks, dirs_exist_ok, preserve_metadata) + # Delegate to the target path's CopyWriter object. + try: + create = target.copy._create + except AttributeError: + raise TypeError(f"Target is not writable: {target}") from None + return create(self._path, follow_symlinks, dirs_exist_ok, preserve_metadata) _readable_metakeys = frozenset() @@ -91,6 +95,10 @@ class CopyWorker: """ raise NotImplementedError + +class CopyWriter(CopyReader): + __slots__ = () + _writable_metakeys = frozenset() def _write_metadata(self, metadata, *, follow_symlinks=True): @@ -182,7 +190,7 @@ class CopyWorker: raise err -class PurePathBase: +class JoinablePath: """Base class for pure path objects. This class *does not* provide several magic methods that are defined in @@ -334,7 +342,7 @@ class PurePathBase: is matched. The recursive wildcard '**' is *not* supported by this method. """ - if not isinstance(path_pattern, PurePathBase): + if not isinstance(path_pattern, JoinablePath): path_pattern = self.with_segments(path_pattern) if case_sensitive is None: case_sensitive = _is_case_sensitive(self.parser) @@ -359,7 +367,7 @@ class PurePathBase: Return True if this path matches the given glob-style pattern. The pattern is matched against the entire path. """ - if not isinstance(pattern, PurePathBase): + if not isinstance(pattern, JoinablePath): pattern = self.with_segments(pattern) if case_sensitive is None: case_sensitive = _is_case_sensitive(self.parser) @@ -369,7 +377,7 @@ class PurePathBase: -class PathBase(PurePathBase): +class ReadablePath(JoinablePath): """Base class for concrete path objects. This class provides dummy implementations for many methods that derived @@ -434,25 +442,6 @@ class PathBase(PurePathBase): with self.open(mode='r', encoding=encoding, errors=errors, newline=newline) as f: return f.read() - def write_bytes(self, data): - """ - Open the file in bytes mode, write to it, and close the file. - """ - # type-check for the buffer interface before truncating the file - view = memoryview(data) - with self.open(mode='wb') as f: - return f.write(view) - - def write_text(self, data, encoding=None, errors=None, newline=None): - """ - Open the file in text mode, write to it, and close the file. - """ - if not isinstance(data, str): - raise TypeError('data must be str, not %s' % - data.__class__.__name__) - with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f: - return f.write(data) - def _scandir(self): """Yield os.DirEntry-like objects of the directory contents. @@ -474,7 +463,7 @@ class PathBase(PurePathBase): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given relative pattern. """ - if not isinstance(pattern, PurePathBase): + if not isinstance(pattern, JoinablePath): pattern = self.with_segments(pattern) anchor, parts = _explode_path(pattern) if anchor: @@ -496,7 +485,7 @@ class PathBase(PurePathBase): directories) matching the given relative pattern, anywhere in this subtree. """ - if not isinstance(pattern, PurePathBase): + if not isinstance(pattern, JoinablePath): pattern = self.with_segments(pattern) pattern = '**' / pattern return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks) @@ -543,6 +532,28 @@ class PathBase(PurePathBase): """ raise NotImplementedError + copy = property(CopyReader, doc=CopyReader.__call__.__doc__) + + def copy_into(self, target_dir, *, follow_symlinks=True, + dirs_exist_ok=False, preserve_metadata=False): + """ + Copy this file or directory tree into the given existing directory. + """ + name = self.name + if not name: + raise ValueError(f"{self!r} has an empty name") + elif isinstance(target_dir, ReadablePath): + target = target_dir / name + else: + target = self.with_segments(target_dir, name) + return self.copy(target, follow_symlinks=follow_symlinks, + dirs_exist_ok=dirs_exist_ok, + preserve_metadata=preserve_metadata) + + +class WritablePath(ReadablePath): + __slots__ = () + def symlink_to(self, target, target_is_directory=False): """ Make this path a symlink pointing to the target path. @@ -556,20 +567,23 @@ class PathBase(PurePathBase): """ raise NotImplementedError - copy = property(CopyWorker, doc=CopyWorker.__call__.__doc__) + def write_bytes(self, data): + """ + Open the file in bytes mode, write to it, and close the file. + """ + # type-check for the buffer interface before truncating the file + view = memoryview(data) + with self.open(mode='wb') as f: + return f.write(view) - def copy_into(self, target_dir, *, follow_symlinks=True, - dirs_exist_ok=False, preserve_metadata=False): + def write_text(self, data, encoding=None, errors=None, newline=None): """ - Copy this file or directory tree into the given existing directory. + Open the file in text mode, write to it, and close the file. """ - name = self.name - if not name: - raise ValueError(f"{self!r} has an empty name") - elif isinstance(target_dir, PathBase): - target = target_dir / name - else: - target = self.with_segments(target_dir, name) - return self.copy(target, follow_symlinks=follow_symlinks, - dirs_exist_ok=dirs_exist_ok, - preserve_metadata=preserve_metadata) + if not isinstance(data, str): + raise TypeError('data must be str, not %s' % + data.__class__.__name__) + with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f: + return f.write(data) + + copy = property(CopyWriter, doc=CopyWriter.__call__.__doc__) |