Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 563ab5c

Browse filesBrowse files
authored
GH-130614: pathlib ABCs: improve support for receiving path metadata (#131259)
In the private pathlib ABCs, replace `_WritablePath._write_info()` with `_WritablePath._copy_from()`. This provides the target path object with more control over the copying process, including support for querying and setting metadata *before* the path is created. Adjust `_ReadablePath.copy()` so that it forwards its keyword arguments to `_WritablePath._copy_from()` of the target path object. This allows us to remove the unimplemented *preserve_metadata* argument in the ABC method, making it a `Path` exclusive.
1 parent e82c2ca commit 563ab5c
Copy full SHA for 563ab5c

File tree

3 files changed

+92
-64
lines changed
Filter options

3 files changed

+92
-64
lines changed

‎Lib/pathlib/__init__.py

Copy file name to clipboardExpand all lines: Lib/pathlib/__init__.py
+61-13Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from 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
"""

‎Lib/pathlib/_os.py

Copy file name to clipboardExpand all lines: Lib/pathlib/_os.py
+3-39Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,16 @@ def _sendfile(source_fd, target_fd):
102102

103103

104104
if _winapi and hasattr(_winapi, 'CopyFile2'):
105-
def _copyfile2(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)
110110
else:
111-
_copyfile2 = 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
"""
@@ -242,42 +242,6 @@ def ensure_different_files(source, target):
242242
raise err
243243

244244

245-
def copy_file(source, target, follow_symlinks=True, preserve_metadata=False):
246-
"""
247-
Recursively copy the given source ReadablePath to the given target WritablePath.
248-
"""
249-
info = source.info
250-
if not follow_symlinks and info.is_symlink():
251-
target.symlink_to(str(source.readlink()), info.is_dir())
252-
if preserve_metadata:
253-
target._write_info(info, follow_symlinks=False)
254-
elif info.is_dir():
255-
children = source.iterdir()
256-
target.mkdir()
257-
for src in children:
258-
dst = target.joinpath(src.name)
259-
copy_file(src, dst, follow_symlinks, preserve_metadata)
260-
if preserve_metadata:
261-
target._write_info(info)
262-
else:
263-
if _copyfile2:
264-
# Use fast OS routine for local file copying where available.
265-
try:
266-
source_p = os.fspath(source)
267-
target_p = os.fspath(target)
268-
except TypeError:
269-
pass
270-
else:
271-
_copyfile2(source_p, target_p)
272-
return
273-
ensure_different_files(source, target)
274-
with magic_open(source, 'rb') as source_f:
275-
with magic_open(target, 'wb') as target_f:
276-
_copyfileobj(source_f, target_f)
277-
if preserve_metadata:
278-
target._write_info(info)
279-
280-
281245
def copy_info(info, target, follow_symlinks=True):
282246
"""Copy metadata from the given PathInfo to the given local path."""
283247
copy_times_ns = (

‎Lib/pathlib/types.py

Copy file name to clipboardExpand all lines: Lib/pathlib/types.py
+28-12Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
from abc import ABC, abstractmethod
1414
from glob import _PathGlobber
15+
from pathlib._os import magic_open, ensure_distinct_paths, ensure_different_files, copyfileobj
1516
from pathlib import PurePath, Path
16-
from pathlib._os import magic_open, ensure_distinct_paths, copy_file
1717
from typing import Optional, Protocol, runtime_checkable
1818

1919

@@ -332,18 +332,21 @@ def readlink(self):
332332
"""
333333
raise NotImplementedError
334334

335-
def copy(self, target, follow_symlinks=True, preserve_metadata=False):
335+
def copy(self, target, **kwargs):
336336
"""
337337
Recursively copy this file or directory tree to the given destination.
338338
"""
339339
if not hasattr(target, 'with_segments'):
340340
target = self.with_segments(target)
341341
ensure_distinct_paths(self, target)
342-
copy_file(self, target, follow_symlinks, preserve_metadata)
342+
try:
343+
copy_to_target = target._copy_from
344+
except AttributeError:
345+
raise TypeError(f"Target path is not writable: {target!r}") from None
346+
copy_to_target(self, **kwargs)
343347
return target.joinpath() # Empty join to ensure fresh metadata.
344348

345-
def copy_into(self, target_dir, *, follow_symlinks=True,
346-
preserve_metadata=False):
349+
def copy_into(self, target_dir, **kwargs):
347350
"""
348351
Copy this file or directory tree into the given existing directory.
349352
"""
@@ -354,8 +357,7 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
354357
target = target_dir / name
355358
else:
356359
target = self.with_segments(target_dir, name)
357-
return self.copy(target, follow_symlinks=follow_symlinks,
358-
preserve_metadata=preserve_metadata)
360+
return self.copy(target, **kwargs)
359361

360362

361363
class _WritablePath(_JoinablePath):
@@ -409,11 +411,25 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
409411
with magic_open(self, mode='w', encoding=encoding, errors=errors, newline=newline) as f:
410412
return f.write(data)
411413

412-
def _write_info(self, info, follow_symlinks=True):
413-
"""
414-
Write the given PathInfo to this path.
415-
"""
416-
pass
414+
def _copy_from(self, source, follow_symlinks=True):
415+
"""
416+
Recursively copy the given path to this path.
417+
"""
418+
stack = [(source, self)]
419+
while stack:
420+
src, dst = stack.pop()
421+
if not follow_symlinks and src.info.is_symlink():
422+
dst.symlink_to(str(src.readlink()), src.info.is_dir())
423+
elif src.info.is_dir():
424+
children = src.iterdir()
425+
dst.mkdir()
426+
for child in children:
427+
stack.append((child, dst.joinpath(child.name)))
428+
else:
429+
ensure_different_files(src, dst)
430+
with magic_open(src, 'rb') as source_f:
431+
with magic_open(dst, 'wb') as target_f:
432+
copyfileobj(source_f, target_f)
417433

418434

419435
_JoinablePath.register(PurePath)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.