Boto3 で S3 のオブジェクトを操作する(高レベルAPIと低レベルAPI)


AWS SDK for Python である Boto3 について、改めて ドキュメント を見ながら使い方を調べてみた。

動作環境

PyPIのページ によると、2系であれば2.6以上、3系では3.3以上で動作するとのこと。

以下は

  • Python 3.4.3
  • Boto3 1.1.3

の環境で動作確認している。

Boto3 の構成

What's New のページにある "Major Features" の項には、次の5つの機能について概要が記載されている。

  • Resources : 高レベルなオブジェクト指向インターフェース
  • Collections : 複数のリソースを操作するイテレータ
  • Clients : 低レベルなサービス接続
  • Paginators : 自動的なページング
  • Waiters : 一定の状態に達するまで待機

自分はこの構成を理解できておらず、いままで ResourcesClients を混同してしまっていた。

なお、高レベルなAPIはすべての AWS サービスで使えるわけではなく、今のところ EC2 や S3 など一部のサービスでしか対応していないようだ。

インストールと事前準備

pip で簡単にインストールできる。

$ pip install boto3

AWS の操作には IAM のアクセスキーが必要なので、あらかじめマネージメントコンソールから作成して、ユーザーに適切なアクセス許可を設定しておく。

端末側ではアクセスキーの情報を ~/.aws/credentials に設定する。
awscli を使っているのであれば aws configure をした際にすでにこのファイルが生成されているが、そうでない場合は awscli のセットアップをするか、 credentials ファイルに直接アクセスキーを設定する。

~/.aws/credentials
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY

高レベルAPIでS3バケットへアクセスする

S3.Bucket オブジェクトを通して、バケットへアクセスすることができる。

import boto3

# バケット名
AWS_S3_BUCKET_NAME = 'hogehoge'

s3 = boto3.resource('s3')
bucket = s3.Bucket(AWS_S3_BUCKET_NAME)

print(bucket.name)
# => hogehoge

属性 objects を通して、バケットに保存されているS3オブジェクトの情報にアクセスできる。

この属性は Bucket.objectsCollectionManager クラスのインスタンスで、 all(), delete(), filter(), limit(), page_size() のメソッドが利用できる。これらのメソッドは s3.Bucket.objectsCollection クラスのインスタンスを返し、このオブジェクトをイテレートすることで ObjectSummary クラスのインスタンスを得ることができる。

print(bucket.objects.all())
# => s3.Bucket.objectsCollection(s3.Bucket(name='hogehoge'), s3.ObjectSummary)

print([obj_summary.key for obj_summary in bucket.objects.all()])
# => ['hayabusa.txt']

objects を使った操作は、バケットに保存されているオブジェクトを探す場合など対象のオブジェクトが特定されていない場合に有効である。

高レベルAPIでS3バケットからオブジェクトを取得する

キーがわかっているS3オブジェクトを取得する場合は、 S3.Object クラスを使う。

GET_OBJECT_KEY_NAME = 'hayabusa.txt'

obj = bucket.Object(GET_OBJECT_KEY_NAME)

print(obj.key)
# => hayabusa.txt

Object オブジェクトは Bucket オブジェクトを介さずにバケット名とキー名を指定することで生成することもできる。

obj = s3.Object(AWS_S3_BUCKET_NAME, GET_OBJECT_KEY_NAME)

print(obj.key)
# => hayabusa.txt

S3オブジェクトの中身を取得するには、オブジェクトの get() メソッドを使用する。

get() メソッドの戻り値は辞書で、その辞書の中にある Body を通してオブジェクトの中身を参照することができる。

この Bodybotocore.response.StreamingBody クラスのインスタンスで、バイト型データを扱うストリームとなっている。そのため、文字列として扱うためにはストリームから読み込んで文字列型に変換する必要がある。

response = obj.get()
body = response['Body'].read()

print(type(body))
# => <class 'bytes'>

print(body.decode('utf-8'))
# => 東京〜新函館北斗 10往復
# 仙台〜新函館北斗 1往復

なお、ストリームはいったん read() してしまうとストリームの末尾にシークされてしまうため、2回目以降の呼び出しでは結果を取得できないので注意が必要である。

高レベルAPIでS3バケットにオブジェクトを追加する

取得と同様、 Object オブジェクトを使うことで、新規にバケットへS3オブジェクトを追加したり、中身を更新したりすることができる。

S3オブジェクトの中身を設定するには、 put() メソッドの引数 Body に保存したい内容をバイト列として渡せばよい。 ACLContentType など、細かなオプションを引数で指定することもできる。

PUT_OBJECT_KEY_NAME = 'hayate.txt'

obj = bucket.Object(PUT_OBJECT_KEY_NAME)

body = """盛岡〜新函館北斗 1往復
新青森〜新函館北斗 1往復
"""

response = obj.put(
    Body=body.encode('utf-8'),
    ContentEncoding='utf-8',
    ContentType='text/plane'
)

低レベルAPIを使った操作

S3.Client オブジェクトを使うことで、低レベルなAPIを使用した操作も可能である。

例えば、S3オブジェクトの取得は低レベルAPIを使って次のように書くこともできる。

s3 = boto3.resource('s3')
client = s3.meta.client

response = client.get_object(Bucket=AWS_S3_BUCKET_NAME, Key=GET_OBJECT_KEY_NAME)
body = response['Body'].read()

print(body.decode('utf-8'))
# => 東京〜新函館北斗 10往復
# 仙台〜新函館北斗 1往復

まとめ

いままでは低レベルなAPI( Clients )と高レベルなAPI( Resources )を混同して使用していた。

低レベルAPIでしか提供されていない機能も一部あるが、オブジェクト指向なプログラムを書くことができるので高レベルなAPIがあるのであればそちらを使用した方がいいだろう。