2929from pathlib ._os import (
3030 PathInfo , DirEntryInfo ,
3131 ensure_different_files , ensure_distinct_paths ,
32- copy_file , copy_info ,
32+ copyfile2 , copyfileobj , magic_open , copy_info ,
3333)
3434
3535
@@ -810,12 +810,6 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
810810 with self .open (mode = 'w' , encoding = encoding , errors = errors , newline = newline ) as f :
811811 return f .write (data )
812812
813- def _write_info (self , info , follow_symlinks = True ):
814- """
815- Write the given PathInfo to this path.
816- """
817- copy_info (info , self , follow_symlinks = follow_symlinks )
818-
819813 _remove_leading_dot = operator .itemgetter (slice (2 , None ))
820814 _remove_trailing_slash = operator .itemgetter (slice (- 1 ))
821815
@@ -1100,18 +1094,21 @@ def replace(self, target):
11001094 target = self .with_segments (target )
11011095 return target
11021096
1103- def copy (self , target , follow_symlinks = True , preserve_metadata = False ):
1097+ def copy (self , target , ** kwargs ):
11041098 """
11051099 Recursively copy this file or directory tree to the given destination.
11061100 """
11071101 if not hasattr (target , 'with_segments' ):
11081102 target = self .with_segments (target )
11091103 ensure_distinct_paths (self , target )
1110- copy_file (self , target , follow_symlinks , preserve_metadata )
1104+ try :
1105+ copy_to_target = target ._copy_from
1106+ except AttributeError :
1107+ raise TypeError (f"Target path is not writable: { target !r} " ) from None
1108+ copy_to_target (self , ** kwargs )
11111109 return target .joinpath () # Empty join to ensure fresh metadata.
11121110
1113- def copy_into (self , target_dir , * , follow_symlinks = True ,
1114- preserve_metadata = False ):
1111+ def copy_into (self , target_dir , ** kwargs ):
11151112 """
11161113 Copy this file or directory tree into the given existing directory.
11171114 """
@@ -1122,8 +1119,59 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
11221119 target = target_dir / name
11231120 else :
11241121 target = self .with_segments (target_dir , name )
1125- return self .copy (target , follow_symlinks = follow_symlinks ,
1126- preserve_metadata = preserve_metadata )
1122+ return self .copy (target , ** kwargs )
1123+
1124+ def _copy_from (self , source , follow_symlinks = True , preserve_metadata = False ):
1125+ """
1126+ Recursively copy the given path to this path.
1127+ """
1128+ if not follow_symlinks and source .info .is_symlink ():
1129+ self ._copy_from_symlink (source , preserve_metadata )
1130+ elif source .info .is_dir ():
1131+ children = source .iterdir ()
1132+ os .mkdir (self )
1133+ for child in children :
1134+ self .joinpath (child .name )._copy_from (
1135+ child , follow_symlinks , preserve_metadata )
1136+ if preserve_metadata :
1137+ copy_info (source .info , self )
1138+ else :
1139+ self ._copy_from_file (source , preserve_metadata )
1140+
1141+ def _copy_from_file (self , source , preserve_metadata = False ):
1142+ ensure_different_files (source , self )
1143+ with magic_open (source , 'rb' ) as source_f :
1144+ with open (self , 'wb' ) as target_f :
1145+ copyfileobj (source_f , target_f )
1146+ if preserve_metadata :
1147+ copy_info (source .info , self )
1148+
1149+ if copyfile2 :
1150+ # Use fast OS routine for local file copying where available.
1151+ _copy_from_file_fallback = _copy_from_file
1152+ def _copy_from_file (self , source , preserve_metadata = False ):
1153+ try :
1154+ source = os .fspath (source )
1155+ except TypeError :
1156+ pass
1157+ else :
1158+ copyfile2 (source , str (self ))
1159+ return
1160+ self ._copy_from_file_fallback (source , preserve_metadata )
1161+
1162+ if os .name == 'nt' :
1163+ # If a directory-symlink is copied *before* its target, then
1164+ # os.symlink() incorrectly creates a file-symlink on Windows. Avoid
1165+ # this by passing *target_is_dir* to os.symlink() on Windows.
1166+ def _copy_from_symlink (self , source , preserve_metadata = False ):
1167+ os .symlink (str (source .readlink ()), self , source .info .is_dir ())
1168+ if preserve_metadata :
1169+ copy_info (source .info , self , follow_symlinks = False )
1170+ else :
1171+ def _copy_from_symlink (self , source , preserve_metadata = False ):
1172+ os .symlink (str (source .readlink ()), self )
1173+ if preserve_metadata :
1174+ copy_info (source .info , self , follow_symlinks = False )
11271175
11281176 def move (self , target ):
11291177 """
0 commit comments