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 つですAttributesResponseMetadata です。確認すべきステータスフィールドも、行数もありません。呼び出しが返ったなら成功しています。ResponseMetadata は、ログ行に入れたい RequestIdHTTPStatusCode を運びます。
  • ReturnValues="UPDATED_NEW" が倹約的な選択肢です。式が触れた属性だけを返すので、大きなアイテムでは、カウンター 1 つを読むのとレコード全体を送り返すのとの差になります。
  • エラーは botocore.exceptions.ClientError として届きe.response["Error"]["Code"] で分岐します。別名が無いと ValidationExceptionInvalid 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 をダウンロードしてください。

関連ガイド

参考資料

最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。

Console なしで DynamoDB を扱う

DynamoDB では実行できない本物の SQL(JOINs、GROUP BY、集計)を実行する高速な DynamoDB デスクトップクライアント。ビジュアル編集と、あなた自身の Bedrock キーで動く AI エージェントを備えています。

30日間無料トライアル、クレジットカード不要 — その後は期限のない Free プラン。