Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commitb251d40

Browse files
authored
GH-125413: Add privatepathlib.Path method to write metadata (#130238)
Replace `WritablePath._copy_writer` with a new `_write_info()` method. Thismethod allows the target of a `copy()` to preserve metadata.Replace `pathlib._os.CopyWriter` and `LocalCopyWriter` classes with new`copy_file()` and `copy_info()` functions. The `copy_file()` function uses`source_path.info` wherever possible to save on `stat()`s.
1 parent5ba69e7 commitb251d40

File tree

4 files changed

+122
-174
lines changed

4 files changed

+122
-174
lines changed

‎Lib/pathlib/_abc.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
fromabcimportABC,abstractmethod
1515
fromglobimport_PathGlobber,_no_recurse_symlinks
1616
frompathlibimportPurePath,Path
17-
frompathlib._osimportmagic_open,CopyWriter
17+
frompathlib._osimportmagic_open,ensure_distinct_paths,copy_file
1818

1919

2020
def_explode_path(path):
@@ -347,13 +347,8 @@ def copy(self, target, follow_symlinks=True, dirs_exist_ok=False,
347347
"""
348348
ifnothasattr(target,'with_segments'):
349349
target=self.with_segments(target)
350-
351-
# Delegate to the target path's CopyWriter object.
352-
try:
353-
create=target._copy_writer._create
354-
exceptAttributeError:
355-
raiseTypeError(f"Target is not writable:{target}")fromNone
356-
create(self,follow_symlinks,dirs_exist_ok,preserve_metadata)
350+
ensure_distinct_paths(self,target)
351+
copy_file(self,target,follow_symlinks,dirs_exist_ok,preserve_metadata)
357352
returntarget.joinpath()# Empty join to ensure fresh metadata.
358353

359354
defcopy_into(self,target_dir,*,follow_symlinks=True,
@@ -424,7 +419,11 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
424419
withmagic_open(self,mode='w',encoding=encoding,errors=errors,newline=newline)asf:
425420
returnf.write(data)
426421

427-
_copy_writer=property(CopyWriter)
422+
def_write_info(self,info,follow_symlinks=True):
423+
"""
424+
Write the given PathInfo to this path.
425+
"""
426+
pass
428427

429428

430429
JoinablePath.register(PurePath)

‎Lib/pathlib/_local.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
exceptImportError:
2020
grp=None
2121

22-
frompathlib._osimportLocalCopyWriter,PathInfo,DirEntryInfo,ensure_different_files
22+
frompathlib._osimport (
23+
PathInfo,DirEntryInfo,
24+
ensure_different_files,ensure_distinct_paths,
25+
copy_file,copy_info,
26+
)
2327

2428

2529
__all__= [
@@ -799,6 +803,12 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
799803
withself.open(mode='w',encoding=encoding,errors=errors,newline=newline)asf:
800804
returnf.write(data)
801805

806+
def_write_info(self,info,follow_symlinks=True):
807+
"""
808+
Write the given PathInfo to this path.
809+
"""
810+
copy_info(info,self,follow_symlinks=follow_symlinks)
811+
802812
_remove_leading_dot=operator.itemgetter(slice(2,None))
803813
_remove_trailing_slash=operator.itemgetter(slice(-1))
804814

@@ -1083,22 +1093,15 @@ def replace(self, target):
10831093
target=self.with_segments(target)
10841094
returntarget
10851095

1086-
_copy_writer=property(LocalCopyWriter)
1087-
10881096
defcopy(self,target,follow_symlinks=True,dirs_exist_ok=False,
10891097
preserve_metadata=False):
10901098
"""
10911099
Recursively copy this file or directory tree to the given destination.
10921100
"""
10931101
ifnothasattr(target,'with_segments'):
10941102
target=self.with_segments(target)
1095-
1096-
# Delegate to the target path's CopyWriter object.
1097-
try:
1098-
create=target._copy_writer._create
1099-
exceptAttributeError:
1100-
raiseTypeError(f"Target is not writable:{target}")fromNone
1101-
create(self,follow_symlinks,dirs_exist_ok,preserve_metadata)
1103+
ensure_distinct_paths(self,target)
1104+
copy_file(self,target,follow_symlinks,dirs_exist_ok,preserve_metadata)
11021105
returntarget.joinpath()# Empty join to ensure fresh metadata.
11031106

11041107
defcopy_into(self,target_dir,*,follow_symlinks=True,

‎Lib/pathlib/_os.py

Lines changed: 99 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,16 @@ def _sendfile(source_fd, target_fd):
102102

103103

104104
if_winapiandhasattr(_winapi,'CopyFile2'):
105-
defcopyfile(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-
copyfile=None
111+
_copyfile2=None
112112

113113

114-
defcopyfileobj(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
raiseTypeError(f"{cls.__name__} can't be opened with mode{mode!r}")
201201

202202

203-
classCopyWriter:
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-
ifnotfollow_symlinksandsource.is_symlink():
221-
self._create_symlink(source,preserve_metadata)
222-
elifsource.is_dir():
223-
self._create_dir(source,follow_symlinks,dirs_exist_ok,preserve_metadata)
224-
else:
225-
self._create_file(source,preserve_metadata)
226-
returnself._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-
forsrcinchildren:
233-
dst=self._path.joinpath(src.name)
234-
ifnotfollow_symlinksandsrc.is_symlink():
235-
dst._copy_writer._create_symlink(src,preserve_metadata)
236-
elifsrc.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-
ifpreserve_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-
withmagic_open(source,'rb')assource_f:
248-
try:
249-
withmagic_open(self._path,'wb')astarget_f:
250-
copyfileobj(source_f,target_f)
251-
exceptIsADirectoryErrorase:
252-
ifnotself._path.exists():
253-
# Raise a less confusing exception.
254-
raiseFileNotFoundError(
255-
f'Directory does not exist:{self._path}')frome
256-
raise
257-
ifpreserve_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-
ifpreserve_metadata:
264-
self._copy_metadata(source,follow_symlinks=False)
265-
266-
267203
defensure_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
raiseerr
285221

286222

287-
classLocalCopyWriter(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_symlinksoros.utimeinos.supports_follow_symlinks))
302-
ifcopy_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_symlinksoros.setxattrinos.supports_follow_symlinks))
313-
ifcopy_xattrs:
314-
xattrs=info._xattrs(follow_symlinks=follow_symlinks)
315-
forattr,valueinxattrs:
316-
try:
317-
os.setxattr(target,attr,value,follow_symlinks=follow_symlinks)
318-
exceptOSErrorase:
319-
ife.errnonotin (EPERM,ENOTSUP,ENODATA,EINVAL,EACCES):
320-
raise
321-
322-
copy_posix_permissions= (
323-
hasattr(info,'_posix_permissions')and
324-
(follow_symlinksoros.chmodinos.supports_follow_symlinks))
325-
ifcopy_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-
exceptNotImplementedError:
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_symlinksoros.chflagsinos.supports_follow_symlinks))
346-
ifcopy_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-
exceptOSErroraswhy:
351-
ifwhy.errnonotin (EOPNOTSUPP,ENOTSUP):
352-
raise
353-
354-
ifcopyfile:
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-
exceptTypeError:
361-
super()._create_file(source,preserve_metadata)
362-
else:
363-
copyfile(source,os.fspath(self._path))
364-
365-
ifos.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-
ifpreserve_metadata:
372-
self._copy_metadata(source,follow_symlinks=False)
373-
374-
375223
defensure_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
raiseerr
395243

396244

245+
defcopy_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+
ifnotfollow_symlinksandinfo.is_symlink():
252+
target.symlink_to(source.readlink(),info.is_dir())
253+
ifpreserve_metadata:
254+
target._write_info(info,follow_symlinks=False)
255+
elifinfo.is_dir():
256+
children=source.iterdir()
257+
target.mkdir(exist_ok=dirs_exist_ok)
258+
forsrcinchildren:
259+
dst=target.joinpath(src.name)
260+
copy_file(src,dst,follow_symlinks,dirs_exist_ok,preserve_metadata)
261+
ifpreserve_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+
exceptTypeError:
270+
pass
271+
else:
272+
_copyfile2(source_p,target_p)
273+
return
274+
ensure_different_files(source,target)
275+
withmagic_open(source,'rb')assource_f:
276+
withmagic_open(target,'wb')astarget_f:
277+
_copyfileobj(source_f,target_f)
278+
ifpreserve_metadata:
279+
target._write_info(info)
280+
281+
282+
defcopy_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_symlinksoros.utimeinos.supports_follow_symlinks))
288+
ifcopy_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_symlinksoros.setxattrinos.supports_follow_symlinks))
299+
ifcopy_xattrs:
300+
xattrs=info._xattrs(follow_symlinks=follow_symlinks)
301+
forattr,valueinxattrs:
302+
try:
303+
os.setxattr(target,attr,value,follow_symlinks=follow_symlinks)
304+
exceptOSErrorase:
305+
ife.errnonotin (EPERM,ENOTSUP,ENODATA,EINVAL,EACCES):
306+
raise
307+
308+
copy_posix_permissions= (
309+
hasattr(info,'_posix_permissions')and
310+
(follow_symlinksoros.chmodinos.supports_follow_symlinks))
311+
ifcopy_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+
exceptNotImplementedError:
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_symlinksoros.chflagsinos.supports_follow_symlinks))
332+
ifcopy_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+
exceptOSErroraswhy:
337+
ifwhy.errnonotin (EOPNOTSUPP,ENOTSUP):
338+
raise
339+
340+
397341
class_PathInfoBase:
398342
__slots__= ('_path','_stat_result','_lstat_result')
399343

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up:meth:`Path.copy <pathlib.Path.copy>` by making better use of
2+
:attr:`~pathlib.Path.info` internally.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp