Skip to content

Commit ebbe803

Browse files
authored
bpo-40564: Avoid copying state from extant ZipFile. (pythonGH-22371)
bpo-40564: Avoid copying state from extant ZipFile.
1 parent c111355 commit ebbe803

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

Lib/test/test_zipfile.py

+33
Original file line numberDiff line numberDiff line change
@@ -2889,6 +2889,33 @@ def test_open(self):
28892889
data = strm.read()
28902890
assert data == "content of a"
28912891

2892+
def test_open_write(self):
2893+
"""
2894+
If the zipfile is open for write, it should be possible to
2895+
write bytes or text to it.
2896+
"""
2897+
zf = zipfile.Path(zipfile.ZipFile(io.BytesIO(), mode='w'))
2898+
with zf.joinpath('file.bin').open('wb') as strm:
2899+
strm.write(b'binary contents')
2900+
with zf.joinpath('file.txt').open('w') as strm:
2901+
strm.write('text file')
2902+
2903+
def test_open_extant_directory(self):
2904+
"""
2905+
Attempting to open a directory raises IsADirectoryError.
2906+
"""
2907+
zf = zipfile.Path(add_dirs(build_alpharep_fixture()))
2908+
with self.assertRaises(IsADirectoryError):
2909+
zf.joinpath('b').open()
2910+
2911+
def test_open_missing_directory(self):
2912+
"""
2913+
Attempting to open a missing directory raises FileNotFoundError.
2914+
"""
2915+
zf = zipfile.Path(add_dirs(build_alpharep_fixture()))
2916+
with self.assertRaises(FileNotFoundError):
2917+
zf.joinpath('z').open()
2918+
28922919
def test_read(self):
28932920
for alpharep in self.zipfile_alpharep():
28942921
root = zipfile.Path(alpharep)
@@ -2986,6 +3013,12 @@ def test_implied_dirs_performance(self):
29863013
data = ['/'.join(string.ascii_lowercase + str(n)) for n in range(10000)]
29873014
zipfile.CompleteDirs._implied_dirs(data)
29883015

3016+
def test_read_does_not_close(self):
3017+
for alpharep in self.zipfile_ondisk():
3018+
with zipfile.ZipFile(alpharep) as file:
3019+
for rep in range(2):
3020+
zipfile.Path(file, 'a.txt').read_text()
3021+
29893022

29903023
if __name__ == "__main__":
29913024
unittest.main()

Lib/zipfile.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -2197,13 +2197,12 @@ def make(cls, source):
21972197
if not isinstance(source, ZipFile):
21982198
return cls(source)
21992199

2200-
# Only allow for FastPath when supplied zipfile is read-only
2200+
# Only allow for FastLookup when supplied zipfile is read-only
22012201
if 'r' not in source.mode:
22022202
cls = CompleteDirs
22032203

2204-
res = cls.__new__(cls)
2205-
vars(res).update(vars(source))
2206-
return res
2204+
source.__class__ = cls
2205+
return source
22072206

22082207

22092208
class FastLookup(CompleteDirs):
@@ -2292,17 +2291,29 @@ class Path:
22922291
__repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
22932292

22942293
def __init__(self, root, at=""):
2294+
"""
2295+
Construct a Path from a ZipFile or filename.
2296+
2297+
Note: When the source is an existing ZipFile object,
2298+
its type (__class__) will be mutated to a
2299+
specialized type. If the caller wishes to retain the
2300+
original type, the caller should either create a
2301+
separate ZipFile object or pass a filename.
2302+
"""
22952303
self.root = FastLookup.make(root)
22962304
self.at = at
22972305

2298-
def open(self, mode='r', *args, **kwargs):
2306+
def open(self, mode='r', *args, pwd=None, **kwargs):
22992307
"""
23002308
Open this entry as text or binary following the semantics
23012309
of ``pathlib.Path.open()`` by passing arguments through
23022310
to io.TextIOWrapper().
23032311
"""
2304-
pwd = kwargs.pop('pwd', None)
2312+
if self.is_dir():
2313+
raise IsADirectoryError(self)
23052314
zip_mode = mode[0]
2315+
if not self.exists() and zip_mode == 'r':
2316+
raise FileNotFoundError(self)
23062317
stream = self.root.open(self.at, zip_mode, pwd=pwd)
23072318
if 'b' in mode:
23082319
if args or kwargs:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
In ``zipfile.Path``, mutate the passed ZipFile object type instead of making a copy. Prevents issues when both the local copy and the caller’s copy attempt to close the same file handle.

0 commit comments

Comments
 (0)