Python(boto3)でS3にデータをファイル保存せず直接アップロードする方法

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、臼田です。

Pythonを利用してS3にデータをアップロードする際、boto3を利用することになると思いますが、検索するとファイルからアップロードする方法がいっぱい出てきます。

でも、私はスクリプトの中で作成したjsonデータを直接S3に格納したかったんです。

なぜなら、Lambdaで処理したデータをjsonにして格納することが目的だったので、一時的にファイルで保存するなんてことは考えられないからです。

boto3の事、よくわかっていなくてハマってしまったので共有したいと思います。

執筆時のboto3のバージョンは1.4.4です。

PythonはLambda前提の2.7です。

S3へ直接保存する方法

boto3のドキュメントのclass S3.Objectのput()メソッドには、下記のように記載があります。(ver.1.4.4現在)

Request Syntax

response = object.put(
    ACL='private'|'public-read'|'public-read-write'|'authenticated-read'|'aws-exec-read'|'bucket-owner-read'|'bucket-owner-full-control',
    Body=b'bytes'|file,
……省略……

これは、Bodyにはfile objectかbytes型を指定すると書かれているように見受けられます。

大方の検索結果には、open()を利用したFileStreamで参考例が上がっていました。

bytesの方ですが、実際にはPython2.7ではbytes型は存在していないので、bytearray型になるかと思います。(私はここでしばらくハマりました)

一見するとjsonをbytearrayに変換して流し込んであげればいいので、下記のように書いてみました。

# test.py
import json
import boto3

bucket_name = "test-bucket"
json_key = "test.json"
s3 = boto3.resource('s3')
obj = s3.Object(bucket_name,json_key)

test_json = {'key': 'value'}
r = obj.put(Body = bytearray(json.dumps(test_json)))

# get json data
print obj.get()['Body'].read()

S3.Object.put()にbytearrayを渡してあげると、下記のように正常にjsonデータが格納されました。

$ python test.py
{"key": "value"}

しかし、これは最適な解ではありませんでした。

S3にjsonを渡すときの最適解

うっかりjson.dumps()ではなくdictのままデータを渡したときにそれは起こりました。

botocore.exceptions.ParamValidationError: Parameter validation failed:
Invalid type for parameter Body, value: {}, type: <type 'dict'>, valid types: <type 'str'>, <type 'bytearray'>, file-like object

ドキュメントにはなかった<type 'str'>の文字が…

というわけで、わざわざbytearrayに入れなくても、下記のような形で直接データを送れます。

# test2.py
import json
import boto3

bucket_name = "test-bucket"
json_key = "test.json"
s3 = boto3.resource('s3')
obj = s3.Object(bucket_name,json_key)

test_json = {'key': 'value'}
r = obj.put(Body = json.dumps(test_json))

# get json data
print obj.get()['Body'].read()
$ python test2.py
{"key": "value"}

それでは、よいboto3ライフを