Boto3(AWS SDK for Python)をコードリーディングして仕組みを理解する

32 min read読了の目安(約29100字

はじめに

Boto3を利用する機会があったので、どのように動いているか気になったのでコードを読んでみた。その中で自分のための情報の整理や、実装が面白いな・なるほどなと感じた部分をまとめる。
Boto3を使っていAWSの開発や、AWSのAPIの理解などの役に立てば嬉しい。

筆者が解釈した内容なので間違っている場合や、最新バージョンで実装が変わっている場合があるという点はご了承ください。

Boto3とは

Boto3とは AWS SDK for Python のことで、内部的にはAWS CLIでも利用されているBotocoreを利用している。

The SDK is composed of two key Python packages: Botocore (the library providing the low-level functionality shared between the Python SDK and the AWS CLI) and Boto3 (the package implementing the Python SDK itself).

そのため今回はBoto3とBotocoreのコードを調査対象とする。

調査対象

今回利用するバージョンは調査を開始した2021/04/25時点での最新版のこちら。この後出てくるコードの行数の指定などは全てこのバージョンのものが適用される。

Package Version
boto3 1.17.57
botocore 1.20.57

Boto3の2つのAPI、クライアントAPIとリソースAPIについて

Boto3を利用してAWSの操作をする場合、リソースAPIとクライアント(「低レベル」)APIが存在するため2通りの使い方がある。
それぞれでS3のListBucketsを呼び出して結果を表示するコードを比較すると以下のような特徴がある。

client API
s3_client = boto3.client('s3')
response = s3_client.list_buckets()
for b in response['Buckets']:
  print('bucket name:', b['Name'], ', created at:', b['CreationDate'])
resource API
s3_resource = boto3.resource('s3')
buckets = s3_resource.buckets
for b in buckets.all():
  print('bucket name:', b.name, ', created at:', b.creation_date)
- クライアントAPI リソースAPI
特徴 boto3.client('<SERVICE_NAME>')を利用。全てのAWSのAPIと1:1で対応し、API名とスネークケースにしたPythonのメソッド名に対応している(詳細) boto3.resource('<SERVICE_NAME>')を利用。AWSリソースをオブジェクト指向で表現している(詳細)
戻り値の型 dict型 対象のAWSリソースを抽象化したオブジェクト型

今回はそれぞれのAPIを利用する時に生成するオブジェクトを以下のように呼ぶこととして進める。

  • boto3.client('<SERVICE_NAME>')で取得できる値をClientオブジェクト
  • boto3.resource('<SERVICE_NAME>')で取得できる値をResourceオブジェクト

ちなみにドキュメントを読むとresourceclientを内包していると書かれており、以下のようにResourceオブジェクトからClientオブジェクトを取り出せる。

It is also possible to access the low-level client from an existing resource:

# Create the resource
sqs_resource = boto3.resource('sqs')

# Get the client from the resource
sqs = sqs_resource.meta.client

なのでまずはクライアントAPIの中身を読んでみて、その後にリソースAPI読むことでクライアントAPIとどう関係しているのか、どんな実装の違いがあるのかを調べていく╭(・ㅂ・)و

クライアントAPIのコードリーディング

今回はS3で利用できるAWS APIのListBucketsを実行する場合で、大まかな流れと要点をまとめた。

boto3.client('s3')によるClientオブジェクトの生成

クライアントAPIを実行するため、対象サービス(今回はS3)のClientオブジェクトを生成する部分。この内部で行われている処理の中で重要な役割を果たすオブジェクトを紹介しながら解説する。

boto3.session.Sessionオブジェクト

Clientオブジェクトを生成するために必要なclientメソッドを持っているため、まず最初に生成されるオブジェクト。

boto3.__init__.py#L87-L93の一部を抜粋してコメントを追加
def client(*args, **kwargs):
    # Clientオブジェクトを生成する前に_get_default_session()でSessionオブジェクトを生成している
    return _get_default_session().client(*args, **kwargs)
  • clientメソッドは、内包しているbotocore.session.Sessionオブジェクトのcreate_clientメソッドを呼び出すだけであり、create_clientが最終的にClientオブジェクトの生成処理をしている
  • つまりboto3側のSessionbotocore側のSessionを薄くラップしていてるだけ

botocore.credentials.Credentialsオブジェクト

IAMロールや~/.aws/credentialsのいずれかから得られたクレデンシャル情報を保持しているオブジェクト。

botocore.credentials.py#L1967-L1977の一部を抜粋してコメントを追加
def load_credentials(self):
    ### 省略 ###
    # 複数のProviderが優先順位を持ちリストになっている
    for provider in self.providers:
        # 提供されているProviderからCredentialsが取得できるかを上から順に確認
        creds = provider.load()
        if creds is not None:
            # 一番最初に取得できたCredentialsを利用する
            return creds

このXXXProviderのリストの順序はこちら↓↓↓

#index botocore.credentials.XXXProvider
0 EnvProvider
1 AssumeRoleProvider
2 AssumeRoleWithWebIdentityProvider
3 SSOProvider
4 SharedCredentialProvider
5 ProcessProvider
6 ConfigProvider
7 OriginalEC2Provider
8 BotoProvider
9 ContainerProvider
10 InstanceMetadataProvider

SDKドキュメントの順序は以下の通りで、1., 2.はオブジェクト生成時に明示的に指定した場合に優先されるので3.以降が↑のリストの順番通りになるかと思いきや、XXXProviderの順番だと"Shared credential file"や""AWS config fileより"Assume Role"の方が優先されてそうで謎…ここはもう少し調べたい🔎

  1. Passing credentials as parameters in the boto.client() method
  2. Passing credentials as parameters when creating a Session object
  3. Environment variables
  4. Shared credential file (~/.aws/credentials)
  5. AWS config file (~/.aws/config)
  6. Assume Role provider
  7. Boto2 config file (/etc/boto.cfg and ~/.boto)
  8. Instance metadata service on an Amazon EC2 instance that has an IAM role configured.

botocore.model.ServiceModelオブジェクト

クライアントAPI作成対象サービス(今回の例であればS3)のAWS APIの一覧情報を保持するオブジェクトで、後述するがClientオブジェクトのメソッド定義はこの情報から作られる。

botocore.client.py#L119-L122の一部を抜粋してコメントを追加
def _load_service_model(self, service_name, api_version=None):
    # 対象サービスのAPIが定義されたjsonファイルをdictに変換
    json_model = self._loader.load_service_model(service_name, 'service-2', api_version=api_version)
    # サービス名やAPI定義のdictなどを保持した`ServiceModel`クラスのオブジェクトにする
    service_model = ServiceModel(json_model, service_name=service_name)
  • 一覧情報はClientCreator#_load_service_modelで対象サービスのAWS APIが定義されたjsonファイルを読み込み、dictに変換することで実現している

読み込んでdictにしているjsonファイルの大まかな構成要素はこちら。(要素名のリンクや値の例はS3のservice-2.json

要素名 役割 値の例
metadata サービスのAPIエンドポイント、プロトコルなどの子要素を持つ "metadata":{ "globalEndpoint":"s3.amazonaws.com", "protocol":"rest-xml", ...}
operations AWS API名一覧と、各AWS APIのHTTPメソッド・パス・リクエスト/レスポンスボディの構成定義を示すキー(shape)などの子要素を持つ "operations":{ "AbortMultipartUpload":{ "name":"AbortMultipartUpload", "http":{ "method":"DELETE", "requestUri":"/{Bucket}/{Key+}", "responseCode":204 }, "input":{"shape":"AbortMultipartUploadRequest"}, "output":{"shape":"AbortMultipartUploadOutput"} }, "CompleteMultipartUpload":{...}, ...}
shape リクエスト/レスポンスボディの構成要素の定義(XXXRequest, XXXOutput)と、その要素(member)の型定義が羅列されていて、例えばXXXOutput->members[]->${MEMBER_NAME}->shape->${MEMBER_DEFINITION}のような階層になっている "shapes":{ "AbortMultipartUploadRequest":{ "type":"structure", "required":[ "Bucket", "Key", "UploadId" ], "members":{ "Bucket":{ "shape":"BucketName", "documentation":"<p>The bucket nam....</p>", "location":"uri", "locationName":"Bucket" }, "Key":{ "shape":"ObjectKey", ...}, ...}, ...}, "AbortRuleId":{"type":"string"}, ...}

Clientオブジェクト

前述したがあらためて、boto3.client('s3')で生成される、AWS APIリクエストを実行できるメソッドを持っているオブジェクト。

引数 説明
str(class_name) 動的に定義されるクラス名、S3の場合は'S3'が入る
tuple(bases) 動的に定義されるクラスの親クラスで、ここではbotocore.client.BaseClientが設定される
class_attributes 動的に定義されるクラスのメソッド名やインスタンス変数名をkey, 実体をvalueとしたdictで、ServiceModelの情報を元にスネークケースのメソッド名をAWS API呼び出しができるメソッドに対応させた形式になっている

より細かい話をすると、class_attributesは、_create_methodsにより作成されるdictで、対象サービスのAPI名をスネークケースにした文字列をkeyとして、_create_api_method内で動的に作成する[_api_callメソッドオブジェクトをvalue]にする(https://github.com/boto/botocore/blob/1.20.57/botocore/client.py#L347-L372)ことで、Clientオブジェクトでメソッド呼び出しするとAPIが実行できるようになっている。
メタプログラミングのテクニックをうまく使っていて、なるほどなと感じた👏


ここで今までの情報を整理し、Clientオブジェクトが生成されているbotocore.client.pyの流れを説明すると↓↓↓

botocore.client.py#L80-L88の一部を抜粋してコメントを追加
# ServiceModelオブジェクトを生成(今回はS3のAPI一覧などの情報を持っている)
service_model = self._load_service_model(service_name, api_version)
# この中でtype()を利用して動的にクラス定義(clsにClientクラスのメタ情報が入る)
cls = self._create_client_class(service_name, service_model)
### 3行省略 ###
# コンストラクタに渡すためのCredentialやConfigなどをまとめる
client_args = self._get_client_args(
    service_model, region_name, is_secure, endpoint_url,
    verify, credentials, scoped_config, client_config, endpoint_bridge)
# 動的クラスからオブジェクト生成 == 今回であればS3のClientオブジェクト
service_client = cls(**client_args)

ここまでで、Clientオブジェクトの生成処理が完了!!

s3_client.list_buckets()でHTTPリクエストを送るまで

先ほど生成したClientオブジェクトのメソッドを実行すると、どのように対応するAWS API呼び出しにバインドしているかについての解説。

botocore.client.py#L349-L357の一部を抜粋してコメントを追加
# operation_name='ListBuckets', py_operation_name='list_buckets'のような値が入る
# _create_api_method自体はclass_attributesを作るときにすでに呼ばれた部分
def _create_api_method(self, py_operation_name, operation_name,
                        service_model):
    # ★呼び出されるのはここで、kwargsはクライアントAPI呼び出し時の引数のdict
    def _api_call(self, *args, **kwargs):
        if args:
            raise TypeError(
                "%s() only accepts keyword arguments." % py_operation_name)
        # 処理の実装はさらにこの中
        return self._make_api_call(operation_name, kwargs)
  • list_bucketすると、動的なクラス定義で利用したclass_attributesのdictの情報を利用することでoperation_name='ListBuckets'の状態で_api_callが呼ばれる
  • kwargsは引数をdictにしたものが入る
    • list_buckets()の場合は引数が空なので、空のdict
    • 引数があるAPI、例えばS3のGetObjectであれば、呼び出し方はs3_client.get_object(Bucket='examplebucket', Key='HappyFace.jpg')であるためkwargs{'Bucket': 'sugikei-sandbox', 'Key': 'HappyFace.jpg'}が入ってくる
  • _make_api_callでは変数のoperation_nameに設定されているAPI名で、API定義のjsonであるoperationsからHTTPリクエストに必要なパスやリクエストメソッドなどの情報(例えばListBucketsであればこの部分)を取り出してリクエストに必要なオブジェクトを生成し、_make_requestでAWS APIにHTTPリクエストを送信処理を呼ぶ

AWS APIのHTTPリクエストの実行ライブラリ・認証設定・レスポンスのパース

SDKがAWSの低レベルAPIと呼ばれるHTTPリクエストを、どのように肩代わりしてくれているのかを説明していく。

リクエスト送信処理と利用しているライブラリなどについて

  • リクエスト処理まわりを担うオブジェクトはbotocore.endpoint.Endpointで、Clientオブジェクト生成の時のcreate_endpointで設定される
    • ここで指定されているbotocore.httpsession.URLLib3Sessionが実際にHTTP通信をするオブジェクトや、エンドポイントのURL、レスポンスをパースするオブジェクトなどを保持している
    • コネクションプールの数やタイムアウトの設定もこの時点で設定される
  • _make_api_callから呼び出す_make_requestEndpointオブジェクトを利用して、リクエストヘッダやボディの生成・HTTPリクエストの送信・HTTPレスポンスボディのXMLをパースしてdict形式にする処理を行なっている

urllib3を利用する直前の処理がここ↓↓↓

botocore.httpsession.py##L306-L331
def send(self, request):
    try:
        # proxyやコネクションの設定
        proxy_url = self._proxy_config.proxy_url_for(request.url)
        manager = self._get_connection_manager(request.url, proxy_url)
        conn = manager.connection_from_url(request.url)
        self._setup_ssl_cert(conn, request.url, self._verify)

        request_target = self._get_request_target(request.url, proxy_url)
	# ここからurllib3でHTTPリクエスト
        urllib_response = conn.urlopen(method=request.method, url=request_target, body=request.body, headers=request.headers, retries=Retry(False), assert_same_host=False, preload_content=False, decode_content=False, chunked=self._chunked(request.headers))

Authorizationヘッダの生成

まずは署名バージョンを確認し、署名情報を計算するためのXXXAuthオブジェクトを取得する。

botocore.signer.py#L92-L162の一部を抜粋してコメントを追加
def sign(self, operation_name, request, region_name=None,
            signing_type='standard', expires_in=None, signing_name=None):
    # 署名バージョンの取得 ex) 'v4', 's3v4', ...
    signature_version = self._choose_signer(
        operation_name, signing_type, request.context)

    if signature_version != botocore.UNSIGNED:
        kwargs = {
            'signing_name': signing_name,
            'region_name': region_name,
            'signature_version': signature_version
        }
        try:
            # SigV4Auth, S3SigV4AuthなどのAuthオブジェクト取得
            auth = self.get_auth_instance(**kwargs)
        except UnknownSignatureVersionError as e:
            ### 省略 ###

        # 署名情報を計算して'Authorization'ヘッダへ設定
        auth.add_auth(request)

AuthオブジェクトとCredentialオブジェクトを利用して署名情報を計算し、Authorizationヘッダに設定する。

botocore.auth.py#L371-L387の一部を抜粋してコメントを追加
def add_auth(self, request):
    datetime_now = datetime.datetime.utcnow()
    request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP)
    # STSのトークンがあればここで'X-Amz-Security-Token'ヘッダ設定
    self._modify_request_before_signing(request)
    # リクエスト情報を文字列化
    canonical_request = self.canonical_request(request)
    # タイムスタンプとリクエスト情報のハッシュ値計算
    string_to_sign = self.string_to_sign(request, canonical_request)
    # ここまでの情報を合わせて署名情報の計算
    signature = self.signature(string_to_sign, request)

レスポンスボディのXMLのパース

ここでAWS APIの結果を取得するが、レスポンスボディはXMLである。SDKとしてPythonで利用しやすいようにdict形式にパース処理をしている部分を解説する。

botocore.endpoint.py#L185-L230の一部を抜粋してコメントを追加
def _do_get_response(self, request, operation_model):
### 省略 ###
    http_response = self._send(request)
    # header, bodyの親要素をdictにしただけで、まだここではレスポンスボディはXMLのまま
    response_dict = convert_to_response_dict(http_response, operation_model)
    # protocol='rest-xml'
    protocol = operation_model.metadata['protocol']
    # XML用のParserオブジェクト取得
    parser = self._response_parser_factory.create_parser(protocol)
    # ここでレスポンスボディのXMLをdictに変換
    parsed_response = parser.parse(
        response_dict, operation_model.output_shape)
  • 今回XMLのパースなのでParserはbotocore.parser.BaseXMLResponseParserが利用される
  • XML以外でもパースできるようになっており、プロトコルによりどのParserが利用されるかはbotocore.parser.pyのpydocでわかりやすいAAが書かれている
  • 変数のoutput_shapeはjsonで定義されているshapeのことで、対象のAWS APIのレスポンスの要素が定義情報が入っている
  • さらに追っていくと_handle_structureの中でXMLをdictにし、shapeに入っている要素と照らし合わせて最終的なレスポンスのdictに必要な要素だけが入るようにしている

クライアントAPIまとめ

クライアントAPIはboto3を使っているがほぼbotocore内で完結していた。(厳密にはboto3.Sessionbotocore.Sessionuser-agentなどの設定を少しだけいじってるようではある)

なので極論、クライアントAPIを利用するだけならboto3を使わずにbotocoreだけでも↓のようなコードを書けば可能。(※全ての操作を確認しているわけではないので注意)

botocore_client_sandbox.py
import botocore.session

# boto3#client('s3')した時にClientオブジェクト取得に必要な部分だけを抜粋
s3_botocore_client = botocore.session.get_session().create_client('s3')
botocore_response = s3_botocore_client.list_buckets()
for b in botocore_response['Buckets']:
  print('bucket name:', b['Name'], ', created at:', b['CreationDate'])

クライアントAPIの動きはだいたい把握できたので、リソースAPIは何をしているのかを見ていく─=≡Σ((( つ•̀ω•́)つ

リソースAPIのコードリーディング

こちらもS3で利用できるAWS APIのListBucketsを実行する場合で、大まかな流れと要点をまとめた。

boto3.resource('s3')によるResourceオブジェクトの生成

リソースAPIを実行するために(今回はS3)のResourceオブジェクトを生成する部分。ポイントとなるオブジェクトを紹介しながら解説する。

boto3.sessin.Sessionオブジェクト

ResourceオブジェクトはSessionresourceメソッドにより生成する。Clientオブジェクト生成もSessionを利用していたが、今回はboto3側でリソースAPI特有の処理を行なっている。

boto3.__init__.py#L96-L102の一部を抜粋してコメントを追加
def resource(*args, **kwargs):
# Resourceオブジェクトを生成する前に_get_default_session()でSessionオブジェクトを生成している
    return _get_default_session().resource(*args, **kwargs)

resources-1.jsonファイル

リソースAPIを定義するためのjsonファイルで、botocore.loader.Loader#load_service_modelでjsonファイルを探して読み込みしている

boto3.session.py#L339-L353の一部を抜粋してコメントを追加
try:
    # ここで'resources-1'を指定
    resource_model = self._loader.load_service_model(
        service_name, 'resources-1', api_version)
except UnknownServiceError:
    # サービスが存在しない場合はエラー
    raise ResourceNotExistsError(...
except DataNotFoundError:
    # 利用可能なAPIバージョンがない場合はエラー
    raise UnknownAPIVersionError(...
  • クライアントAPIと異なり、resources-1.jsonを指定して読み込んでいる
  • 指定したサービスのresources-1.jsonがロードできない場合、リソースAPIが使用できないと判定されてエラーになる
  • このjsonファイルが動的なクラス定義で利用される

読み込んでdictにしているjsonファイルの大まかな構成要素はこちら。(要素名のリンクや値の例はS3のresources-1.json)

要素名 役割 値の例
service Resourceオブジェクトが利用できるメソッドやプロパティの親要素 -
action Resourceオブジェクトが利用できるメソッド CreateBucketを定義したjson
has このjsonのresources要素と合わせて利用される(詳しい役割は読み解けなかった) Bucketを定義したjson
hasMany Resourceオブジェクトが利用できるプロパティ Bucketsを定義したjson
resources Resourceオブジェクトで生成できるオブジェクト Bucket, BucketPolicy, Object, ObjectVersionなどを定義したjson

Clientオブジェクト

クライアントAPIの時に利用したClientオブジェクトを生成して、リソースAPI内部で利用できるようにしている。生成方法はすでに説明したので省略。

boto3.resources.factory.ResourceFactoryオブジェクト

load_from_definitionで動的にResourceオブジェクトのクラス定義をする役割を持つ。

boto3.resources.factory.py#L42-L139の一部を抜粋してコメントを追加
def load_from_definition(self, resource_name,
                            single_resource_json_definition, service_context):
    # 'action'をクラス定義に組み込むための処理
    self._load_actions(attrs=attrs, ...
    # 'hasMany'をクラス定義に組み込むための処理
    self._load_collections(attrs=attrs, ...
    # 'resources'をクラス定義に組み込むための処理
    self._load_has_relations(attrs=attrs, ...
    # 親クラス
    base_classes = [ServiceResource]
    # 動的にクラスを定義
    return type(str(cls_name), tuple(base_classes), attrs)
  • _load_actionsresources-1.jsonaction要素をそれぞれ呼び出せるよう、do_actionメソッドにバインド
    • S3の場合create_bucketメソッドがここで作られる
    • 対応するクライアントAPIのメソッドに紐づけられている
  • _load_collectionsresources-1.jsonhasManyの要素をそれぞれPythonのpropertyとして呼び出せるようにしたget_collectionメソッドをバインド
    • get_collectionboto3.resources.CollectionManagerクラスを親にして動的に定義したクラスのオブジェクトを返すメソッド
    • S3の場合bucketsプロパティがここで作られる
  • _load_has_relationsresources-1.jsonresources要素をそれぞれcreate_resourceメソッドにバインド
    • S3の場合Bucket, BucketPolicy, Object, ObjectVersionなどなど
  • 最後にBuilt-in Functionsのtype()を利用して、対象サービスのResourceオブジェクトのクラスを定義

Resourceオブジェクト

ResourceFactoryによって定義されたクラスから生成される

引数 説明
str(class_name) 動的に定義されるクラス名、S3の場合は's3.ServiceResource'が入る
tuple(bases) 動的に定義されるクラスの親クラスで、ここではboto3.resources.base.ServiceResourceが設定される
class_attributes 動的に定義されるクラスのメソッド名やインスタンス変数名をkey, 実体をvalueとしたdictで、ResourceFactoryで作成した情報を元にリソースAPIのメソッドやプロパティ呼び出しができるようになっている

ここで今までの情報を整理し、Resourceオブジェクトが生成されているboto3.session.Session#resourceの流れを説明すると↓↓↓

boto3.session.py#L265-L409の一部を抜粋してコメントを追加
# jsonからリソースAPIの定義を読み込み
resource_model = self._loader.load_service_model(service_name, 'resources-1', api_version)
# Clientオブジェクト生成
client = self.client(...
# 動的なクラス定義
cls = self.resource_factory.load_from_definition(...
# Clientオブジェクトを引数にResourceオブジェクト生成
return cls(client=client)

API実行時のバインド

ResourceFactoryオブジェクトの部分で解説したが、action, collection(hasManyから定義されたproperty), resourceそれぞれでバインドされたメソッドが呼ばれるが、少しだけ補足する。

actionの呼び出し(do_action)

ServiceAction#__call__の中でgetattrを利用することで、Clientオブジェクトの対応するクライアントAPIを実行し、dictのレスポンスをリソースオブジェクトに変換する。

collectionの呼び出し(get_collection)

親クラスをCollectionManagerにしたクラスを動的に定義し、そこからXXXCollectionManagerオブジェクトを生成する。(S3のbucketsであればs3.bucketsCollectionManagerというクラス名)
XXXCollectionManagerallを実行した場合はResourceCollection#__iter__が呼ばれる。そのpageの中でgetattrを利用することで、Clientオブジェクトの対応するクライアントAPI(bucketsであればlist_buckets)を実行し、dictのレスポンスをリソースオブジェクトに変換する。

resourceの呼び出し(create_resource)

load_from_definitionを利用してBucketなどのサブリソースのクラスを動的に定義してオブジェクト生成している。これはResourceオブジェクト自体のクラス定義もしているメソッドである。

リソースAPIまとめ

resources-1.jsonからリソースAPIの定義を読み取っていて、オブジェクトの定義やAWS APIの呼び出し方が記載されている。そしてAWS APIを実行しているのはクライアントAPIで利用されていたClientオブジェクトである。
なので僅かではあるが、レスポンスの成型時にオブジェクト生成などやっていない分リソースAPIの方が少しだけパフォーマンスが良いのかな?と想像(単純に比較しようとしてもAWSのAPIの結果取得した後の部分のみを抽出してテストしないといけないので今回は断念)。
ただしパフォーマンスにこだわるならそもそもPython以外のSDK使う方が良いと思うので、Boto3を使う場合は可読性や使いやすさを考慮してリソースAPI使えるサービスなら使う方針が良いと思う。

まとめ

理解できたこと

  • クラス定義はjsonファイルを利用して動的に作成している(なのでIDEで補完が効かないのが個人的にはちょっとつらい…)
  • リソースAPIはAWSサービスAPI呼び出しの部分は内部でクライアントAPIを利用しつつ、インプットとアウトプットをオブジェクト型にバインドしている
  • クライアントAPIの処理は、ほぼbotocoreで完結している

Boto3を利用することで開発者が自前で実装しないで良い部分

  • AWSのAPIのレスポンスボディのXMLをパース(HTTPのAPIのレスポンスはjsonではない、AWSが2006年からあることを考えると違和感はないし、今からjsonに変更すると影響箇所が多そうなので今もXMLなのは納得できると勝手に感じた)
  • アクセスキーやIAMロールどの認証情報を使うか判断して、HTTPヘッダのAuthorizationの値を生成

おまけでわかったこと

  • クライアントAPIにはWaitersという、特定のリソースがある状態(S3のバケットやオブジェクトが存在するorしないなど)になるまでポーリンングして状態を待ち続ける機能があることを知った
    • 並列処理での待ち合わせとかに使うのかな?🤔
  • ずっとコードを読んでると('s3')が顔文字に見えてくる
  • コードリーディングして理解した内容を文章で伝えるのめちゃむずい

おわりに

過去にはQiitaにも記事を書いてましたので、ぜひこちらも見てください。

https://qiita.com/sugikeitter