GH-128520: Make pathlib._abc.WritablePath a sibling of ReadablePath (#129014)

In the private pathlib ABCs, support write-only virtual filesystems by
making `WritablePath` inherit directly from `JoinablePath`, rather than
subclassing `ReadablePath`.

There are two complications:

- `ReadablePath.open()` applies to both reading and writing
- `ReadablePath.copy` is secretly an object that supports the *read* side
  of copying, whereas `WritablePath.copy` is a different kind of object
  supporting the *write* side

We untangle these as follow:

- A new `pathlib._abc.magic_open()` function replaces the `open()` method,
  which is dropped from the ABCs but remains in `pathlib.Path`. The
  function works like `io.open()`, but additionally accepts objects with
  `__open_rb__()` or `__open_wb__()` methods as appropriate for the mode.
  These new dunders are made abstract methods of `ReadablePath` and
  `WritablePath` respectively.  If the pathlib ABCs are made public, we
  could consider blessing an "openable" protocol and supporting it in
  `io.open()`, removing the need for `pathlib._abc.magic_open()`.
- `ReadablePath.copy` becomes a true method, whereas `WritablePath.copy` is
  deleted. A new `ReadablePath._copy_reader` property provides a
  `CopyReader` object, and similarly `WritablePath._copy_writer` is a
  `CopyWriter` object. Once GH-125413 is resolved, we'll be able to move
  the `CopyReader` functionality into `ReadablePath.info` and eliminate
  `ReadablePath._copy_reader`.
This commit is contained in:
Barney Gale 2025-01-21 18:35:37 +00:00 committed by GitHub
parent 3d7c0e5366
commit 01d91500ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 178 additions and 115 deletions

View file

@ -924,7 +924,7 @@ class PurePathSubclassTest(PurePathTest):
# Tests for the concrete classes.
#
class PathTest(test_pathlib_abc.DummyWritablePathTest, PurePathTest):
class PathTest(test_pathlib_abc.DummyRWPathTest, PurePathTest):
"""Tests for the FS-accessing functionalities of the Path classes."""
cls = pathlib.Path
can_symlink = os_helper.can_symlink()
@ -1102,6 +1102,15 @@ class PathTest(test_pathlib_abc.DummyWritablePathTest, PurePathTest):
for dirpath, dirnames, filenames in p.walk():
self.assertEqual(42, dirpath.session_id)
def test_open_common(self):
p = self.cls(self.base)
with (p / 'fileA').open('r') as f:
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.read(), "this is file A\n")
with (p / 'fileA').open('rb') as f:
self.assertIsInstance(f, io.BufferedIOBase)
self.assertEqual(f.read().strip(), b"this is file A")
def test_open_unbuffered(self):
p = self.cls(self.base)
with (p / 'fileA').open('rb', buffering=0) as f: