Skip to content

Commit 4ea51e3

Browse files
committed
Add partial flushing of ZipStreams
I use this to flush partial zips as files are streamed into them from requests, and then at the end add manifest and error files to the end of the archive I've also added a related test and example of use
1 parent 7e861b1 commit 4ea51e3

4 files changed

Lines changed: 55 additions & 4 deletions

File tree

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,27 @@ def zipball():
113113
response = Response(z, mimetype='application/zip')
114114
response.headers['Content-Disposition'] = 'attachment; filename={}'.format('files.zip')
115115
return response
116+
117+
# Partial flushing of the zip before closing
118+
119+
@app.route('/package.zip', methods=['GET'], endpoint='zipball')
120+
def zipball():
121+
def generate_zip_with_manifest():
122+
z = zipstream.ZipFile(mode='w', compression=ZIP_DEFLATED)
123+
124+
manifest = []
125+
for filename in os.listdir('/path/to/files'):
126+
z.write(os.path.join('/path/to/files', filename), arcname=filename)
127+
yield from z.flush()
128+
manifest.append(filename)
129+
130+
z.write_str('manifest.json', json.dumps(manifest).encode())
131+
132+
yield from z
133+
134+
response = Response(z, mimetype='application/zip')
135+
response.headers['Content-Disposition'] = 'attachment; filename={}'.format('files.zip')
136+
return response
116137
```
117138

118139
### django 1.5+

tests/test_zipstream.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,31 @@ def test_writestr(self):
9292

9393
os.remove(f.name)
9494

95+
def test_partial_writes(self):
96+
z = zipstream.ZipFile(mode='w')
97+
f = tempfile.NamedTemporaryFile(suffix='zip', delete=False)
98+
99+
with open(SAMPLE_FILE_RTF, 'rb') as fp:
100+
z.writestr('sample1.rtf', fp.read())
101+
102+
for chunk in z.flush():
103+
f.write(chunk)
104+
105+
with open(SAMPLE_FILE_RTF, 'rb') as fp:
106+
z.writestr('sample2.rtf', fp.read())
107+
108+
for chunk in z.flush():
109+
f.write(chunk)
110+
111+
for chunk in z:
112+
f.write(chunk)
113+
114+
f.close()
115+
z2 = zipfile.ZipFile(f.name, 'r')
116+
self.assertFalse(z2.testzip())
117+
118+
os.remove(f.name)
119+
95120
def test_write_iterable_no_archive(self):
96121
z = zipstream.ZipFile(mode='w')
97122
self.assertRaises(TypeError, z.write_iter, iterable=range(10))

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py26, py27, py32, py33, py34, py35, pypy
2+
envlist = py26, py27, py32, py33, py34, py35, py36, py37, pypy
33

44
[testenv]
55
deps=nose

zipstream/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,8 @@ def __init__(self, fileobj=None, mode='w', compression=ZIP_STORED, allowZip64=Fa
178178
self.paths_to_write = []
179179

180180
def __iter__(self):
181-
for kwargs in self.paths_to_write:
182-
for data in self.__write(**kwargs):
183-
yield data
181+
for data in self.flush():
182+
yield data
184183
for data in self.__close():
185184
yield data
186185

@@ -190,6 +189,12 @@ def __enter__(self):
190189
def __exit__(self, type, value, traceback):
191190
self.close()
192191

192+
def flush(self):
193+
while self.paths_to_write:
194+
kwargs = self.paths_to_write.pop()
195+
for data in self.__write(**kwargs):
196+
yield data
197+
193198
@property
194199
def comment(self):
195200
"""The comment text associated with the ZIP file."""

0 commit comments

Comments
 (0)