Как выполнять потоковую передачу из ZipFile? Как заархивировать «на лету»?

#python #stream #aws-lambda #zip

#python #поток #aws-lambda #zip

Вопрос:

Я хочу заархивировать поток и передать результат. Я делаю это с помощью AWS Lambda, что имеет значение в смысле доступного дискового пространства и других ограничений. Я собираюсь использовать архивированный поток для записи объекта AWS S3 с помощью upload_fileobj() или put() , если это имеет значение.

Я могу создавать архив в виде файла, пока у меня не появятся небольшие объекты:

 import zipfile
zf = zipfile.ZipFile("/tmp/byte.zip", "w")
zf.writestr(filename, my_stream.read())
zf.close()
  

Для большого объема данных я могу создать объект вместо файла:

 from io import BytesIO
...
byte = BytesIO()
zf = zipfile.ZipFile(byte, "w")
....
  

но как я могу передать заархивированный поток на вывод? Если я использую zf.close() — поток будет закрыт, если я его не использую — архив будет неполным.

Ответ №1:

Вместо использования zipfile, встроенного в Python, вы можете использовать stream-zip (полное раскрытие: написано мной)

Если у вас есть итерация байтов, my_data_iter скажем, вы можете получить итерацию zip-файла, используя его stream_zip функцию:

 from datetime import datetime
from stream_zip import stream_zip, ZIP_64

def files():
    modified_at = datetime.now()
    perms = 0o600
    yield 'my-file-1.txt', modified_at, perms, ZIP_64, my_data_iter

my_zip_iter = stream_zip(files())
  

Если вам нужен объект, подобный файлу, скажем, для передачи в upload_fileobj boto3, вы можете конвертировать из iterable с помощью функции преобразования:

 def to_file_like_obj(iterable):
    chunk = b''
    offset = 0
    it = iter(iterable)

    def up_to_iter(size):
        nonlocal chunk, offset

        while size:
            if offset == len(chunk):
                try:
                    chunk = next(it)
                except StopIteration:
                    break
                else:
                    offset = 0
            to_yield = min(size, len(chunk) - offset)
            offset = offset   to_yield
            size -= to_yield
            yield chunk[offset - to_yield:offset]

    class FileLikeObj:
        def read(self, size=-1):
            return b''.join(up_to_iter(float('inf') if size is None or size < 0 else size))

    return FileLikeObj()

my_file_like_obj = to_file_like_obj(my_zip_iter)
  

Комментарии:

1. Спасибо, что написали эту библиотеку, она очень полезная и именно то, что я искал!

Ответ №2:

Возможно, вы захотите попробовать zipstream версию zipfile. Например, сжать stdin в stdout в виде zip-файла, содержащего данные в виде файла с именем TheLogFile , используя итераторы:

 #!/usr/bin/python3
import sys, zipstream
with zipstream.ZipFile(mode='w', compression=zipstream.ZIP_DEFLATED) as z:
    z.write_iter('TheLogFile', sys.stdin.buffer)
    for chunk in z:
        sys.stdout.buffer.write(chunk)
  

Комментарии:

1. Ключ здесь «хранение данных в виде файла». Я не хочу использовать файл из-за ограничений среды. Как же тогда это должно выглядеть?

2. Я не совсем ясно выразился. Я просто имел в виду, что конечный результат — это поток, который, если бы вы сохранили его в файл, выглядел бы как zip-файл. Если бы вы разархивировали его, вы бы получили файл с именем TheLogFile , содержащий любые данные, которые вы прочитали из stdin. Единственным файлом является номинальный файл, который является частью формата выходного потока. Посмотрите на webpy пример в конце ссылки, так как это похоже на вашу ситуацию.

3. понял, спасибо вам. Другой вопрос: похоже, что отступ немного неаккуратен, with zipstream ... содержит только z.write_iter... only или for chunk... слишком?