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

Commit01d9150

Browse files
authored
GH-128520: Makepathlib._abc.WritablePath a sibling ofReadablePath (#129014)
In the private pathlib ABCs, support write-only virtual filesystems bymaking `WritablePath` inherit directly from `JoinablePath`, rather thansubclassing `ReadablePath`.There are two complications:- `ReadablePath.open()` applies to both reading and writing- `ReadablePath.copy` is secretly an object that supports the *read* side of copying, whereas `WritablePath.copy` is a different kind of object supporting the *write* sideWe untangle these as follow:- A new `pathlib._abc.magic_open()` function replaces the `open()` method, which is dropped from the ABCs but remains in `pathlib.Path`. The function works like `io.open()`, but additionally accepts objects with `__open_rb__()` or `__open_wb__()` methods as appropriate for the mode. These new dunders are made abstract methods of `ReadablePath` and `WritablePath` respectively. If the pathlib ABCs are made public, we could consider blessing an "openable" protocol and supporting it in `io.open()`, removing the need for `pathlib._abc.magic_open()`.- `ReadablePath.copy` becomes a true method, whereas `WritablePath.copy` is deleted. A new `ReadablePath._copy_reader` property provides a `CopyReader` object, and similarly `WritablePath._copy_writer` is a `CopyWriter` object. OnceGH-125413 is resolved, we'll be able to move the `CopyReader` functionality into `ReadablePath.info` and eliminate `ReadablePath._copy_reader`.
1 parent3d7c0e5 commit01d9150

File tree

4 files changed

+178
-115
lines changed

4 files changed

+178
-115
lines changed

‎Lib/pathlib/_abc.py

Lines changed: 90 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"""
1313

1414
importfunctools
15+
importio
1516
importoperator
1617
importposixpath
1718
fromerrnoimportEINVAL
@@ -41,6 +42,40 @@ def _explode_path(path):
4142
returnpath,names
4243

4344

45+
defmagic_open(path,mode='r',buffering=-1,encoding=None,errors=None,
46+
newline=None):
47+
"""
48+
Open the file pointed to by this path and return a file object, as
49+
the built-in open() function does.
50+
"""
51+
try:
52+
returnio.open(path,mode,buffering,encoding,errors,newline)
53+
exceptTypeError:
54+
pass
55+
cls=type(path)
56+
text='b'notinmode
57+
mode=''.join(sorted(cforcinmodeifcnotin'bt'))
58+
iftext:
59+
try:
60+
attr=getattr(cls,f'__open_{mode}__')
61+
exceptAttributeError:
62+
pass
63+
else:
64+
returnattr(path,buffering,encoding,errors,newline)
65+
66+
try:
67+
attr=getattr(cls,f'__open_{mode}b__')
68+
exceptAttributeError:
69+
pass
70+
else:
71+
stream=attr(path,buffering)
72+
iftext:
73+
stream=io.TextIOWrapper(stream,encoding,errors,newline)
74+
returnstream
75+
76+
raiseTypeError(f"{cls.__name__} can't be opened with mode{mode!r}")
77+
78+
4479
classPathGlobber(_GlobberBase):
4580
"""
4681
Class providing shell-style globbing for path objects.
@@ -58,35 +93,15 @@ def concat_path(path, text):
5893

5994
classCopyReader:
6095
"""
61-
Class that implements copying between path objects. An instance of this
62-
class is available from the ReadablePath.copy property; it's made callable
63-
so that ReadablePath.copy() can be treated as a method.
64-
65-
The target path's CopyWriter drives the process from its _create() method.
66-
Files and directories are exchanged by calling methods on the source and
67-
target paths, and metadata is exchanged by calling
68-
source.copy._read_metadata() and target.copy._write_metadata().
96+
Class that implements the "read" part of copying between path objects.
97+
An instance of this class is available from the ReadablePath._copy_reader
98+
property.
6999
"""
70100
__slots__= ('_path',)
71101

72102
def__init__(self,path):
73103
self._path=path
74104

75-
def__call__(self,target,follow_symlinks=True,dirs_exist_ok=False,
76-
preserve_metadata=False):
77-
"""
78-
Recursively copy this file or directory tree to the given destination.
79-
"""
80-
ifnotisinstance(target,ReadablePath):
81-
target=self._path.with_segments(target)
82-
83-
# Delegate to the target path's CopyWriter object.
84-
try:
85-
create=target.copy._create
86-
exceptAttributeError:
87-
raiseTypeError(f"Target is not writable:{target}")fromNone
88-
returncreate(self._path,follow_symlinks,dirs_exist_ok,preserve_metadata)
89-
90105
_readable_metakeys=frozenset()
91106

92107
def_read_metadata(self,metakeys,*,follow_symlinks=True):
@@ -96,8 +111,16 @@ def _read_metadata(self, metakeys, *, follow_symlinks=True):
96111
raiseNotImplementedError
97112

98113

99-
classCopyWriter(CopyReader):
100-
__slots__= ()
114+
classCopyWriter:
115+
"""
116+
Class that implements the "write" part of copying between path objects. An
117+
instance of this class is available from the WritablePath._copy_writer
118+
property.
119+
"""
120+
__slots__= ('_path',)
121+
122+
def__init__(self,path):
123+
self._path=path
101124

102125
_writable_metakeys=frozenset()
103126

@@ -110,7 +133,7 @@ def _write_metadata(self, metadata, *, follow_symlinks=True):
110133
def_create(self,source,follow_symlinks,dirs_exist_ok,preserve_metadata):
111134
self._ensure_distinct_path(source)
112135
ifpreserve_metadata:
113-
metakeys=self._writable_metakeys&source.copy._readable_metakeys
136+
metakeys=self._writable_metakeys&source._copy_reader._readable_metakeys
114137
else:
115138
metakeys=None
116139
ifnotfollow_symlinksandsource.is_symlink():
@@ -128,22 +151,22 @@ def _create_dir(self, source, metakeys, follow_symlinks, dirs_exist_ok):
128151
forsrcinchildren:
129152
dst=self._path.joinpath(src.name)
130153
ifnotfollow_symlinksandsrc.is_symlink():
131-
dst.copy._create_symlink(src,metakeys)
154+
dst._copy_writer._create_symlink(src,metakeys)
132155
elifsrc.is_dir():
133-
dst.copy._create_dir(src,metakeys,follow_symlinks,dirs_exist_ok)
156+
dst._copy_writer._create_dir(src,metakeys,follow_symlinks,dirs_exist_ok)
134157
else:
135-
dst.copy._create_file(src,metakeys)
158+
dst._copy_writer._create_file(src,metakeys)
136159
ifmetakeys:
137-
metadata=source.copy._read_metadata(metakeys)
160+
metadata=source._copy_reader._read_metadata(metakeys)
138161
ifmetadata:
139162
self._write_metadata(metadata)
140163

141164
def_create_file(self,source,metakeys):
142165
"""Copy the given file to our path."""
143166
self._ensure_different_file(source)
144-
withsource.open('rb')assource_f:
167+
withmagic_open(source,'rb')assource_f:
145168
try:
146-
withself._path.open('wb')astarget_f:
169+
withmagic_open(self._path,'wb')astarget_f:
147170
copyfileobj(source_f,target_f)
148171
exceptIsADirectoryErrorase:
149172
ifnotself._path.exists():
@@ -152,15 +175,15 @@ def _create_file(self, source, metakeys):
152175
f'Directory does not exist:{self._path}')frome
153176
raise
154177
ifmetakeys:
155-
metadata=source.copy._read_metadata(metakeys)
178+
metadata=source._copy_reader._read_metadata(metakeys)
156179
ifmetadata:
157180
self._write_metadata(metadata)
158181

159182
def_create_symlink(self,source,metakeys):
160183
"""Copy the given symbolic link to our path."""
161184
self._path.symlink_to(source.readlink())
162185
ifmetakeys:
163-
metadata=source.copy._read_metadata(metakeys,follow_symlinks=False)
186+
metadata=source._copy_reader._read_metadata(metakeys,follow_symlinks=False)
164187
ifmetadata:
165188
self._write_metadata(metadata,follow_symlinks=False)
166189

@@ -420,26 +443,25 @@ def is_symlink(self):
420443
"""
421444
raiseNotImplementedError
422445

423-
defopen(self,mode='r',buffering=-1,encoding=None,
424-
errors=None,newline=None):
446+
def__open_rb__(self,buffering=-1):
425447
"""
426-
Open the file pointed to by this pathand return a file object, as
427-
the built-inopen() function does.
448+
Open the file pointed to by this pathfor reading in binary mode and
449+
return a file object, likeopen(mode='rb').
428450
"""
429451
raiseNotImplementedError
430452

431453
defread_bytes(self):
432454
"""
433455
Open the file in bytes mode, read it, and close the file.
434456
"""
435-
withself.open(mode='rb',buffering=0)asf:
457+
withmagic_open(self,mode='rb',buffering=0)asf:
436458
returnf.read()
437459

438460
defread_text(self,encoding=None,errors=None,newline=None):
439461
"""
440462
Open the file in text mode, read it, and close the file.
441463
"""
442-
withself.open(mode='r',encoding=encoding,errors=errors,newline=newline)asf:
464+
withmagic_open(self,mode='r',encoding=encoding,errors=errors,newline=newline)asf:
443465
returnf.read()
444466

445467
def_scandir(self):
@@ -532,7 +554,22 @@ def readlink(self):
532554
"""
533555
raiseNotImplementedError
534556

535-
copy=property(CopyReader,doc=CopyReader.__call__.__doc__)
557+
_copy_reader=property(CopyReader)
558+
559+
defcopy(self,target,follow_symlinks=True,dirs_exist_ok=False,
560+
preserve_metadata=False):
561+
"""
562+
Recursively copy this file or directory tree to the given destination.
563+
"""
564+
ifnothasattr(target,'_copy_writer'):
565+
target=self.with_segments(target)
566+
567+
# Delegate to the target path's CopyWriter object.
568+
try:
569+
create=target._copy_writer._create
570+
exceptAttributeError:
571+
raiseTypeError(f"Target is not writable:{target}")fromNone
572+
returncreate(self,follow_symlinks,dirs_exist_ok,preserve_metadata)
536573

537574
defcopy_into(self,target_dir,*,follow_symlinks=True,
538575
dirs_exist_ok=False,preserve_metadata=False):
@@ -542,7 +579,7 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
542579
name=self.name
543580
ifnotname:
544581
raiseValueError(f"{self!r} has an empty name")
545-
elifisinstance(target_dir,ReadablePath):
582+
elifhasattr(target_dir,'_copy_writer'):
546583
target=target_dir/name
547584
else:
548585
target=self.with_segments(target_dir,name)
@@ -551,7 +588,7 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
551588
preserve_metadata=preserve_metadata)
552589

553590

554-
classWritablePath(ReadablePath):
591+
classWritablePath(JoinablePath):
555592
__slots__= ()
556593

557594
defsymlink_to(self,target,target_is_directory=False):
@@ -567,13 +604,20 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
567604
"""
568605
raiseNotImplementedError
569606

607+
def__open_wb__(self,buffering=-1):
608+
"""
609+
Open the file pointed to by this path for writing in binary mode and
610+
return a file object, like open(mode='wb').
611+
"""
612+
raiseNotImplementedError
613+
570614
defwrite_bytes(self,data):
571615
"""
572616
Open the file in bytes mode, write to it, and close the file.
573617
"""
574618
# type-check for the buffer interface before truncating the file
575619
view=memoryview(data)
576-
withself.open(mode='wb')asf:
620+
withmagic_open(self,mode='wb')asf:
577621
returnf.write(view)
578622

579623
defwrite_text(self,data,encoding=None,errors=None,newline=None):
@@ -583,7 +627,7 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
583627
ifnotisinstance(data,str):
584628
raiseTypeError('data must be str, not %s'%
585629
data.__class__.__name__)
586-
withself.open(mode='w',encoding=encoding,errors=errors,newline=newline)asf:
630+
withmagic_open(self,mode='w',encoding=encoding,errors=errors,newline=newline)asf:
587631
returnf.write(data)
588632

589-
copy=property(CopyWriter,doc=CopyWriter.__call__.__doc__)
633+
_copy_writer=property(CopyWriter)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp