boto3のclientでDynamoDBテーブルを利用してみる(scan, put_item, get_item, update_item, delete_item)
boto3でDynamoDBを利用するとき、リソース(resource)を利用していました。 しかし、次のニュースが飛び込んできました。
おそらく、最も影響を受けるのは、DynamoDBに対するアクセスだと思います。
そこで、本記事では、DynamoDBに対するアクセスをresource
ではなくclient
で試してみました。
resourceは無くなりませんが、新機能の追加がされないようです。
おすすめの方
- boto3のclientでDynamoDBテーブルを利用したい方
環境
項目 | バージョン |
---|---|
Python | 3.9.9 |
boto3 | 1.26.52 |
botocore | 1.29.52 |
boto3のDynamoDBで非推奨になるであろう内容
ドキュメントの下記が該当すると思われます。
- DynamoDB Service Resource — Boto3 Docs 1.26.52 documentation
- DynamoDB Table — Boto3 Docs 1.26.52 documentation
簡単に説明すると、今までお手軽に使っていた次のコードたちが非推奨になります。
dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('name') table.foo()
実験用のDynamoDBテーブルを用意する
DynamoDBテーブルを作成する
適当に作成します。
boto3のclientでDynamoDBを利用するサンプル
サンプルコード
import boto3 from boto3.dynamodb.conditions import Attr, Key from datetime import datetime, timezone TABLE_NAME = 'boto3-test' dynamodb = boto3.client('dynamodb') # これはできない # table = dynamodb.Table('boto3-test') def main(): print('--- scan ---') scan() print('--- put ---') put() print('--- get ---') get() print('--- update ---') update() print('--- scan ---') scan() print('--- delete ---') delete() print('--- scan ---') scan() def scan(): options = { 'TableName': TABLE_NAME, 'ProjectionExpression': '#todoId, #title', 'ExpressionAttributeNames': { '#todoId': 'todoId', '#title': 'title', }, 'Limit': 1, # loopの実験用 } ret = [] while True: res = dynamodb.scan(**options) ret += res.get('Items', []) if 'LastEvaluatedKey' not in res: break options['ExclusiveStartKey'] = res['LastEvaluatedKey'] print(len(ret)) print(ret) def put(): now = int(datetime.now(timezone.utc).timestamp() * 1000) options = { 'TableName': TABLE_NAME, 'Item': { 'todoId': {'S': 't0001'}, 'title': {'S': 'this is title'}, 'status': {'S': 'unstarted'}, 'createdAt': {'N': str(now)}, 'binary': {'B': 'pen'.encode()}, 'any string set': {'SS': ['Tag Name1', 'Tag Name2', 'Tag Name3']}, 'any number set': {'NS': ['111','222','333']}, 'any binary set': {'BS': ['aaa'.encode(), 'bbb'.encode(), 'ccc'.encode()]}, 'any map': {'M': { 'map-name1': {'S': 'map-value1'}, 'map-name2': {'N': '111'}, }}, 'any list': {'L': [ {'S': 'list-value1'}, {'N': '222'}, ]}, 'any null': {'NULL': True}, 'any bool': {'BOOL': False}, }, # 'ConditionExpression': 'attribute_not_exists(todoId)', } dynamodb.put_item(**options) options['Item']['todoId']['S'] = 't0002' dynamodb.put_item(**options) def get(): options = { 'TableName': TABLE_NAME, 'Key': { 'todoId': {'S': 't0001'}, } } ret = dynamodb.get_item(**options) print(ret['Item']) def update(): now = int(datetime.now(timezone.utc).timestamp() * 1000) options = { 'TableName': TABLE_NAME, 'Key': { 'todoId': {'S': 't0001'}, }, 'ConditionExpression': '#status = :unstarted', 'UpdateExpression': 'set #updatedAt = :updatedAt, #status = :running, #title = :title', 'ExpressionAttributeNames': { '#updatedAt': 'updatedAt', '#status': 'status', '#title': 'title', }, 'ExpressionAttributeValues': { ':updatedAt': {'N': str(now)}, ':unstarted': {'S': 'unstarted'}, ':running': {'S': 'running'}, ':title': {'S': 'This is an apple.'}, } # 'ConditionExpression': 'attribute_not_exists(todoId)', } dynamodb.update_item(**options) def delete(): options = { 'TableName': TABLE_NAME, 'Key': { 'todoId': {'S': 't0001'}, } } dynamodb.delete_item(**options) if __name__ == '__main__': main()
実行結果
--- scan --- 0 [] --- put --- --- get --- {'any list': {'L': [{'S': 'list-value1'}, {'N': '222'}]}, 'todoId': {'S': 't0001'}, 'any number set': {'NS': ['111', '222', '333']}, 'status': {'S': 'unstarted'}, 'any null': {'NULL': True}, 'createdAt': {'N': '1674124101857'}, 'any map': {'M': {'map-name1': {'S': 'map-value1'}, 'map-name2': {'N': '111'}}}, 'any string set': {'SS': ['Tag Name1', 'Tag Name2', 'Tag Name3']}, 'any bool': {'BOOL': False}, 'any binary set': {'BS': [b'aaa', b'bbb', b'ccc']}, 'binary': {'B': b'pen'}, 'title': {'S': 'this is title'}} --- update --- --- scan --- 2 [{'todoId': {'S': 't0001'}, 'title': {'S': 'This is an apple.'}}, {'todoId': {'S': 't0002'}, 'title': {'S': 'this is title'}}] --- delete --- --- scan --- 1 [{'todoId': {'S': 't0002'}, 'title': {'S': 'this is title'}}]
DynamoDBにputされたItem
実行結果後の情報です。
{ "todoId": { "S": "t0002" }, "any binary set": { "BS": [ "YWFh", "YmJi", "Y2Nj" ] }, "any bool": { "BOOL": false }, "any list": { "L": [ { "S": "list-value1" }, { "N": "222" } ] }, "any map": { "M": { "map-name1": { "S": "map-value1" }, "map-name2": { "N": "111" } } }, "any null": { "NULL": true }, "any number set": { "NS": [ "111", "222", "333" ] }, "any string set": { "SS": [ "Tag Name1", "Tag Name2", "Tag Name3" ] }, "binary": { "B": "cGVu" }, "createdAt": { "N": "1674124101857" }, "status": { "S": "unstarted" }, "title": { "S": "this is title" } }
型変換の代替策
DynamoDBテーブルの型情報が必要なのがとても手間です。 その代替策として、とりあえず、3つありそうです。
boto3のTypeSerializerとTypeDeserializerを利用する
boto3で完結しており、他ライブラリが不要なので、これが良さそうですね。
型情報をいい感じにしてくれるライブラリを使う
「通常のJSON」と「DynamoDB用のJSON」を変換してくれるライブラリです。
ただし、メンテナンスはされていなさそうですので、万全を期すなら、forkして利用すると安心ですね。
PynamoDBを使う
PythonでDynamoDBを扱うライブラリです。
PynamoDBはboto3
本体に依存しておらず、botocore
を利用しているため、今回のようなboto3
の変更には影響を受けないと思われます。
$ pip install pynamodb Collecting pynamodb Downloading pynamodb-5.3.4-py3-none-any.whl (58 kB) |████████████████████████████████| 58 kB 6.6 MB/s Collecting botocore>=1.12.54 Using cached botocore-1.29.52-py3-none-any.whl (10.3 MB) Collecting urllib3<1.27,>=1.25.4 Using cached urllib3-1.26.14-py2.py3-none-any.whl (140 kB) Collecting python-dateutil<3.0.0,>=2.1 Using cached python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB) Collecting jmespath<2.0.0,>=0.7.1 Using cached jmespath-1.0.1-py3-none-any.whl (20 kB) Collecting six>=1.5 Using cached six-1.16.0-py2.py3-none-any.whl (11 kB) Installing collected packages: six, urllib3, python-dateutil, jmespath, botocore, pynamodb Successfully installed botocore-1.29.52 jmespath-1.0.1 pynamodb-5.3.4 python-dateutil-2.8.2 six-1.16.0 urllib3-1.26.14
さいごに
Pythonでboto3を利用している場合には、かなり大きく影響のあるアップデート通知です。 今すぐの対応は不要ですが、今後どうしていくかの作戦は決めておくほうが良さそうですね。
参考
- AWS Python SDK(boto3)のリソース・インターフェースが改修凍結されました | DevelopersIO(https://dev.classmethod.jp/articles/aws-python-sdk-resource-interface-deperecation-warning/)
- DynamoDB Service Resource — Boto3 Docs 1.26.52 documentation
- DynamoDB Table — Boto3 Docs 1.26.52 documentation
- Alonreznik/dynamodb-json: DynamoDB JSON util to load and dump strings of Dynamodb JSON format to python object and vise-versa
- pynamodb/PynamoDB: A pythonic interface to Amazon's DynamoDB
- DynamoDB JSONの型変換をboto3だけで行ってみる | DevelopersIO