1212""" 
1313
1414import  functools 
15+ import  io 
1516import  operator 
1617import  posixpath 
1718from  errno  import  EINVAL 
@@ -41,6 +42,40 @@ def _explode_path(path):
4142    return  path , names 
4243
4344
45+ def  magic_open (path , mode = 'r' , buffering = - 1 , encoding = None , errors = None ,
46+                newline = None ):
47+     """ 
48+     Open the file pointed to by this path and return a file object, as 
49+     the built-in open() function does. 
50+     """ 
51+     try :
52+         return  io .open (path , mode , buffering , encoding , errors , newline )
53+     except  TypeError :
54+         pass 
55+     cls  =  type (path )
56+     text  =  'b'  not  in mode 
57+     mode  =  '' .join (sorted (c  for  c  in  mode  if  c  not  in 'bt' ))
58+     if  text :
59+         try :
60+             attr  =  getattr (cls , f'__open_{ mode }  )
61+         except  AttributeError :
62+             pass 
63+         else :
64+             return  attr (path , buffering , encoding , errors , newline )
65+ 
66+     try :
67+         attr  =  getattr (cls , f'__open_{ mode }  )
68+     except  AttributeError :
69+         pass 
70+     else :
71+         stream  =  attr (path , buffering )
72+         if  text :
73+             stream  =  io .TextIOWrapper (stream , encoding , errors , newline )
74+         return  stream 
75+ 
76+     raise  TypeError (f"{ cls .__name__ } { mode !r}  )
77+ 
78+ 
4479class  PathGlobber (_GlobberBase ):
4580    """ 
4681    Class providing shell-style globbing for path objects. 
@@ -58,35 +93,15 @@ def concat_path(path, text):
5893
5994class  CopyReader :
6095    """ 
61-     Class that implements copying between path objects. An instance of this 
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- 
65-     The target path's CopyWriter drives the process from its _create() method. 
66-     Files and directories are exchanged by calling methods on the source and 
67-     target paths, and metadata is exchanged by calling 
68-     source.copy._read_metadata() and target.copy._write_metadata(). 
96+     Class that implements the "read" part of copying between path objects. 
97+     An instance of this class is available from the ReadablePath._copy_reader 
98+     property. 
6999    """ 
70100    __slots__  =  ('_path' ,)
71101
72102    def  __init__ (self , path ):
73103        self ._path  =  path 
74104
75-     def  __call__ (self , target , follow_symlinks = True , dirs_exist_ok = False ,
76-              preserve_metadata = False ):
77-         """ 
78-         Recursively copy this file or directory tree to the given destination. 
79-         """ 
80-         if  not  isinstance (target , ReadablePath ):
81-             target  =  self ._path .with_segments (target )
82- 
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 )
89- 
90105    _readable_metakeys  =  frozenset ()
91106
92107    def  _read_metadata (self , metakeys , * , follow_symlinks = True ):
@@ -96,8 +111,16 @@ def _read_metadata(self, metakeys, *, follow_symlinks=True):
96111        raise  NotImplementedError 
97112
98113
99- class  CopyWriter (CopyReader ):
100-     __slots__  =  ()
114+ class  CopyWriter :
115+     """ 
116+     Class that implements the "write" part of copying between path objects. An 
117+     instance of this class is available from the WritablePath._copy_writer 
118+     property. 
119+     """ 
120+     __slots__  =  ('_path' ,)
121+ 
122+     def  __init__ (self , path ):
123+         self ._path  =  path 
101124
102125    _writable_metakeys  =  frozenset ()
103126
@@ -110,7 +133,7 @@ def _write_metadata(self, metadata, *, follow_symlinks=True):
110133    def  _create (self , source , follow_symlinks , dirs_exist_ok , preserve_metadata ):
111134        self ._ensure_distinct_path (source )
112135        if  preserve_metadata :
113-             metakeys  =  self ._writable_metakeys  &  source .copy ._readable_metakeys 
136+             metakeys  =  self ._writable_metakeys  &  source ._copy_reader ._readable_metakeys 
114137        else :
115138            metakeys  =  None 
116139        if  not  follow_symlinks  and  source .is_symlink ():
@@ -128,22 +151,22 @@ def _create_dir(self, source, metakeys, follow_symlinks, dirs_exist_ok):
128151        for  src  in  children :
129152            dst  =  self ._path .joinpath (src .name )
130153            if  not  follow_symlinks  and  src .is_symlink ():
131-                 dst .copy ._create_symlink (src , metakeys )
154+                 dst ._copy_writer ._create_symlink (src , metakeys )
132155            elif  src .is_dir ():
133-                 dst .copy ._create_dir (src , metakeys , follow_symlinks , dirs_exist_ok )
156+                 dst ._copy_writer ._create_dir (src , metakeys , follow_symlinks , dirs_exist_ok )
134157            else :
135-                 dst .copy ._create_file (src , metakeys )
158+                 dst ._copy_writer ._create_file (src , metakeys )
136159        if  metakeys :
137-             metadata  =  source .copy ._read_metadata (metakeys )
160+             metadata  =  source ._copy_reader ._read_metadata (metakeys )
138161            if  metadata :
139162                self ._write_metadata (metadata )
140163
141164    def  _create_file (self , source , metakeys ):
142165        """Copy the given file to our path.""" 
143166        self ._ensure_different_file (source )
144-         with  source . open ( 'rb' ) as  source_f :
167+         with  magic_open ( source ,  'rb' ) as  source_f :
145168            try :
146-                 with  self ._path . open ( 'wb' ) as  target_f :
169+                 with  magic_open ( self ._path ,  'wb' ) as  target_f :
147170                    copyfileobj (source_f , target_f )
148171            except  IsADirectoryError  as  e :
149172                if  not  self ._path .exists ():
@@ -152,15 +175,15 @@ def _create_file(self, source, metakeys):
152175                        f'Directory does not exist: { self ._path }  ) from  e 
153176                raise 
154177        if  metakeys :
155-             metadata  =  source .copy ._read_metadata (metakeys )
178+             metadata  =  source ._copy_reader ._read_metadata (metakeys )
156179            if  metadata :
157180                self ._write_metadata (metadata )
158181
159182    def  _create_symlink (self , source , metakeys ):
160183        """Copy the given symbolic link to our path.""" 
161184        self ._path .symlink_to (source .readlink ())
162185        if  metakeys :
163-             metadata  =  source .copy ._read_metadata (metakeys , follow_symlinks = False )
186+             metadata  =  source ._copy_reader ._read_metadata (metakeys , follow_symlinks = False )
164187            if  metadata :
165188                self ._write_metadata (metadata , follow_symlinks = False )
166189
@@ -420,26 +443,25 @@ def is_symlink(self):
420443        """ 
421444        raise  NotImplementedError 
422445
423-     def  open (self , mode = 'r' , buffering = - 1 , encoding = None ,
424-              errors = None , newline = None ):
446+     def  __open_rb__ (self , buffering = - 1 ):
425447        """ 
426-         Open the file pointed to by this path and return a file object, as  
427-         the built-in  open() function does . 
448+         Open the file pointed to by this path for reading in binary mode and  
449+         return a file object, like  open(mode='rb') . 
428450        """ 
429451        raise  NotImplementedError 
430452
431453    def  read_bytes (self ):
432454        """ 
433455        Open the file in bytes mode, read it, and close the file. 
434456        """ 
435-         with  self . open ( mode = 'rb' , buffering = 0 ) as  f :
457+         with  magic_open ( self ,  mode = 'rb' , buffering = 0 ) as  f :
436458            return  f .read ()
437459
438460    def  read_text (self , encoding = None , errors = None , newline = None ):
439461        """ 
440462        Open the file in text mode, read it, and close the file. 
441463        """ 
442-         with  self . open ( mode = 'r' , encoding = encoding , errors = errors , newline = newline ) as  f :
464+         with  magic_open ( self ,  mode = 'r' , encoding = encoding , errors = errors , newline = newline ) as  f :
443465            return  f .read ()
444466
445467    def  _scandir (self ):
@@ -532,7 +554,22 @@ def readlink(self):
532554        """ 
533555        raise  NotImplementedError 
534556
535-     copy  =  property (CopyReader , doc = CopyReader .__call__ .__doc__ )
557+     _copy_reader  =  property (CopyReader )
558+ 
559+     def  copy (self , target , follow_symlinks = True , dirs_exist_ok = False ,
560+              preserve_metadata = False ):
561+         """ 
562+         Recursively copy this file or directory tree to the given destination. 
563+         """ 
564+         if  not  isinstance (target , ReadablePath ):
565+             target  =  self .with_segments (target )
566+ 
567+         # Delegate to the target path's CopyWriter object. 
568+         try :
569+             create  =  target ._copy_writer ._create 
570+         except  AttributeError :
571+             raise  TypeError (f"Target is not writable: { target }  ) from  None 
572+         return  create (self , follow_symlinks , dirs_exist_ok , preserve_metadata )
536573
537574    def  copy_into (self , target_dir , * , follow_symlinks = True ,
538575                  dirs_exist_ok = False , preserve_metadata = False ):
@@ -551,7 +588,7 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
551588                         preserve_metadata = preserve_metadata )
552589
553590
554- class  WritablePath (ReadablePath ):
591+ class  WritablePath (JoinablePath ):
555592    __slots__  =  ()
556593
557594    def  symlink_to (self , target , target_is_directory = False ):
@@ -567,13 +604,20 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
567604        """ 
568605        raise  NotImplementedError 
569606
607+     def  __open_wb__ (self , buffering = - 1 ):
608+         """ 
609+         Open the file pointed to by this path for writing in binary mode and 
610+         return a file object, like open(mode='wb'). 
611+         """ 
612+         raise  NotImplementedError 
613+ 
570614    def  write_bytes (self , data ):
571615        """ 
572616        Open the file in bytes mode, write to it, and close the file. 
573617        """ 
574618        # type-check for the buffer interface before truncating the file 
575619        view  =  memoryview (data )
576-         with  self . open ( mode = 'wb' ) as  f :
620+         with  magic_open ( self ,  mode = 'wb' ) as  f :
577621            return  f .write (view )
578622
579623    def  write_text (self , data , encoding = None , errors = None , newline = None ):
@@ -583,7 +627,7 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
583627        if  not  isinstance (data , str ):
584628            raise  TypeError ('data must be str, not %s'  % 
585629                            data .__class__ .__name__ )
586-         with  self . open ( mode = 'w' , encoding = encoding , errors = errors , newline = newline ) as  f :
630+         with  magic_open ( self ,  mode = 'w' , encoding = encoding , errors = errors , newline = newline ) as  f :
587631            return  f .write (data )
588632
589-     copy  =  property (CopyWriter ,  doc = CopyWriter . __call__ . __doc__ )
633+     _copy_writer  =  property (CopyWriter )
0 commit comments