この記事は ABEJA Advent Calendar 2020 11日目の記事です。
この記事ではPythonのAWS SDKであるboto3が、どのような仕組みでAWSへリクエストを送っているかを説明します。
boto3とは
boto3とはPythonのAWS SDKです。PythonでAWSのリソースを操作する場合には(多くの場合)boto3を使う事となります。boto3でAWSのリソースを操作する場合には、以下のようにサービス毎にclientのインスタンスを作成して操作を行います。
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()
を返しています。
botocore.session.create_client()はbotocore.client.ClientCreator._create_client_class
で作られるservice_client
インスタンスを返しています。このservice_client
インスタンスのクラスは組み込み関数のtype
を使ってサービス毎に動的に生成されています。
cls = type(str(class_name), tuple(bases), class_attributes)
サービス毎のクラスを生成する際に必要となる、class_name
とclass_attributes
はbotocore.client.ClientCreator._load_service_model()
で作られるServiceModel
から生成されます。
このServiceModelはbotocore.loaders.Loader.load_service_model()
で、各サービスのサービスモデルの定義ファイルを参照しています。定義ファイルには、サービスの各オペレーション毎のAPIエンドポイント、リクエストパラメータ、レスポンスパラメータ、エラーの定義が含まれています。
{
"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に含まれるサービスの定義ファイルをPythonのクラスに変換する事で、AWSの各サービスのSDKとして機能しています。変換途中でオペレーション名をsnake_case
に変換する等の細かい処理も入っています。
AWSの新規サービスが追加された場合には、サービスモデルの定義ファイルをbotocoreに追加する事でboto3から利用可能になります。
実際にboto3.client()
を見てみると以下のようにservice_modelは定義ファイルからオペレーション名やエラーの定義をロードしています。
>>> 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を書く場合などは役にたつかもしれません。