changeset: 94315:9cbf9f96920d user: Serhiy Storchaka date: Mon Jan 26 14:01:27 2015 +0200 files: Lib/test/test_zipfile.py Lib/zipfile.py Misc/NEWS description: Issue #14099: Restored support of writing ZIP files to tellable but non-seekable streams. diff -r 4973ccd46e32 -r 9cbf9f96920d Lib/test/test_zipfile.py --- a/Lib/test/test_zipfile.py Mon Jan 26 13:53:38 2015 +0200 +++ b/Lib/test/test_zipfile.py Mon Jan 26 14:01:27 2015 +0200 @@ -1668,6 +1668,34 @@ compression = zipfile.ZIP_LZMA +# Privide the tell() method but not seek() +class Tellable: + def __init__(self, fp): + self.fp = fp + self.offset = 0 + + def write(self, data): + self.offset += self.fp.write(data) + + def tell(self): + return self.offset + + def flush(self): + pass + +class UnseekableTests(unittest.TestCase): + def test_writestr_tellable(self): + f = io.BytesIO() + with zipfile.ZipFile(Tellable(f), 'w', zipfile.ZIP_STORED) as zipfp: + zipfp.writestr('ones', b'111') + zipfp.writestr('twos', b'222') + with zipfile.ZipFile(f, mode='r') as zipf: + with zipf.open('ones') as zopen: + self.assertEqual(zopen.read(), b'111') + with zipf.open('twos') as zopen: + self.assertEqual(zopen.read(), b'222') + + @requires_zlib class TestsWithMultipleOpens(unittest.TestCase): @classmethod diff -r 4973ccd46e32 -r 9cbf9f96920d Lib/zipfile.py --- a/Lib/zipfile.py Mon Jan 26 13:53:38 2015 +0200 +++ b/Lib/zipfile.py Mon Jan 26 14:01:27 2015 +0200 @@ -1504,7 +1504,14 @@ zinfo.file_size = len(data) # Uncompressed size with self._lock: - self.fp.seek(self.start_dir, 0) + try: + self.fp.seek(self.start_dir) + except (AttributeError, io.UnsupportedOperation): + # Some file-like objects can provide tell() but not seek() + pass + zinfo.header_offset = self.fp.tell() # Start of header data + if compress_type is not None: + zinfo.compress_type = compress_type zinfo.header_offset = self.fp.tell() # Start of header data if compress_type is not None: zinfo.compress_type = compress_type @@ -1550,7 +1557,11 @@ try: if self.mode in ("w", "a") and self._didModify: # write ending records with self._lock: - self.fp.seek(self.start_dir, 0) + try: + self.fp.seek(self.start_dir) + except (AttributeError, io.UnsupportedOperation): + # Some file-like objects can provide tell() but not seek() + pass self._write_end_record() finally: fp = self.fp @@ -1558,7 +1569,6 @@ self._fpclose(fp) def _write_end_record(self): - self.fp.seek(self.start_dir, 0) for zinfo in self.filelist: # write central directory dt = zinfo.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] diff -r 4973ccd46e32 -r 9cbf9f96920d Misc/NEWS --- a/Misc/NEWS Mon Jan 26 13:53:38 2015 +0200 +++ b/Misc/NEWS Mon Jan 26 14:01:27 2015 +0200 @@ -218,6 +218,9 @@ Library ------- +- Issue #14099: Restored support of writing ZIP files to tellable but + non-seekable streams. + - Issue #14099: Writing to ZipFile and reading multiple ZipExtFiles is threadsafe now.