Как gzip при загрузке в s3 с помощью boto

У меня есть большой локальный файл. Я хочу загрузить gzipped версию этого файла в S3, используя библиотеку boto. Файл слишком большой, чтобы эффективно загружать его на диск перед загрузкой, поэтому при загрузке он должен быть gzip потоковым способом.

Библиотека boto знает функцию set_contents_from_file(), которая ожидает файл-подобный объект, который он будет читать.

Библиотека gzip знает класс GzipFile, который может получить объект через параметр с именем fileobj; он будет записывать этот объект при сжатии.

Я хотел бы объединить эти две функции, но один API хочет читать сам по себе, другой API хочет писать сам по себе; не знает пассивной операции (например, записывается или читается).

Есть ли у кого-нибудь идея о том, как объединить их в рабочем режиме?

EDIT: я принял один ответ (см. ниже), потому что он намекнул мне, куда идти, но если у вас есть такая же проблема, вы можете найти мой собственный ответ (также ниже) более полезным, потому что я реализовал решение с использованием multipart загружает в нее.

Ответ 1

На самом деле это не так, потому что S3 не поддерживает истинный потоковый ввод (т.е. закодированное кодирование передачи). Вы должны знать Content-Length перед загрузкой и единственный способ узнать, что нужно выполнить операцию gzip в первую очередь.

Ответ 2

Я реализовал решение, намеченное в комментариях принятого ответа garnaat:

import cStringIO
import gzip

def sendFileGz(bucket, key, fileName, suffix='.gz'):
    key += suffix
    mpu = bucket.initiate_multipart_upload(key)
    stream = cStringIO.StringIO()
    compressor = gzip.GzipFile(fileobj=stream, mode='w')

    def uploadPart(partCount=[0]):
        partCount[0] += 1
        stream.seek(0)
        mpu.upload_part_from_file(stream, partCount[0])
        stream.seek(0)
        stream.truncate()

    with file(fileName) as inputFile:
        while True:  # until EOF
            chunk = inputFile.read(8192)
            if not chunk:  # EOF?
                compressor.close()
                uploadPart()
                mpu.complete_upload()
                break
            compressor.write(chunk)
            if stream.tell() > 10<<20:  # min size for multipart upload is 5242880
                uploadPart()

Кажется, что это работает без проблем. И в конце концов, потоковая передача в большинстве случаев просто фрагментирует данные. В этом случае куски размером около 10 МБ, но кому это нужно? Пока мы не говорим о нескольких блоках GB, я в порядке с этим.


Обновление для Python 3:

from io import BytesIO
import gzip

def sendFileGz(bucket, key, fileName, suffix='.gz'):
    key += suffix
    mpu = bucket.initiate_multipart_upload(key)
    stream = BytesIO()
    compressor = gzip.GzipFile(fileobj=stream, mode='w')

    def uploadPart(partCount=[0]):
        partCount[0] += 1
        stream.seek(0)
        mpu.upload_part_from_file(stream, partCount[0])
        stream.seek(0)
        stream.truncate()

    with open(fileName, "rb") as inputFile:
        while True:  # until EOF
            chunk = inputFile.read(8192)
            if not chunk:  # EOF?
                compressor.close()
                uploadPart()
                mpu.complete_upload()
                break
            compressor.write(chunk)
            if stream.tell() > 10<<20:  # min size for multipart upload is 5242880
                uploadPart()

Ответ 3

Вы также можете легко сжать байты с помощью gzip и легко загрузить их следующим образом:

import gzip
import boto3

cred = boto3.Session().get_credentials()

s3client = boto3.client('s3',
                            aws_access_key_id=cred.access_key,
                            aws_secret_access_key=cred.secret_key,
                            aws_session_token=cred.token
                            )

bucketname = 'my-bucket-name'      
key = 'filename.gz'  

s_in = b"Lots of content here"
gzip_object = gzip.compress(s_in)

s3client.put_object(Bucket=bucket, Body=gzip_object, Key=key)

Можно заменить s_in любыми байтами, io.BytesIO, дампами, файлами и т.д.

Если вы хотите загрузить сжатый Json, вот хороший пример: Загрузить сжатый Json в S3