Skip to content

Commit 268387b

Browse files
committed
Sync from upstream
1 parent d43ace8 commit 268387b

File tree

11 files changed

+234
-159
lines changed

11 files changed

+234
-159
lines changed

CHANGES.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ Version History
44
Unreleased
55
----------
66

7-
- Nothing yet
7+
- Add ``vfspath()``, which returns the virtual filesystem path as a string.
8+
- Replace ``JoinablePath.__str__()`` abstract method with ``__vfspath__()``.
9+
- Rename ``magic_open()`` to ``vfsopen()``.
10+
- Replace ``ReadablePath.__open_rb__()`` abstract method with
11+
``__open_reader__()``, and remove *buffering* argument.
12+
- Replace ``WritablePath.__open_wb__()`` abstract method with
13+
``__open_writer__()``, and remove *buffering* and add *mode* arguments.
814

915
v0.4.3
1016
------

docs/api.rst

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ Functions
1010

1111
This package offers the following functions:
1212

13-
.. function:: magic_open(path, mode='r' buffering=-1, \
14-
encoding=None, errors=None, newline=None)
13+
.. function:: vfspath(obj)
1514

16-
Open the path and return a file object. Unlike the built-in ``open()``
17-
function, this function tries to call :meth:`!__open_r__`,
18-
:meth:`~ReadablePath.__open_rb__`, :meth:`!__open_w__`, and
19-
:meth:`~WritablePath.__open_wb__` methods on the given path object as
20-
appropriate for the given mode.
15+
Return the virtual filesystem path (a string) of the given object. Unlike
16+
the ``os.fspath()`` function, this function calls the object's
17+
:meth:`~JoinablePath.__vfspath__` method.
18+
19+
.. function:: vfsopen(obj, mode='r' buffering=-1, \
20+
encoding=None, errors=None, newline=None)
21+
22+
Open the given object and return a file object. Unlike the built-in
23+
``open()`` function, this function calls the object's
24+
:meth:`~ReadablePath.__open_reader__`,
25+
:meth:`~WritablePath.__open_writer__` or :meth:`!__open_updater__` method,
26+
as appropriate for the given mode.
2127

2228

2329
Protocols
@@ -136,7 +142,7 @@ This package offers the following abstract base classes:
136142
*
137143
* :attr:`~JoinablePath.parser`
138144

139-
:meth:`~JoinablePath.__str__`
145+
:meth:`~JoinablePath.__vfspath__`
140146

141147
:meth:`~JoinablePath.with_segments`
142148
* :attr:`~JoinablePath.parts`
@@ -164,7 +170,7 @@ This package offers the following abstract base classes:
164170
* :class:`JoinablePath`
165171
* :attr:`~ReadablePath.info`
166172

167-
:meth:`~ReadablePath.__open_rb__`
173+
:meth:`~ReadablePath.__open_reader__`
168174

169175
:meth:`~ReadablePath.iterdir`
170176

@@ -181,7 +187,7 @@ This package offers the following abstract base classes:
181187

182188
- * :class:`WritablePath`
183189
* :class:`JoinablePath`
184-
* :meth:`~WritablePath.__open_wb__`
190+
* :meth:`~WritablePath.__open_writer__`
185191

186192
:meth:`~WritablePath.mkdir`
187193

@@ -201,7 +207,7 @@ This package offers the following abstract base classes:
201207
(**Abstract attribute**.) Implementation of :class:`PathParser` used for
202208
low-level splitting and joining.
203209

204-
.. method:: __str__()
210+
.. method:: __vfspath__()
205211

206212
(**Abstract method**.) Return a string representation of the path,
207213
suitable for passing to methods of the :attr:`parser`.
@@ -302,7 +308,7 @@ This package offers the following abstract base classes:
302308
(**Abstract attribute**.) Implementation of :class:`PathInfo` that
303309
supports querying the file type.
304310

305-
.. method:: __open_rb__(buffering=-1)
311+
.. method:: __open_reader__()
306312

307313
(**Abstract method.**) Open the path for reading in binary mode, and
308314
return a file object.
@@ -318,12 +324,12 @@ This package offers the following abstract base classes:
318324
.. method:: read_bytes()
319325

320326
Return the binary contents of the path. The default implementation
321-
calls :meth:`__open_rb__`.
327+
calls :func:`vfsopen`.
322328

323329
.. method:: read_text(encoding=None, errors=None, newline=None)
324330

325-
Return the text contents of the path. The default implementation calls
326-
:meth:`!__open_r__` if it exists, falling back to :meth:`__open_rb__`.
331+
Return the text contents of the path. The default implementation
332+
calls :func:`vfsopen`.
327333

328334
.. method:: copy(target, **kwargs)
329335

@@ -364,10 +370,10 @@ This package offers the following abstract base classes:
364370
Abstract base class for path objects with support for writing data. This
365371
is a subclass of :class:`JoinablePath`
366372

367-
.. method:: __open_wb__(buffering=-1)
373+
.. method:: __open_writer__(mode)
368374

369375
(**Abstract method**.) Open the path for writing in binary mode, and
370-
return a file object.
376+
return a file object. The mode is either ``'w'`` or ``'a'``.
371377

372378
.. method:: mkdir()
373379

@@ -381,20 +387,19 @@ This package offers the following abstract base classes:
381387
.. method:: write_bytes(data)
382388

383389
Write the given binary data to the path, and return the number of bytes
384-
written. The default implementation calls :meth:`__open_wb__`.
390+
written. The default implementation calls :func:`vfsopen`.
385391

386392
.. method:: write_text(data, encoding=None, errors=None, newline=None)
387393

388394
Write the given text data to the path, and return the number of bytes
389-
written. The default implementation calls :meth:`!__open_w__` if it
390-
exists, falling back to :meth:`__open_wb__`.
395+
written. The default implementation calls :func:`vfsopen`.
391396

392397
.. method:: _copy_from(source, *, follow_symlinks=True)
393398

394399
Copy the path from the given source, which should be an instance of
395400
:class:`ReadablePath`. The default implementation uses
396401
:attr:`ReadablePath.info` to establish the type of the source path. It
397-
uses :meth:`~ReadablePath.__open_rb__` and :meth:`__open_wb__` to copy
398-
regular files; :meth:`~ReadablePath.iterdir` and :meth:`mkdir` to copy
399-
directories; and :meth:`~ReadablePath.readlink` and :meth:`symlink_to`
400-
to copy symlinks when *follow_symlinks* is false.
402+
uses :func:`vfsopen` to copy regular files;
403+
:meth:`~ReadablePath.iterdir` and :meth:`mkdir` to copy directories; and
404+
:meth:`~ReadablePath.readlink` and :meth:`symlink_to` to copy symlinks
405+
when *follow_symlinks* is false.

pathlib_abc/__init__.py

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212

1313
from abc import ABC, abstractmethod
14-
from pathlib_abc._glob import _PathGlobber
15-
from pathlib_abc._os import magic_open, ensure_distinct_paths, ensure_different_files, copyfileobj
14+
from pathlib_abc._glob import _GlobberBase
15+
from pathlib_abc._os import (
16+
copyfileobj, ensure_different_files,
17+
ensure_distinct_paths, vfsopen, vfspath)
1618
from typing import Optional, Protocol, runtime_checkable
1719
try:
1820
from io import text_encoding
@@ -66,6 +68,25 @@ def is_file(self, *, follow_symlinks: bool = True) -> bool: ...
6668
def is_symlink(self) -> bool: ...
6769

6870

71+
class _PathGlobber(_GlobberBase):
72+
"""Provides shell-style pattern matching and globbing for ReadablePath.
73+
"""
74+
75+
@staticmethod
76+
def lexists(path):
77+
return path.info.exists(follow_symlinks=False)
78+
79+
@staticmethod
80+
def scandir(path):
81+
return ((child.info, child.name, child) for child in path.iterdir())
82+
83+
@staticmethod
84+
def concat_path(path, text):
85+
return path.with_segments(vfspath(path) + text)
86+
87+
stringify_path = staticmethod(vfspath)
88+
89+
6990
class JoinablePath(ABC):
7091
"""Abstract base class for pure path objects.
7192
@@ -92,20 +113,19 @@ def with_segments(self, *pathsegments):
92113
raise NotImplementedError
93114

94115
@abstractmethod
95-
def __str__(self):
96-
"""Return the string representation of the path, suitable for
97-
passing to system calls."""
116+
def __vfspath__(self):
117+
"""Return the string representation of the path."""
98118
raise NotImplementedError
99119

100120
@property
101121
def anchor(self):
102122
"""The concatenation of the drive and root, or ''."""
103-
return _explode_path(str(self), self.parser.split)[0]
123+
return _explode_path(vfspath(self), self.parser.split)[0]
104124

105125
@property
106126
def name(self):
107127
"""The final path component, if any."""
108-
return self.parser.split(str(self))[1]
128+
return self.parser.split(vfspath(self))[1]
109129

110130
@property
111131
def suffix(self):
@@ -141,7 +161,7 @@ def with_name(self, name):
141161
split = self.parser.split
142162
if split(name)[0]:
143163
raise ValueError(f"Invalid name {name!r}")
144-
path = str(self)
164+
path = vfspath(self)
145165
path = path.removesuffix(split(path)[1]) + name
146166
return self.with_segments(path)
147167

@@ -174,7 +194,7 @@ def with_suffix(self, suffix):
174194
def parts(self):
175195
"""An object providing sequence-like access to the
176196
components in the filesystem path."""
177-
anchor, parts = _explode_path(str(self), self.parser.split)
197+
anchor, parts = _explode_path(vfspath(self), self.parser.split)
178198
if anchor:
179199
parts.append(anchor)
180200
return tuple(reversed(parts))
@@ -185,24 +205,24 @@ def joinpath(self, *pathsegments):
185205
paths) or a totally different path (if one of the arguments is
186206
anchored).
187207
"""
188-
return self.with_segments(str(self), *pathsegments)
208+
return self.with_segments(vfspath(self), *pathsegments)
189209

190210
def __truediv__(self, key):
191211
try:
192-
return self.with_segments(str(self), key)
212+
return self.with_segments(vfspath(self), key)
193213
except TypeError:
194214
return NotImplemented
195215

196216
def __rtruediv__(self, key):
197217
try:
198-
return self.with_segments(key, str(self))
218+
return self.with_segments(key, vfspath(self))
199219
except TypeError:
200220
return NotImplemented
201221

202222
@property
203223
def parent(self):
204224
"""The logical parent of the path."""
205-
path = str(self)
225+
path = vfspath(self)
206226
parent = self.parser.split(path)[0]
207227
if path != parent:
208228
return self.with_segments(parent)
@@ -212,7 +232,7 @@ def parent(self):
212232
def parents(self):
213233
"""A sequence of this path's logical parents."""
214234
split = self.parser.split
215-
path = str(self)
235+
path = vfspath(self)
216236
parent = split(path)[0]
217237
parents = []
218238
while path != parent:
@@ -229,7 +249,7 @@ def full_match(self, pattern):
229249
case_sensitive = self.parser.normcase('Aa') == 'Aa'
230250
globber = _PathGlobber(self.parser.sep, case_sensitive, recursive=True)
231251
match = globber.compile(pattern, altsep=self.parser.altsep)
232-
return match(str(self)) is not None
252+
return match(vfspath(self)) is not None
233253

234254

235255
class ReadablePath(JoinablePath):
@@ -251,18 +271,18 @@ def info(self):
251271
raise NotImplementedError
252272

253273
@abstractmethod
254-
def __open_rb__(self, buffering=-1):
274+
def __open_reader__(self):
255275
"""
256276
Open the file pointed to by this path for reading in binary mode and
257-
return a file object, like open(mode='rb').
277+
return a file object.
258278
"""
259279
raise NotImplementedError
260280

261281
def read_bytes(self):
262282
"""
263283
Open the file in bytes mode, read it, and close the file.
264284
"""
265-
with magic_open(self, mode='rb', buffering=0) as f:
285+
with vfsopen(self, mode='rb') as f:
266286
return f.read()
267287

268288
def read_text(self, encoding=None, errors=None, newline=None):
@@ -272,7 +292,7 @@ def read_text(self, encoding=None, errors=None, newline=None):
272292
# Call io.text_encoding() here to ensure any warning is raised at an
273293
# appropriate stack level.
274294
encoding = text_encoding(encoding)
275-
with magic_open(self, mode='r', encoding=encoding, errors=errors, newline=newline) as f:
295+
with vfsopen(self, mode='r', encoding=encoding, errors=errors, newline=newline) as f:
276296
return f.read()
277297

278298
@abstractmethod
@@ -381,10 +401,10 @@ def mkdir(self):
381401
raise NotImplementedError
382402

383403
@abstractmethod
384-
def __open_wb__(self, buffering=-1):
404+
def __open_writer__(self, mode):
385405
"""
386406
Open the file pointed to by this path for writing in binary mode and
387-
return a file object, like open(mode='wb').
407+
return a file object.
388408
"""
389409
raise NotImplementedError
390410

@@ -394,7 +414,7 @@ def write_bytes(self, data):
394414
"""
395415
# type-check for the buffer interface before truncating the file
396416
view = memoryview(data)
397-
with magic_open(self, mode='wb') as f:
417+
with vfsopen(self, mode='wb') as f:
398418
return f.write(view)
399419

400420
def write_text(self, data, encoding=None, errors=None, newline=None):
@@ -407,7 +427,7 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
407427
if not isinstance(data, str):
408428
raise TypeError('data must be str, not %s' %
409429
data.__class__.__name__)
410-
with magic_open(self, mode='w', encoding=encoding, errors=errors, newline=newline) as f:
430+
with vfsopen(self, mode='w', encoding=encoding, errors=errors, newline=newline) as f:
411431
return f.write(data)
412432

413433
def _copy_from(self, source, follow_symlinks=True):
@@ -418,16 +438,16 @@ def _copy_from(self, source, follow_symlinks=True):
418438
while stack:
419439
src, dst = stack.pop()
420440
if not follow_symlinks and src.info.is_symlink():
421-
dst.symlink_to(str(src.readlink()), src.info.is_dir())
441+
dst.symlink_to(vfspath(src.readlink()), src.info.is_dir())
422442
elif src.info.is_dir():
423443
children = src.iterdir()
424444
dst.mkdir()
425445
for child in children:
426446
stack.append((child, dst.joinpath(child.name)))
427447
else:
428448
ensure_different_files(src, dst)
429-
with magic_open(src, 'rb') as source_f:
430-
with magic_open(dst, 'wb') as target_f:
449+
with vfsopen(src, 'rb') as source_f:
450+
with vfsopen(dst, 'wb') as target_f:
431451
copyfileobj(source_f, target_f)
432452

433453

0 commit comments

Comments
 (0)