boto3でDynamoDBを利用するとき、リソース(resource)を利用していました。 しかし、次のニュースが飛び込んできました。
おそらく、最も影響を受けるのは、DynamoDBに対するアクセスだと思います。
そこで、本記事では、DynamoDBに対するアクセスをresource
ではなくclient
で試してみました。
2023/1/23追記:表現が変わりました。
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を利用するサンプル
サンプルコード
sample.py
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