Enviar varios archivos .CSV a .ZIP sin almacenar en disco en Python

Estoy trabajando en una aplicación de informes para mi sitio web con Django. Quiero ejecutar varios informes y hacer que cada informe genere un archivo .csv en la memoria que se pueda descargar por lotes como .zip. Me gustaría hacer esto sin almacenar ningún archivo en el disco. Hasta ahora, para generar un solo archivo .csv, estoy siguiendo la operación común:

mem_file = StringIO.StringIO() writer = csv.writer(mem_file) writer.writerow(["My content", my_value]) mem_file.seek(0) response = HttpResponse(mem_file, content_type='text/csv') response['Content-Disposition'] = 'attachment; filename=my_file.csv' 

Esto funciona bien, pero solo para un único .csv descomprimido. Si tuviera, por ejemplo, una lista de archivos .csv creados con una secuencia de StringIO:

 firstFile = StringIO.StringIO() # write some data to the file secondFile = StringIO.StringIO() # write some data to the file thirdFile = StringIO.StringIO() # write some data to the file myFiles = [firstFile, secondFile, thirdFile] 

¿Cómo podría devolver un archivo comprimido que contenga todos los objetos en myFiles y se pueda descomprimir correctamente para revelar tres archivos .csv?

zipfile es un módulo de biblioteca estándar que hace exactamente lo que está buscando. Para su caso de uso, la carne y las papas es un método llamado “writestr” que toma el nombre de un archivo y los datos que contiene y que le gustaría comprimir.

En el código a continuación, he usado un esquema de nomenclatura secuencial para los archivos cuando están descomprimidos, pero se puede cambiar a lo que quieras.

 import zipfile import StringIO zipped_file = StringIO.StringIO() with zipfile.ZipFile(zipped_file, 'w') as zip: for i, file in enumerate(files): file.seek(0) zip.writestr("{}.csv".format(i), file.read()) zipped_file.seek(0) 

Si desea probar su código en el futuro (sugerencia de pista Sugerencia de sugerencia de Python 3), es posible que desee cambiar al uso de io.BytesIO en lugar de StringIO, ya que Python 3 tiene que ver con los bytes. Otra ventaja es que las búsquedas explícitas no son necesarias con io.BytesIO antes de las lecturas (no he probado este comportamiento con HttpResponse de Django, así que dejé esa búsqueda final allí por si acaso).

 import io import zipfile zipped_file = io.BytesIO() with zipfile.ZipFile(zipped_file, 'w') as f: for i, file in enumerate(files): f.writestr("{}.csv".format(i), file.getvalue()) zipped_file.seek(0) 

El stdlib viene con el zipfile del módulo, y la clase principal, ZipFile , acepta un archivo u objeto similar a un archivo:

 from zipfile import ZipFile temp_file = StringIO.StringIO() zipped = ZipFile(temp_file, 'w') # create temp csv_files = [(name1, data1), (name2, data2), ... ] for name, data in csv_files: data.seek(0) zipped.writestr(name, data.read()) zipped.close() temp_file.seek(0) # etc. etc. 

No soy un usuario de StringIO así que puedo seek y read fuera de lugar, pero espero que tengas la idea.

 def zipFiles(files): outfile = StringIO() # io.BytesIO() for python 3 with zipfile.ZipFile(outfile, 'w') as zf: for n, f in enumarate(files): zf.writestr("{}.csv".format(n), f.getvalue()) return outfile.getvalue() zipped_file = zip_files(myfiles) response = HttpResponse(zipped_file, content_type='application/octet-stream') response['Content-Disposition'] = 'attachment; filename=my_file.zip' 

StringIO tiene el método getvalue que devuelve todo el contenido. Puede comprimir el archivo zip por zipfile.ZipFile(outfile, 'w', zipfile.ZIP_DEFLATED) . El valor predeterminado de compresión es ZIP_STORED que creará un archivo zip sin comprimir.