Python(boto3)での DynamoDB UpdateItem
boto3 はこの呼び出しに 2 つのクライアントを用意しており、両者は「数値とは何か」で意見が食い違います。下の低レベル client は DynamoDB JSON を送受信し、そこではすべての数値がクォートされた文字列です。resource("dynamodb").Table(...) はネイティブな Python オブジェクトを取り、float をきっぱり拒否し、数値を decimal.Decimal として返します。どちらを選ぶかが、このページの本当の意思決定です。
コード
import boto3
client = boto3.client("dynamodb")
response = client.update_item(
TableName="Music",
Key={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
UpdateExpression="SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2",
ExpressionAttributeNames={"#upd0": "Genre", "#upd1": "Year", "#upd2": "Awards"},
ExpressionAttributeValues={":updValue0": {"S": "Latin Jazz"}, ":updValue1": {"N": "1994"}, ":updValue2": {"N": "1"}},
ReturnValues="ALL_NEW",
)
print(response["Attributes"]) # the item after the update解説
- 句の文法は boto3 の関知するところではありません。
UpdateExpressionは boto3 が転送するだけの不透明な文字列で、パースするのは DynamoDB だけです。だから誤りはラウンドトリップ 1 回分の代償を伴います。ここでのADDは read-modify-write の競合を取り除くアトミックなインクリメントであり、ConditionExpressionの中のattribute_exists(Artist)はアップサートを更新専用に変えます。残りは更新式にあります。 - レスポンスのトップレベルキーはちょうど 2 つです。
AttributesとResponseMetadataです。確認すべきステータスフィールドも、行数もありません。呼び出しが返ったなら成功しています。ResponseMetadataは、ログ行に入れたいRequestIdとHTTPStatusCodeを運びます。 ReturnValues="UPDATED_NEW"が倹約的な選択肢です。式が触れた属性だけを返すので、大きなアイテムでは、カウンター 1 つを読むのとレコード全体を送り返すのとの差になります。- エラーは
botocore.exceptions.ClientErrorとして届き、e.response["Error"]["Code"]で分岐します。別名が無いとValidationExceptionがInvalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Yearというメッセージで発生します。型付きのサブクラスは確かに存在しますが、botocore がクライアントインスタンス上に生成する属性としてのみ(client.exceptions.ConditionalCheckFailedException)であり、インポート可能なシンボルとしては決して存在しません。だからクライアントがスコープに無いヘルパー関数はコード文字列を使うしかありません。
Decimal か DynamoDB JSON か、どちらかを選ぶ
リソース API は、リクエストが組み立てられる前に float を拒否し、何を求めているかをそのまま伝えるメッセージを出します。
TypeError: Float types are not supported. Use Decimal types instead.これは DynamoDB ではなく boto3 自身の型チェックです。リソース API を通して Decimal("4.5") を保存し、同じ属性を両方のクライアントで読み戻すと、こうなります。
resource Rating: Decimal('4.5') Awards: Decimal('2')
client Rating: {'N': '4.5'} Awards: {'N': '2'}どちらも間違いではなく、契約が異なるだけです。Decimal は DynamoDB が実際に保存している精度を保ち、算術について考えることを強います。その代償が、int を期待していたコードに Decimal("1") * 2 が現れることです。低レベルクライアントは文字列を渡し、パースを自分に任せます。上のスニペットがやっているのはそれです。
そこから導かれるルールは、1 つのコードパスで両者を混ぜないこと。Table.put_item で書いて client.get_item で読んだアイテムは違う形で返り、そのバグは、テストが手薄だったほうの分岐で表に出ます。
TTL 属性についての注意
Python のコードベースで最もよくある数値の SET は TTL です。Unix エポックを使った SET expires_at = :t の形です。DynamoDB はこの属性を 秒 として読みます。代わりに int(time.time() * 1000) を書くと値は 1785269450912 になり、秒として解釈すると 58542 年に着地するので、アイテムは決して削除されず、誰も文句を言いません。DynamoDB TTL コンバーターは、エポックを両方の単位で読み直し、どちらを書いたのかを教えてくれます。その後、実テーブルから保存された値を読み戻すには、DynoTable をダウンロードしてください。
関連ガイド
- DynamoDB の更新式 —
SET、ADD、REMOVE、DELETE、そしてイディオム。 - ReturnValues を理解する — 各
ReturnValuesオプションが何をもたらすか。 - "Attribute name is a reserved keyword" — ここの別名マップが任意ではない理由。
- "Invalid UpdateExpression" syntax errors — SET/ADD でよくある構文の誤りを読み解きます。
参考資料
- UpdateItem — Amazon DynamoDB API Reference
- update_item — Boto3 DynamoDB.Client Reference
- Update expressions — Amazon DynamoDB Developer Guide
最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。