@@ -57,6 +57,132 @@ def concat_path(path, text):
5757        return  path .with_segments (str (path ) +  text )
5858
5959
60+ class  CopyWorker :
61+     """ 
62+     Class that implements copying between path objects. An instance of this 
63+     class is available from the PathBase.copy property; it's made callable so 
64+     that PathBase.copy() can be treated as a method. 
65+ 
66+     The target path's CopyWorker drives the process from its _create() method. 
67+     Files and directories are exchanged by calling methods on the source and 
68+     target paths, and metadata is exchanged by calling 
69+     source.copy._read_metadata() and target.copy._write_metadata(). 
70+     """ 
71+     __slots__  =  ('_path' ,)
72+ 
73+     def  __init__ (self , path ):
74+         self ._path  =  path 
75+ 
76+     def  __call__ (self , target , follow_symlinks = True , dirs_exist_ok = False ,
77+              preserve_metadata = False ):
78+         """ 
79+         Recursively copy this file or directory tree to the given destination. 
80+         """ 
81+         if  not  isinstance (target , PathBase ):
82+             target  =  self ._path .with_segments (target )
83+ 
84+         # Delegate to the target path's CopyWorker object. 
85+         return  target .copy ._create (self ._path , follow_symlinks , dirs_exist_ok , preserve_metadata )
86+ 
87+     _readable_metakeys  =  frozenset ()
88+ 
89+     def  _read_metadata (self , metakeys , * , follow_symlinks = True ):
90+         """ 
91+         Returns path metadata as a dict with string keys. 
92+         """ 
93+         raise  NotImplementedError 
94+ 
95+     _writable_metakeys  =  frozenset ()
96+ 
97+     def  _write_metadata (self , metadata , * , follow_symlinks = True ):
98+         """ 
99+         Sets path metadata from the given dict with string keys. 
100+         """ 
101+         raise  NotImplementedError 
102+ 
103+     def  _create (self , source , follow_symlinks , dirs_exist_ok , preserve_metadata ):
104+         self ._ensure_distinct_path (source )
105+         if  preserve_metadata :
106+             metakeys  =  self ._writable_metakeys  &  source .copy ._readable_metakeys 
107+         else :
108+             metakeys  =  None 
109+         if  not  follow_symlinks  and  source .is_symlink ():
110+             self ._create_symlink (source , metakeys )
111+         elif  source .is_dir ():
112+             self ._create_dir (source , metakeys , follow_symlinks , dirs_exist_ok )
113+         else :
114+             self ._create_file (source , metakeys )
115+         return  self ._path 
116+ 
117+     def  _create_dir (self , source , metakeys , follow_symlinks , dirs_exist_ok ):
118+         """Copy the given directory to our path.""" 
119+         children  =  list (source .iterdir ())
120+         self ._path .mkdir (exist_ok = dirs_exist_ok )
121+         for  src  in  children :
122+             dst  =  self ._path .joinpath (src .name )
123+             if  not  follow_symlinks  and  src .is_symlink ():
124+                 dst .copy ._create_symlink (src , metakeys )
125+             elif  src .is_dir ():
126+                 dst .copy ._create_dir (src , metakeys , follow_symlinks , dirs_exist_ok )
127+             else :
128+                 dst .copy ._create_file (src , metakeys )
129+         if  metakeys :
130+             metadata  =  source .copy ._read_metadata (metakeys )
131+             if  metadata :
132+                 self ._write_metadata (metadata )
133+ 
134+     def  _create_file (self , source , metakeys ):
135+         """Copy the given file to our path.""" 
136+         self ._ensure_different_file (source )
137+         with  source .open ('rb' ) as  source_f :
138+             try :
139+                 with  self ._path .open ('wb' ) as  target_f :
140+                     copyfileobj (source_f , target_f )
141+             except  IsADirectoryError  as  e :
142+                 if  not  self ._path .exists ():
143+                     # Raise a less confusing exception. 
144+                     raise  FileNotFoundError (
145+                         f'Directory does not exist: { self ._path }  ) from  e 
146+                 raise 
147+         if  metakeys :
148+             metadata  =  source .copy ._read_metadata (metakeys )
149+             if  metadata :
150+                 self ._write_metadata (metadata )
151+ 
152+     def  _create_symlink (self , source , metakeys ):
153+         """Copy the given symbolic link to our path.""" 
154+         self ._path .symlink_to (source .readlink ())
155+         if  metakeys :
156+             metadata  =  source .copy ._read_metadata (metakeys , follow_symlinks = False )
157+             if  metadata :
158+                 self ._write_metadata (metadata , follow_symlinks = False )
159+ 
160+     def  _ensure_different_file (self , source ):
161+         """ 
162+         Raise OSError(EINVAL) if both paths refer to the same file. 
163+         """ 
164+         pass 
165+ 
166+     def  _ensure_distinct_path (self , source ):
167+         """ 
168+         Raise OSError(EINVAL) if the other path is within this path. 
169+         """ 
170+         # Note: there is no straightforward, foolproof algorithm to determine 
171+         # if one directory is within another (a particularly perverse example 
172+         # would be a single network share mounted in one location via NFS, and 
173+         # in another location via CIFS), so we simply checks whether the 
174+         # other path is lexically equal to, or within, this path. 
175+         if  source  ==  self ._path :
176+             err  =  OSError (EINVAL , "Source and target are the same path" )
177+         elif  source  in  self ._path .parents :
178+             err  =  OSError (EINVAL , "Source path is a parent of target path" )
179+         else :
180+             return 
181+         err .filename  =  str (source )
182+         err .filename2  =  str (self ._path )
183+         raise  err 
184+ 
185+ 
60186class  PurePathBase :
61187    """Base class for pure path objects. 
62188
@@ -374,31 +500,6 @@ def is_symlink(self):
374500        except  (OSError , ValueError ):
375501            return  False 
376502
377-     def  _ensure_different_file (self , other_path ):
378-         """ 
379-         Raise OSError(EINVAL) if both paths refer to the same file. 
380-         """ 
381-         pass 
382- 
383-     def  _ensure_distinct_path (self , other_path ):
384-         """ 
385-         Raise OSError(EINVAL) if the other path is within this path. 
386-         """ 
387-         # Note: there is no straightforward, foolproof algorithm to determine 
388-         # if one directory is within another (a particularly perverse example 
389-         # would be a single network share mounted in one location via NFS, and 
390-         # in another location via CIFS), so we simply checks whether the 
391-         # other path is lexically equal to, or within, this path. 
392-         if  self  ==  other_path :
393-             err  =  OSError (EINVAL , "Source and target are the same path" )
394-         elif  self  in  other_path .parents :
395-             err  =  OSError (EINVAL , "Source path is a parent of target path" )
396-         else :
397-             return 
398-         err .filename  =  str (self )
399-         err .filename2  =  str (other_path )
400-         raise  err 
401- 
402503    def  open (self , mode = 'r' , buffering = - 1 , encoding = None ,
403504             errors = None , newline = None ):
404505        """ 
@@ -537,88 +638,13 @@ def symlink_to(self, target, target_is_directory=False):
537638        """ 
538639        raise  NotImplementedError 
539640
540-     def  _symlink_to_target_of (self , link ):
541-         """ 
542-         Make this path a symlink with the same target as the given link. This 
543-         is used by copy(). 
544-         """ 
545-         self .symlink_to (link .readlink ())
546- 
547641    def  mkdir (self , mode = 0o777 , parents = False , exist_ok = False ):
548642        """ 
549643        Create a new directory at this given path. 
550644        """ 
551645        raise  NotImplementedError 
552646
553-     # Metadata keys supported by this path type. 
554-     _readable_metadata  =  _writable_metadata  =  frozenset ()
555- 
556-     def  _read_metadata (self , keys = None , * , follow_symlinks = True ):
557-         """ 
558-         Returns path metadata as a dict with string keys. 
559-         """ 
560-         raise  NotImplementedError 
561- 
562-     def  _write_metadata (self , metadata , * , follow_symlinks = True ):
563-         """ 
564-         Sets path metadata from the given dict with string keys. 
565-         """ 
566-         raise  NotImplementedError 
567- 
568-     def  _copy_metadata (self , target , * , follow_symlinks = True ):
569-         """ 
570-         Copies metadata (permissions, timestamps, etc) from this path to target. 
571-         """ 
572-         # Metadata types supported by both source and target. 
573-         keys  =  self ._readable_metadata  &  target ._writable_metadata 
574-         if  keys :
575-             metadata  =  self ._read_metadata (keys , follow_symlinks = follow_symlinks )
576-             target ._write_metadata (metadata , follow_symlinks = follow_symlinks )
577- 
578-     def  _copy_file (self , target ):
579-         """ 
580-         Copy the contents of this file to the given target. 
581-         """ 
582-         self ._ensure_different_file (target )
583-         with  self .open ('rb' ) as  source_f :
584-             try :
585-                 with  target .open ('wb' ) as  target_f :
586-                     copyfileobj (source_f , target_f )
587-             except  IsADirectoryError  as  e :
588-                 if  not  target .exists ():
589-                     # Raise a less confusing exception. 
590-                     raise  FileNotFoundError (
591-                         f'Directory does not exist: { target }  ) from  e 
592-                 else :
593-                     raise 
594- 
595-     def  copy (self , target , * , follow_symlinks = True , dirs_exist_ok = False ,
596-              preserve_metadata = False ):
597-         """ 
598-         Recursively copy this file or directory tree to the given destination. 
599-         """ 
600-         if  not  isinstance (target , PathBase ):
601-             target  =  self .with_segments (target )
602-         self ._ensure_distinct_path (target )
603-         stack  =  [(self , target )]
604-         while  stack :
605-             src , dst  =  stack .pop ()
606-             if  not  follow_symlinks  and  src .is_symlink ():
607-                 dst ._symlink_to_target_of (src )
608-                 if  preserve_metadata :
609-                     src ._copy_metadata (dst , follow_symlinks = False )
610-             elif  src .is_dir ():
611-                 children  =  src .iterdir ()
612-                 dst .mkdir (exist_ok = dirs_exist_ok )
613-                 stack .extend ((child , dst .joinpath (child .name ))
614-                              for  child  in  children )
615-                 if  preserve_metadata :
616-                     src ._copy_metadata (dst )
617-             else :
618-                 src ._copy_file (dst )
619-                 if  preserve_metadata :
620-                     src ._copy_metadata (dst )
621-         return  target 
647+     copy  =  property (CopyWorker , doc = CopyWorker .__call__ .__doc__ )
622648
623649    def  copy_into (self , target_dir , * , follow_symlinks = True ,
624650                  dirs_exist_ok = False , preserve_metadata = False ):
0 commit comments