Как сжать при загрузке в S3 с помощью Boto
У меня есть большой локальный файл. Я хочу загрузить сжатую версию этого файла в S3, используя boto
библиотека. Файл слишком велик, чтобы его можно было эффективно сжать на диске перед загрузкой, поэтому он должен быть разархивирован во время загрузки.
boto
библиотека знает функцию set_contents_from_file()
который ожидает файл-подобный объект, из которого это будет читать.
gzip
библиотека знает класс GzipFile
который может получить объект через параметр с именем fileobj
; он будет писать в этот объект при сжатии.
Я хотел бы объединить эти две функции, но один API хочет читать сам, другой API хочет писать сам; ни один из них не знает пассивной операции (например, запись в нее или чтение из нее).
У кого-нибудь есть идеи о том, как объединить их в рабочем режиме?
РЕДАКТИРОВАТЬ: я принял один ответ (см. Ниже), потому что он подсказал мне, куда идти, но если у вас есть та же проблема, вы могли бы найти мой собственный ответ (также ниже) более полезным, потому что я реализовал решение с использованием многочастных загрузок в нем,
2 ответа
На самом деле нет способа сделать это, потому что S3 не поддерживает истинный потоковый ввод (то есть кодирование передачи по частям). Вы должны знать Content-Length перед загрузкой и единственный способ узнать, что сначала нужно выполнить операцию gzip.
Я реализовал решение, на которое намекают в комментариях принятый ответ от 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 МБ, но кого это волнует? Пока мы не говорим о нескольких кусках ГБ, я в порядке с этим.
Обновление для 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()
Вы также можете легко сжать байты с помощью 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, pickle dump, файлами и т. д.
Если вы хотите загрузить сжатый Json, вот хороший пример: Загрузить сжатый Json в S3