@@ -102,16 +102,16 @@ def _sendfile(source_fd, target_fd):
102102
103103
104104if _winapi and hasattr (_winapi , 'CopyFile2' ):
105- def copyfile (source , target ):
105+ def _copyfile2 (source , target ):
106106 """
107107 Copy from one file to another using CopyFile2 (Windows only).
108108 """
109109 _winapi .CopyFile2 (source , target , 0 )
110110else :
111- copyfile = None
111+ _copyfile2 = None
112112
113113
114- def copyfileobj (source_f , target_f ):
114+ def _copyfileobj (source_f , target_f ):
115115 """
116116 Copy data from file-like object source_f to file-like object target_f.
117117 """
@@ -200,70 +200,6 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None,
200200 raise TypeError (f"{ cls .__name__ } can't be opened with mode { mode !r} " )
201201
202202
203- class CopyWriter :
204- """
205- Class that implements the "write" part of copying between path objects. An
206- instance of this class is available from the WritablePath._copy_writer
207- property.
208- """
209- __slots__ = ('_path' ,)
210-
211- def __init__ (self , path ):
212- self ._path = path
213-
214- def _copy_metadata (self , source , follow_symlinks = True ):
215- """Copy metadata from the given path to our path."""
216- pass
217-
218- def _create (self , source , follow_symlinks , dirs_exist_ok , preserve_metadata ):
219- ensure_distinct_paths (source , self ._path )
220- if not follow_symlinks and source .is_symlink ():
221- self ._create_symlink (source , preserve_metadata )
222- elif source .is_dir ():
223- self ._create_dir (source , follow_symlinks , dirs_exist_ok , preserve_metadata )
224- else :
225- self ._create_file (source , preserve_metadata )
226- return self ._path
227-
228- def _create_dir (self , source , follow_symlinks , dirs_exist_ok , preserve_metadata ):
229- """Copy the given directory to our path."""
230- children = list (source .iterdir ())
231- self ._path .mkdir (exist_ok = dirs_exist_ok )
232- for src in children :
233- dst = self ._path .joinpath (src .name )
234- if not follow_symlinks and src .is_symlink ():
235- dst ._copy_writer ._create_symlink (src , preserve_metadata )
236- elif src .is_dir ():
237- dst ._copy_writer ._create_dir (src , follow_symlinks , dirs_exist_ok , preserve_metadata )
238- else :
239- dst ._copy_writer ._create_file (src , preserve_metadata )
240-
241- if preserve_metadata :
242- self ._copy_metadata (source )
243-
244- def _create_file (self , source , preserve_metadata ):
245- """Copy the given file to our path."""
246- ensure_different_files (source , self ._path )
247- with magic_open (source , 'rb' ) as source_f :
248- try :
249- with magic_open (self ._path , 'wb' ) as target_f :
250- copyfileobj (source_f , target_f )
251- except IsADirectoryError as e :
252- if not self ._path .exists ():
253- # Raise a less confusing exception.
254- raise FileNotFoundError (
255- f'Directory does not exist: { self ._path } ' ) from e
256- raise
257- if preserve_metadata :
258- self ._copy_metadata (source )
259-
260- def _create_symlink (self , source , preserve_metadata ):
261- """Copy the given symbolic link to our path."""
262- self ._path .symlink_to (source .readlink ())
263- if preserve_metadata :
264- self ._copy_metadata (source , follow_symlinks = False )
265-
266-
267203def ensure_distinct_paths (source , target ):
268204 """
269205 Raise OSError(EINVAL) if the other path is within this path.
@@ -284,94 +220,6 @@ def ensure_distinct_paths(source, target):
284220 raise err
285221
286222
287- class LocalCopyWriter (CopyWriter ):
288- """This object implements the "write" part of copying local paths. Don't
289- try to construct it yourself.
290- """
291- __slots__ = ()
292-
293- def _copy_metadata (self , source , follow_symlinks = True ):
294- """Copy metadata from the given path to our path."""
295- target = self ._path
296- info = source .info
297-
298- copy_times_ns = (
299- hasattr (info , '_access_time_ns' ) and
300- hasattr (info , '_mod_time_ns' ) and
301- (follow_symlinks or os .utime in os .supports_follow_symlinks ))
302- if copy_times_ns :
303- t0 = info ._access_time_ns (follow_symlinks = follow_symlinks )
304- t1 = info ._mod_time_ns (follow_symlinks = follow_symlinks )
305- os .utime (target , ns = (t0 , t1 ), follow_symlinks = follow_symlinks )
306-
307- # We must copy extended attributes before the file is (potentially)
308- # chmod()'ed read-only, otherwise setxattr() will error with -EACCES.
309- copy_xattrs = (
310- hasattr (info , '_xattrs' ) and
311- hasattr (os , 'setxattr' ) and
312- (follow_symlinks or os .setxattr in os .supports_follow_symlinks ))
313- if copy_xattrs :
314- xattrs = info ._xattrs (follow_symlinks = follow_symlinks )
315- for attr , value in xattrs :
316- try :
317- os .setxattr (target , attr , value , follow_symlinks = follow_symlinks )
318- except OSError as e :
319- if e .errno not in (EPERM , ENOTSUP , ENODATA , EINVAL , EACCES ):
320- raise
321-
322- copy_posix_permissions = (
323- hasattr (info , '_posix_permissions' ) and
324- (follow_symlinks or os .chmod in os .supports_follow_symlinks ))
325- if copy_posix_permissions :
326- posix_permissions = info ._posix_permissions (follow_symlinks = follow_symlinks )
327- try :
328- os .chmod (target , posix_permissions , follow_symlinks = follow_symlinks )
329- except NotImplementedError :
330- # if we got a NotImplementedError, it's because
331- # * follow_symlinks=False,
332- # * lchown() is unavailable, and
333- # * either
334- # * fchownat() is unavailable or
335- # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW.
336- # (it returned ENOSUP.)
337- # therefore we're out of options--we simply cannot chown the
338- # symlink. give up, suppress the error.
339- # (which is what shutil always did in this circumstance.)
340- pass
341-
342- copy_bsd_flags = (
343- hasattr (info , '_bsd_flags' ) and
344- hasattr (os , 'chflags' ) and
345- (follow_symlinks or os .chflags in os .supports_follow_symlinks ))
346- if copy_bsd_flags :
347- bsd_flags = info ._bsd_flags (follow_symlinks = follow_symlinks )
348- try :
349- os .chflags (target , bsd_flags , follow_symlinks = follow_symlinks )
350- except OSError as why :
351- if why .errno not in (EOPNOTSUPP , ENOTSUP ):
352- raise
353-
354- if copyfile :
355- # Use fast OS routine for local file copying where available.
356- def _create_file (self , source , preserve_metadata ):
357- """Copy the given file to the given target."""
358- try :
359- source = os .fspath (source )
360- except TypeError :
361- super ()._create_file (source , preserve_metadata )
362- else :
363- copyfile (source , os .fspath (self ._path ))
364-
365- if os .name == 'nt' :
366- # Windows: symlink target might not exist yet if we're copying several
367- # files, so ensure we pass is_dir to os.symlink().
368- def _create_symlink (self , source , preserve_metadata ):
369- """Copy the given symlink to the given target."""
370- self ._path .symlink_to (source .readlink (), source .is_dir ())
371- if preserve_metadata :
372- self ._copy_metadata (source , follow_symlinks = False )
373-
374-
375223def ensure_different_files (source , target ):
376224 """
377225 Raise OSError(EINVAL) if both paths refer to the same file.
@@ -394,6 +242,102 @@ def ensure_different_files(source, target):
394242 raise err
395243
396244
245+ def copy_file (source , target , follow_symlinks = True , dirs_exist_ok = False ,
246+ preserve_metadata = False ):
247+ """
248+ Recursively copy the given source ReadablePath to the given target WritablePath.
249+ """
250+ info = source .info
251+ if not follow_symlinks and info .is_symlink ():
252+ target .symlink_to (source .readlink (), info .is_dir ())
253+ if preserve_metadata :
254+ target ._write_info (info , follow_symlinks = False )
255+ elif info .is_dir ():
256+ children = source .iterdir ()
257+ target .mkdir (exist_ok = dirs_exist_ok )
258+ for src in children :
259+ dst = target .joinpath (src .name )
260+ copy_file (src , dst , follow_symlinks , dirs_exist_ok , preserve_metadata )
261+ if preserve_metadata :
262+ target ._write_info (info )
263+ else :
264+ if _copyfile2 :
265+ # Use fast OS routine for local file copying where available.
266+ try :
267+ source_p = os .fspath (source )
268+ target_p = os .fspath (target )
269+ except TypeError :
270+ pass
271+ else :
272+ _copyfile2 (source_p , target_p )
273+ return
274+ ensure_different_files (source , target )
275+ with magic_open (source , 'rb' ) as source_f :
276+ with magic_open (target , 'wb' ) as target_f :
277+ _copyfileobj (source_f , target_f )
278+ if preserve_metadata :
279+ target ._write_info (info )
280+
281+
282+ def copy_info (info , target , follow_symlinks = True ):
283+ """Copy metadata from the given PathInfo to the given local path."""
284+ copy_times_ns = (
285+ hasattr (info , '_access_time_ns' ) and
286+ hasattr (info , '_mod_time_ns' ) and
287+ (follow_symlinks or os .utime in os .supports_follow_symlinks ))
288+ if copy_times_ns :
289+ t0 = info ._access_time_ns (follow_symlinks = follow_symlinks )
290+ t1 = info ._mod_time_ns (follow_symlinks = follow_symlinks )
291+ os .utime (target , ns = (t0 , t1 ), follow_symlinks = follow_symlinks )
292+
293+ # We must copy extended attributes before the file is (potentially)
294+ # chmod()'ed read-only, otherwise setxattr() will error with -EACCES.
295+ copy_xattrs = (
296+ hasattr (info , '_xattrs' ) and
297+ hasattr (os , 'setxattr' ) and
298+ (follow_symlinks or os .setxattr in os .supports_follow_symlinks ))
299+ if copy_xattrs :
300+ xattrs = info ._xattrs (follow_symlinks = follow_symlinks )
301+ for attr , value in xattrs :
302+ try :
303+ os .setxattr (target , attr , value , follow_symlinks = follow_symlinks )
304+ except OSError as e :
305+ if e .errno not in (EPERM , ENOTSUP , ENODATA , EINVAL , EACCES ):
306+ raise
307+
308+ copy_posix_permissions = (
309+ hasattr (info , '_posix_permissions' ) and
310+ (follow_symlinks or os .chmod in os .supports_follow_symlinks ))
311+ if copy_posix_permissions :
312+ posix_permissions = info ._posix_permissions (follow_symlinks = follow_symlinks )
313+ try :
314+ os .chmod (target , posix_permissions , follow_symlinks = follow_symlinks )
315+ except NotImplementedError :
316+ # if we got a NotImplementedError, it's because
317+ # * follow_symlinks=False,
318+ # * lchown() is unavailable, and
319+ # * either
320+ # * fchownat() is unavailable or
321+ # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW.
322+ # (it returned ENOSUP.)
323+ # therefore we're out of options--we simply cannot chown the
324+ # symlink. give up, suppress the error.
325+ # (which is what shutil always did in this circumstance.)
326+ pass
327+
328+ copy_bsd_flags = (
329+ hasattr (info , '_bsd_flags' ) and
330+ hasattr (os , 'chflags' ) and
331+ (follow_symlinks or os .chflags in os .supports_follow_symlinks ))
332+ if copy_bsd_flags :
333+ bsd_flags = info ._bsd_flags (follow_symlinks = follow_symlinks )
334+ try :
335+ os .chflags (target , bsd_flags , follow_symlinks = follow_symlinks )
336+ except OSError as why :
337+ if why .errno not in (EOPNOTSUPP , ENOTSUP ):
338+ raise
339+
340+
397341class _PathInfoBase :
398342 __slots__ = ('_path' , '_stat_result' , '_lstat_result' )
399343
0 commit comments