boto3を使ったソースコードを読んでいると、
boto3.client(“サービス名”) と使っているものと、boto3.resource(“サービス名”) と使っているものがあり、
自分でも無意識に使い分けていたことに気づいたのでこれらの違いについて調べてみました。
ドキュメントを探すと、次のページに書いてありました。
参照: AWS SDK for Python | AWS
Boto3 には、2 つの異なるレベルの API があります。クライアント(「低レベル」)API では、下層の HTTP API 操作との 1 対 1 のマッピングが提供されます。 リソース API では、明示的なネットワーク呼び出しが表示されず、属性にアクセスしアクションを実行するためのリソースオブジェクトとコレクションが提供されます。
ちょっとわかりにくいですね。実際に動かしてみた違いから考えると、
クライアント API(boto3.clientの方)は、AWSの各リソースが持っているREST APIと1対1に対応した単純なPythonラッパーのようです。
それに対して、リソースAPI(boto3.resourceの方)はAWSのリソースをオブジェクト指向のプログラムで操作できるようにしたもののようです。
実際に動かしてみましょう。
EC2のインスタンスのインスタンスIDとインスタンスタイプ、現在の動作状況を一覧取得するプログラムをそれぞれ書いてみます。
まず、クライアントAPIの方です。
インスタンスの一覧は、describe_instances()で取得できます。
まず、単純に結果を表示すると、結果が辞書型で得られていることが確認できます。 (すごく長いので省略しています)
import boto3
ec2_client = boto3.client("ec2")
result = ec2_client.describe_instances()
print(result)
# 以下出力
{
'Reservations': [
{
'Groups': [],
'Instances': [
{
'AmiLaunchIndex': 0,
'ImageId': 'ami-da9e2cbc',
'InstanceId': '{1つ目のインスタンスID}',
'InstanceType': 't1.micro',
'KeyName': '{キーファイルの名前}',
'LaunchTime': datetime.datetime(2021, 3, 20, 5, 35, 47, tzinfo=tzutc()),
'Monitoring': {'State': 'enabled'},
'Placement': {'AvailabilityZone': 'ap-northeast-1a',
'GroupName': '',
'Tenancy': 'default'
},
# 中略
'ResponseMetadata': {
'RequestId': '1b6c171d-d199-46c0-b2a6-7037fcfda28b',
'HTTPStatusCode': 200,
'HTTPHeaders': {
'x-amzn-requestid': '1b6c171d-d199-46c0-b2a6-7037fcfda28b',
'cache-control': 'no-cache, no-store',
'strict-transport-security': 'max-age=31536000; includeSubDomains',
'content-type': 'text/xml;charset=UTF-8',
'transfer-encoding': 'chunked',
'vary': 'accept-encoding',
'date': 'Sun, 23 May 2021 08:21:51 GMT',
'server': 'AmazonEC2'
},
'RetryAttempts': 0
}
}
この巨大な辞書ファイルの中から必要な情報を取り出します。
for r in result["Reservations"]:
print(r["Instances"][0]["InstanceId"])
print(r["Instances"][0]["InstanceType"])
print(r["Instances"][0]["State"])
print(" - ・"*10)
"""
{1つ目のインスタンスID}
t1.micro
{'Code': 80, 'Name': 'stopped'}
- ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・
{2つ目のインスタンスID}
t2.nano
{'Code': 16, 'Name': 'running'}
- ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・
"""
上のコードを見ていただければわかる通り、単純な辞書の巨大な塊なのでちょっと情報が取り出しにくいのがわかると思います。
一方で、リソースAPIではどうでしょうか。
こちらはインスタンスの一覧は、ec2_resource.instances.all()でそれらのイテレーターが取得できます。
そして、インスタンスIDやインスタンスタイプ、状態などは属性として取得できます。
実際に先程のクライアントAPIと同様の情報を取得してみましょう。
ec2_resource = boto3.resource("ec2")
for instance in ec2_resource.instances.all():
print(instance.instance_id)
print(instance.instance_type)
print(instance.public_ip_address)
print(instance.state)
print(" - ・"*10)
"""
{1つ目のインスタンスID}
t1.micro
None
{'Code': 80, 'Name': 'stopped'}
- ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・
{2つ目のインスタンスID}
t2.nano
54.199.43.209
{'Code': 16, 'Name': 'running'}
- ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・ - ・
"""
得られる結果は同じですが、コードがずいぶんわかりやすいのが実感していただけると思います。
さて、結論としてどっちを使えばいいのか、という話ですが、
実現したい操作がリソースAPIでできるのであればリソースAPIを使えばいいのではないかなと思いました。
ただ、提供されているREST APIと1対1に対応しているクライアントAPIと違って、
リソースAPIは全ての操作が実現できると保証されているものではありません。
たとえば、翻訳サービスである、Translateなどは、クライアントAPIしか存在せず、リソースAPIで使おうとするとエラーになります。
try:
boto3.resource("translate")
except Exception as e:
print(e)
"""
The 'translate' resource does not exist.
The available resources are:
- cloudformation
- cloudwatch
- dynamodb
- ec2
- glacier
- iam
- opsworks
- s3
- sns
- sqs
Consider using a boto3.client('translate') instead of a resource for 'translate'
"""
boto3.client(‘translate’) 使えって言われてますね。
このように、リソースAPIが未対応の時は、諦めてクライアントAPIを使いましょう。