@@ -102,16 +102,16 @@ def _sendfile(source_fd, target_fd):
102
102
103
103
104
104
if _winapi and hasattr (_winapi , 'CopyFile2' ):
105
- def copyfile (source , target ):
105
+ def _copyfile2 (source , target ):
106
106
"""
107
107
Copy from one file to another using CopyFile2 (Windows only).
108
108
"""
109
109
_winapi .CopyFile2 (source , target , 0 )
110
110
else :
111
- copyfile = None
111
+ _copyfile2 = None
112
112
113
113
114
- def copyfileobj (source_f , target_f ):
114
+ def _copyfileobj (source_f , target_f ):
115
115
"""
116
116
Copy data from file-like object source_f to file-like object target_f.
117
117
"""
@@ -200,70 +200,6 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None,
200
200
raise TypeError (f"{ cls .__name__ } can't be opened with mode { mode !r} " )
201
201
202
202
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
-
267
203
def ensure_distinct_paths (source , target ):
268
204
"""
269
205
Raise OSError(EINVAL) if the other path is within this path.
@@ -284,94 +220,6 @@ def ensure_distinct_paths(source, target):
284
220
raise err
285
221
286
222
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
-
375
223
def ensure_different_files (source , target ):
376
224
"""
377
225
Raise OSError(EINVAL) if both paths refer to the same file.
@@ -394,6 +242,102 @@ def ensure_different_files(source, target):
394
242
raise err
395
243
396
244
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
+
397
341
class _PathInfoBase :
398
342
__slots__ = ('_path' , '_stat_result' , '_lstat_result' )
399
343
0 commit comments