1111
1212
1313from 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 )
1618from typing import Optional , Protocol , runtime_checkable
1719try :
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+
6990class 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
235255class 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