'Zipfile module for Python3.6: write to Bytes instead of Files for Odoo
I've been trying to use the zipfile module for Python 3.6 to create a .zip file which contains multiple objects.
My problem is, I have to manage files from an Odoo database that only allows me to use bytes
objects instead of files.
This is my current code:
import zipfile
empty_zip_data = b'PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
zip = zipfile.ZipFile(empty_zip_data, 'w')
# files is a list of tuples: [(u'file_name', b'file_data'), ...]
for file in files:
file_name = file[0]
file_data = file[1]
zip.writestr(file_name, file_data)
Which returns this error:
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/zipfile.py", line 1658, in writestr
with self.open(zinfo, mode='w') as dest:
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/zipfile.py", line 1355, in open
return self._open_to_write(zinfo, force_zip64=force_zip64)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/zipfile.py", line 1468, in _open_to_write
self.fp.write(zinfo.FileHeader(zip64))
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/zipfile.py", line 723, in write
n = self.fp.write(data)
AttributeError: 'bytes' object has no attribute 'write'
How am I supposed to do it? I followed the ZipFile.writestr() docs, but that got me nowhere...
EDIT: using file_data = file[1].decode('utf-8')
as second parameter is not useful either, I get the same error.
Solution 1:[1]
As mentioned in my comment, the issue is with this line:
empty_zip_data = b'PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
zip = zipfile.ZipFile(empty_zip_data, 'w')
You're trying to pass a byte
object into the ZipFile()
method, but like open()
it is expecting a path-like object.
In your case, you might want to utilize the tempfile
module (in this particular example we'll use SpooledTemporaryFile
from this relevant question:
import tempfile
import zipfile
# Create a virtual temp file
with tempfile.SpooledTemporaryFile() as tp:
# pass the temp file for zip File to open
with zipfile.ZipFile(tp, 'w') as zip:
files = [(u'file_name', b'file_data'), (u'file_name2', b'file_data2'),]
for file in files:
file_name = file[0]
file_data = file[1]
zip.writestr(file_name, file_data)
# Reset the cursor back to beginning of the temp file
tp.seek(0)
zipped_bytes = tp.read()
zipped_bytes
# b'PK\x03\x04\x14\x00\x00\x00\x00\x00\xa8U ... \x00\x00'
Note the use of context managers to ensure all your file objects are closed properly after being loaded.
This gives you zipped_bytes
which is the bytes you want to pass back to Odoo. You can also test the zipped_bytes
by writing it to a physical file to see what it looks like first:
with open('test.zip', 'wb') as zf:
zf.write(zipped_bytes)
If you are handling file size that are considerably large, make sure to pay attention and make use of max_size
argument in the documentation.
Solution 2:[2]
If you want to handle all of this in memory without a temporary file then use io.BytesIO
as the file object for ZipFile
:
import io
from zipfile import ZIP_DEFLATED, ZipFile
file = io.BytesIO()
with ZipFile(file, 'w', ZIP_DEFLATED) as zip_file:
for name, content in [
('file.dat', b'data'), ('another_file.dat', b'more data')
]:
zip_file.writestr(name, content)
zip_data = file.getvalue()
print(zip_data)
You may also want to set the compression algorithm as shown because otherwise the default (no compression!) is used.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | BlackJack |