50

I have the following code to create an in memory zip file that throws an error running in Python 3.

from io import StringIO
from pprint import pprint
import zipfile


in_memory_data = StringIO()
in_memory_zip = zipfile.ZipFile(
    in_memory_data, "w", zipfile.ZIP_DEFLATED, False)
in_memory_zip.debug = 3

filename_in_zip = 'test_filename.txt'
file_contents = 'asdf'

in_memory_zip.writestr(filename_in_zip, file_contents)

To be clear this is only a Python 3 problem. I can run the code fine on Python 2. To be exact I'm using Python 3.4.3. The stack trace is below:

Traceback (most recent call last):
  File "in_memory_zip_debug.py", line 14, in <module>
    in_memory_zip.writestr(filename_in_zip, file_contents)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/zipfile.py", line 1453, in writestr
    self.fp.write(zinfo.FileHeader(zip64))
TypeError: string argument expected, got 'bytes'
Exception ignored in: <bound method ZipFile.__del__ of <zipfile.ZipFile object at 0x1006e1ef0>>
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/zipfile.py", line 1466, in __del__
    self.close()
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/zipfile.py", line 1573, in close
    self.fp.write(endrec)
TypeError: string argument expected, got 'bytes'
pxg
  • 1,033
  • 1
  • 10
  • 16

2 Answers2

77

ZipFile writes its data as bytes, not strings. This means you'll have to use BytesIO instead of StringIO on Python 3.

The distinction between bytes and strings is new in Python 3. The six compatibility library has a BytesIO class for Python 2 if you want your program to be compatible with both.

Jareq
  • 31
  • 1
  • 7
Wander Nauta
  • 18,832
  • 1
  • 45
  • 62
  • 6
    This is confusing because the error message from the OP implies the exact opposite problem. He's providing a string value since that's what the error message states is the expected argument type. – Dan Loewenherz Nov 08 '17 at 22:45
  • except OP is using writestr method and not write, so it should indeed be a string and not bytes, or am I missing something ? – Tyrannas Oct 14 '21 at 14:31
27

The problem is that io.StringIO() is being used as the memory buffer, when it needs to be io.BytesIO. The error is occurring because the zipfile code is eventually calling the StringIO().Write() with bytes when StringIO expects a string.

Once it's changed to BytesIO(), it works:

from io import BytesIO
from pprint import pprint
import zipfile


in_memory_data = BytesIO()
in_memory_zip = zipfile.ZipFile(
    in_memory_data, "w", zipfile.ZIP_DEFLATED, False)
in_memory_zip.debug = 3

filename_in_zip = 'test_filename.txt'
file_contents = 'asdf'

in_memory_zip.writestr(filename_in_zip, file_contents)
alexcb
  • 296
  • 3
  • 2