7
7
it's developed alongside pathlib. If it finds success and maturity as a PyPI
8
8
package, it could become a public part of the standard library.
9
9
10
- Two base classes are defined here -- PurePathBase and PathBase -- that
11
- resemble pathlib's PurePath and Path respectively .
10
+ Three base classes are defined here -- JoinablePath, ReadablePath and
11
+ WritablePath .
12
12
"""
13
13
14
14
import functools
@@ -56,13 +56,13 @@ def concat_path(path, text):
56
56
return path .with_segments (str (path ) + text )
57
57
58
58
59
- class CopyWorker :
59
+ class CopyReader :
60
60
"""
61
61
Class that implements copying between path objects. An instance of this
62
- class is available from the PathBase .copy property; it's made callable so
63
- that PathBase .copy() can be treated as a method.
62
+ class is available from the ReadablePath .copy property; it's made callable
63
+ so that ReadablePath .copy() can be treated as a method.
64
64
65
- The target path's CopyWorker drives the process from its _create() method.
65
+ The target path's CopyWriter drives the process from its _create() method.
66
66
Files and directories are exchanged by calling methods on the source and
67
67
target paths, and metadata is exchanged by calling
68
68
source.copy._read_metadata() and target.copy._write_metadata().
@@ -77,11 +77,15 @@ def __call__(self, target, follow_symlinks=True, dirs_exist_ok=False,
77
77
"""
78
78
Recursively copy this file or directory tree to the given destination.
79
79
"""
80
- if not isinstance (target , PathBase ):
80
+ if not isinstance (target , ReadablePath ):
81
81
target = self ._path .with_segments (target )
82
82
83
- # Delegate to the target path's CopyWorker object.
84
- return target .copy ._create (self ._path , follow_symlinks , dirs_exist_ok , preserve_metadata )
83
+ # Delegate to the target path's CopyWriter object.
84
+ try :
85
+ create = target .copy ._create
86
+ except AttributeError :
87
+ raise TypeError (f"Target is not writable: { target } " ) from None
88
+ return create (self ._path , follow_symlinks , dirs_exist_ok , preserve_metadata )
85
89
86
90
_readable_metakeys = frozenset ()
87
91
@@ -91,6 +95,10 @@ def _read_metadata(self, metakeys, *, follow_symlinks=True):
91
95
"""
92
96
raise NotImplementedError
93
97
98
+
99
+ class CopyWriter (CopyReader ):
100
+ __slots__ = ()
101
+
94
102
_writable_metakeys = frozenset ()
95
103
96
104
def _write_metadata (self , metadata , * , follow_symlinks = True ):
@@ -182,7 +190,7 @@ def _ensure_distinct_path(self, source):
182
190
raise err
183
191
184
192
185
- class PurePathBase :
193
+ class JoinablePath :
186
194
"""Base class for pure path objects.
187
195
188
196
This class *does not* provide several magic methods that are defined in
@@ -334,7 +342,7 @@ def match(self, path_pattern, *, case_sensitive=None):
334
342
is matched. The recursive wildcard '**' is *not* supported by this
335
343
method.
336
344
"""
337
- if not isinstance (path_pattern , PurePathBase ):
345
+ if not isinstance (path_pattern , JoinablePath ):
338
346
path_pattern = self .with_segments (path_pattern )
339
347
if case_sensitive is None :
340
348
case_sensitive = _is_case_sensitive (self .parser )
@@ -359,7 +367,7 @@ def full_match(self, pattern, *, case_sensitive=None):
359
367
Return True if this path matches the given glob-style pattern. The
360
368
pattern is matched against the entire path.
361
369
"""
362
- if not isinstance (pattern , PurePathBase ):
370
+ if not isinstance (pattern , JoinablePath ):
363
371
pattern = self .with_segments (pattern )
364
372
if case_sensitive is None :
365
373
case_sensitive = _is_case_sensitive (self .parser )
@@ -369,7 +377,7 @@ def full_match(self, pattern, *, case_sensitive=None):
369
377
370
378
371
379
372
- class PathBase ( PurePathBase ):
380
+ class ReadablePath ( JoinablePath ):
373
381
"""Base class for concrete path objects.
374
382
375
383
This class provides dummy implementations for many methods that derived
@@ -434,25 +442,6 @@ def read_text(self, encoding=None, errors=None, newline=None):
434
442
with self .open (mode = 'r' , encoding = encoding , errors = errors , newline = newline ) as f :
435
443
return f .read ()
436
444
437
- def write_bytes (self , data ):
438
- """
439
- Open the file in bytes mode, write to it, and close the file.
440
- """
441
- # type-check for the buffer interface before truncating the file
442
- view = memoryview (data )
443
- with self .open (mode = 'wb' ) as f :
444
- return f .write (view )
445
-
446
- def write_text (self , data , encoding = None , errors = None , newline = None ):
447
- """
448
- Open the file in text mode, write to it, and close the file.
449
- """
450
- if not isinstance (data , str ):
451
- raise TypeError ('data must be str, not %s' %
452
- data .__class__ .__name__ )
453
- with self .open (mode = 'w' , encoding = encoding , errors = errors , newline = newline ) as f :
454
- return f .write (data )
455
-
456
445
def _scandir (self ):
457
446
"""Yield os.DirEntry-like objects of the directory contents.
458
447
@@ -474,7 +463,7 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
474
463
"""Iterate over this subtree and yield all existing files (of any
475
464
kind, including directories) matching the given relative pattern.
476
465
"""
477
- if not isinstance (pattern , PurePathBase ):
466
+ if not isinstance (pattern , JoinablePath ):
478
467
pattern = self .with_segments (pattern )
479
468
anchor , parts = _explode_path (pattern )
480
469
if anchor :
@@ -496,7 +485,7 @@ def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
496
485
directories) matching the given relative pattern, anywhere in
497
486
this subtree.
498
487
"""
499
- if not isinstance (pattern , PurePathBase ):
488
+ if not isinstance (pattern , JoinablePath ):
500
489
pattern = self .with_segments (pattern )
501
490
pattern = '**' / pattern
502
491
return self .glob (pattern , case_sensitive = case_sensitive , recurse_symlinks = recurse_symlinks )
@@ -543,6 +532,28 @@ def readlink(self):
543
532
"""
544
533
raise NotImplementedError
545
534
535
+ copy = property (CopyReader , doc = CopyReader .__call__ .__doc__ )
536
+
537
+ def copy_into (self , target_dir , * , follow_symlinks = True ,
538
+ dirs_exist_ok = False , preserve_metadata = False ):
539
+ """
540
+ Copy this file or directory tree into the given existing directory.
541
+ """
542
+ name = self .name
543
+ if not name :
544
+ raise ValueError (f"{ self !r} has an empty name" )
545
+ elif isinstance (target_dir , ReadablePath ):
546
+ target = target_dir / name
547
+ else :
548
+ target = self .with_segments (target_dir , name )
549
+ return self .copy (target , follow_symlinks = follow_symlinks ,
550
+ dirs_exist_ok = dirs_exist_ok ,
551
+ preserve_metadata = preserve_metadata )
552
+
553
+
554
+ class WritablePath (ReadablePath ):
555
+ __slots__ = ()
556
+
546
557
def symlink_to (self , target , target_is_directory = False ):
547
558
"""
548
559
Make this path a symlink pointing to the target path.
@@ -556,20 +567,23 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
556
567
"""
557
568
raise NotImplementedError
558
569
559
- copy = property (CopyWorker , doc = CopyWorker .__call__ .__doc__ )
570
+ def write_bytes (self , data ):
571
+ """
572
+ Open the file in bytes mode, write to it, and close the file.
573
+ """
574
+ # type-check for the buffer interface before truncating the file
575
+ view = memoryview (data )
576
+ with self .open (mode = 'wb' ) as f :
577
+ return f .write (view )
560
578
561
- def copy_into (self , target_dir , * , follow_symlinks = True ,
562
- dirs_exist_ok = False , preserve_metadata = False ):
579
+ def write_text (self , data , encoding = None , errors = None , newline = None ):
563
580
"""
564
- Copy this file or directory tree into the given existing directory .
581
+ Open the file in text mode, write to it, and close the file .
565
582
"""
566
- name = self .name
567
- if not name :
568
- raise ValueError (f"{ self !r} has an empty name" )
569
- elif isinstance (target_dir , PathBase ):
570
- target = target_dir / name
571
- else :
572
- target = self .with_segments (target_dir , name )
573
- return self .copy (target , follow_symlinks = follow_symlinks ,
574
- dirs_exist_ok = dirs_exist_ok ,
575
- preserve_metadata = preserve_metadata )
583
+ if not isinstance (data , str ):
584
+ raise TypeError ('data must be str, not %s' %
585
+ data .__class__ .__name__ )
586
+ with self .open (mode = 'w' , encoding = encoding , errors = errors , newline = newline ) as f :
587
+ return f .write (data )
588
+
589
+ copy = property (CopyWriter , doc = CopyWriter .__call__ .__doc__ )
0 commit comments