boto3はどのようにしてAWSへリクエストを送っているか

この記事は [ABEJA Advent Calendar 2020](https://qiita.com/advent-calendar/2020/abeja) 11日目の記事です。

この記事ではPythonのAWS SDKであるboto3が、どのような仕組みでAWSへリクエストを送っているかを説明します。

# boto3とは
boto3とはPythonのAWS SDKです。PythonでAWSのリソースを操作する場合には（多くの場合）boto3を使う事となります。boto3でAWSのリソースを操作する場合には、以下のようにサービス毎にclientのインスタンスを作成して操作を行います。

```python
import boto3
client = boto3.client('kinesisvideo')
client.list_streams()
```
AWSに新しいサービスが追加された際にはリリース直後にCLIやSDKで追加サービスの操作が可能となります。その際、boto3ではAWSのAPIの各オペレーションはclientのインスタンスメソッドとして提供され、リクエスト・パラメータはメソッドの引数として渡す事ができます。boto3はどのようにしてSDKを迅速に提供しているのでしょうか？
boto3はbotocoreというコアライブラリを参照する事で、SDKとしての機能を動的に提供しています。
なお、`awscli`は内部でbotocoreが使われているため、boto3と同じ仕組みでコマンドの提供が行われています。

# boto3がAWSのAPIをclientに反映する仕組み

`boto3.client()`は[`botocore.session.create_client()`](https://github.com/boto/boto3/blob/e811814bb0060b897a516fde7ba44c2ce0861e97/boto3/__init__.py#L87-L93)を返しています。
botocore.session.create_client()は[`botocore.client.ClientCreator._create_client_class`](https://github.com/boto/botocore/blob/develop/botocore/client.py#L105-L117)で作られる`service_client`インスタンスを返しています。この`service_client`インスタンスのクラスは[組み込み関数の`type`](https://docs.python.org/3/library/functions.html#type)を使ってサービス毎に動的に生成されています。

```python
cls = type(str(class_name), tuple(bases), class_attributes)
```

サービス毎のクラスを生成する際に必要となる、`class_name`と`class_attributes`は[`botocore.client.ClientCreator._load_service_model()`](https://github.com/boto/botocore/blob/develop/botocore/client.py#L119-L123)で作られる`ServiceModel`から生成されます。

このServiceModelは[`botocore.loaders.Loader.load_service_model()`](https://github.com/boto/botocore/blob/8ab0d6b8333a1721873cf20f4bc8e1b333c5c154/botocore/loaders.py#L343)で、各サービスの[サービスモデルの定義ファイルを参照](https://github.com/boto/botocore/blob/8ab0d6b8333a1721873cf20f4bc8e1b333c5c154/botocore/data/kinesisvideo/2017-09-30/service-2.json)しています。定義ファイルには、サービスの各オペレーション毎のAPIエンドポイント、リクエストパラメータ、レスポンスパラメータ、エラーの定義が含まれています。

```json:botocore/botocore/data/kinesisvideo/2017-09-30/service-2.json
{
  "version":"2.0",
  "metadata":{
     ...
  },
  "operations":{
    "CreateSignalingChannel":{
      "name":"CreateSignalingChannel",
      "http":{
        "method":"POST",
        "requestUri":"/createSignalingChannel"
      },
      "input":{"shape":"CreateSignalingChannelInput"},
      "output":{"shape":"CreateSignalingChannelOutput"},
      "errors":[
        {"shape":"InvalidArgumentException"},
        {"shape":"ClientLimitExceededException"},
        {"shape":"AccountChannelLimitExceededException"},
        {"shape":"ResourceInUseException"},
        {"shape":"AccessDeniedException"},
        {"shape":"TagsPerResourceExceededLimitException"}
      ],
....
```

つまり、boto3のclientは[botocoreに含まれるサービスの定義ファイル](https://github.com/boto/botocore/tree/8ab0d6b8333a1721873cf20f4bc8e1b333c5c154/botocore/data)をPythonのクラスに変換する事で、AWSの各サービスのSDKとして機能しています。変換途中でオペレーション名を`snake_case`に変換する等の細かい処理も入っています。

AWSの新規サービスが追加された場合には、サービスモデルの定義ファイルをbotocoreに追加する事でboto3から利用可能になります。

実際に`boto3.client()`を見てみると以下のようにservice_modelは定義ファイルからオペレーション名やエラーの定義をロードしています。

```python
>>> import boto3
>>> client = boto3.client("kinesisvideo")
>>> client
<botocore.client.KinesisVideo object at 0x10c689210>
>>> client._service_model
ServiceModel(kinesisvideo)
>>> client._service_model.operation_names
['CreateSignalingChannel', 'CreateStream', 'DeleteSignalingChannel', 'DeleteStream', 'DescribeSignalingChannel', 'DescribeStream', 'GetDataEndpoint', 'GetSignalingChannelEndpoint', 'ListSignalingChannels', 'ListStreams', 'ListTagsForResource', 'ListTagsForStream', 'TagResource', 'TagStream', 'UntagResource', 'UntagStream', 'UpdateDataRetention', 'UpdateSignalingChannel', 'UpdateStream']
>>> client._service_model.error_shapes
[<StructureShape(AccessDeniedException)>, <StructureShape(AccountChannelLimitExceededException)>, <StructureShape(AccountStreamLimitExceededException)>, <StructureShape(ClientLimitExceededException)>, <StructureShape(DeviceStreamLimitExceededException)>, <StructureShape(InvalidArgumentException)>, <StructureShape(InvalidDeviceException)>, <StructureShape(InvalidResourceFormatException)>, <StructureShape(NotAuthorizedException)>, <StructureShape(ResourceInUseException)>, <StructureShape(ResourceNotFoundException)>, <StructureShape(TagsPerResourceExceededLimitException)>, <StructureShape(VersionMismatchException)>]
```

# まとめ
boto3やawscliはbotocoreに含まれる各サービスの定義ファイルを参照してSDK/CLIを提供しています。SDKが想定外の挙動をした場合や、AWSのmockを書く場合などは役にたつかもしれません。