Отправка нескольких.CSV-файлов.ZIP без сохранения на диске в Python


Я работаю над приложением для отчетов для моего веб-сайта на базе Django. Я хочу запустить несколько отчетов, и каждый отчет должен генерировать a .csv-файл в памяти, который можно загрузить в пакетном виде .застежка-молния. Я хотел бы сделать это без сохранения каких-либо файлов на диске. До сих пор, чтобы генерировать один .CSV-файл, я после операции:

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'
Это прекрасно работает, но только для одного, расстегнутого .csv. Если бы у меня был, например, список .csv файлы, созданные с помощью 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]

Как я могу вернуть сжатый файл, который содержит все объекты в myFiles и может быть правильно распакован, чтобы показать три .файлы csv?

3 8

3 ответа:

Zipfile - это стандартный библиотечный модуль, который делает именно то, что вы ищете. Для вашего случая использования, мясо и картофель-это метод под названием "writestr", который принимает имя файла и содержащиеся в нем данные, которые вы хотели бы заархивировать.

В приведенном ниже коде я использовал последовательную схему именования файлов, когда они распаковываются, но это может быть переключено на все, что вы хотите.

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)

Если вы хотите, чтобы ваш код был защищен в будущем (hint hint Python 3 hint hint), вы возможно, вы захотите переключиться на использование ввода-вывода.BytesIO вместо StringIO, так как Python 3-это все о байтах. Еще один бонус заключается в том, что явные запросы не нужны с io.BytesIO перед чтением (я не проверял это поведение с помощью HttpResponse от Django, поэтому я оставил этот последний поиск там на всякий случай).

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)

stdlib поставляется с модулем zipfile, а основной класс ZipFile принимает файл или файлоподобный объект:

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.

Я не являюсь пользователем StringIO, поэтому у меня могут быть seek и read неуместные, но, надеюсь, вы понимаете идею.

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 имеет метод getvalue, который возвращает все содержимое. Вы можете сжать файл zipfile по zipfile.ZipFile(outfile, 'w', zipfile.ZIP_DEFLATED). Значение сжатия по умолчанию - ZIP_STORED, которое создаст zip-файл без сжатия.