Python(boto3)での DynamoDB PutItem
put_item はアイテムを丸ごと書き込み、同じプライマリキーを持つ既存のアイテムを置き換えます(update_item との違いはアイテム単位の操作で扱っています)。低レベルクライアント では、すべての属性が DynamoDB JSON として渡され、boto3 は何かを送る前にその形をローカルで検査します。
コード
import boto3
from botocore.exceptions import ClientError
client = boto3.client("dynamodb")
try:
client.put_item(
TableName="Music",
Item={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}, "AlbumTitle": {"S": "Danzon"}, "Year": {"N": "1994"}, "Awards": {"N": "0"}},
ConditionExpression="attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)",
ExpressionAttributeNames={"#cond0": "Artist", "#cond1": "SongTitle"},
)
print("Song written")
except ClientError as err:
if err.response["Error"]["Code"] == "ConditionalCheckFailedException":
print("A song with that key already exists — not overwritten")
else:
raise解説
{"N": 1994} は AWS まで届かず、except ClientError はそれを捕まえません。botocore はまず自前のサービスモデルに照らしてリクエストを検証するので、N 型が文字列を求めている場所に Python の int があると、そこで失敗します。
ParamValidationError: Parameter validation failed:
Invalid type for parameter Item.Year.N, value: 1994, type: <class 'int'>, valid types: <class 'str'>ParamValidationError は ClientError ではなく BotoCoreError を継承しているので、上のスニペットのハンドラーはこれを素通りさせます。ビジネス上の結果ではなくバグなので、たいていはそれが望ましい挙動です。ただし、書き込みを try/except ClientError で囲んでも万能ではない、ということでもあります。良い面として、このエラーは Item.Year.N という正確なパスを名指しするので、デバッグではサーバー側の ValidationException に勝ります。詳しくは"Parameter validation failed"にあります。
条件が失敗したときの全体像。同じ条件付き put を 2 回実行して例外の中身をすべて表示すると、こうなりました。
type(e).__name__ ConditionalCheckFailedException
e.response["Error"]["Code"] ConditionalCheckFailedException
e.response["Error"]["Message"] The conditional request failed
e.response["ResponseMetadata"]["HTTPStatusCode"] 400
str(e) An error occurred (ConditionalCheckFailedException) when calling the PutItem operation: The conditional request failedここから 2 つのことが言えます。botocore 1.43.58 では、このオブジェクトは モデル化されたサブクラス なので、スニペットが使っている err.response["Error"]["Code"] のチェックと並んで except client.exceptions.ConditionalCheckFailedException も機能します。どちらかを選んで一貫させましょう。そして str(e) は整形された文であってサービスのメッセージではないので、決してリテラルと比較しないでください。
条件が失敗しても書き込みは課金されます。AWS の記述はこうです。"if the expression evaluates to false, DynamoDB still consumes write capacity units from the table"(2026-07-28 取得)。作成専用のリトライループは、拒否された試行のすべてに支払います。規模感として、約 15 KB のアイテムの put が成功したとき、ReturnConsumedCapacity="TOTAL" の下では "CapacityUnits": 15 と報告されました。書き込みは読み取りの 4 KB ではなく 1 KB 単位で切り上げられます。
リソース API は別の契約であり、それに気づかされるのが float です。boto3.resource("dynamodb").Table("Music").put_item(Item={...}) は素の Python を受け取って代わりにマーシャルしますが、2 進浮動小数点はきっぱり拒否します。
TypeError: Float types are not supported. Use Decimal types instead.値は decimal.Decimal("4.5") で包みましょう。float からではなく 文字列 からです。そうしないと、Decimal が見る前に誤差が既に焼き付いています。同じ API を通して読み戻すと、数値はすべて Decimal で返ります。これは書式の細部ではなく、コードにとって本物の変化です。"Float types are not supported"を参照してください。
2 つの API を混ぜることは、どちらも警告してくれない罠です。低レベルクライアントは {"N": "1.5"} を平気で受け取りますが、これはリソース API なら float として拒否した値です。片方で書いてもう片方で読むコードベースは、入り口で Decimal を通っていないデータから Decimal を受け取ることになります。
#cond0 の別名は飾りではありません。これらは ExpressionAttributeNames を通して Artist/SongTitle に解決されます。属性名の直書きは、どれかが予約語と衝突するまでは動き、衝突した瞬間、自分では変えていない名前のせいで式が失敗します。
ビジュアルに行う
手書きが最初に破綻するのは条件式です。間違った条件は構文エラーではなく、拒否された書き込みとして失敗するからです。無料の DynamoDB Expression Builder は ConditionExpression を名前マップと値マップとともに組み立て、そのまま貼り付けられる boto3 の呼び出しを出力します。
自分のテーブルに対してアイテムを書き込み・編集するには — 属性ごとのフォーム、型ピッカー、結果の boto3 としてのコピー — DynoTable をダウンロードしてください。
関連ガイド
- DynamoDB の条件式 —
attribute_not_exists、楽観ロック、ほか。 - DynamoDB のデータ型 — 各属性型が DynamoDB JSON でどう書かれるか。
- DynamoDB ConditionalCheckFailedException — アイテムが既に存在するとき、作成専用の条件が送出するもの。
- DynamoDB ValidationException — 不正なアイテムや式に対する総括的なエラー。
参考資料
- PutItem — Amazon DynamoDB API Reference
- put_item — Boto3 DynamoDB.Client Reference
- Error handling — Boto3 Developer Guide
- Capacity unit consumption — Amazon DynamoDB Developer Guide
- Condition expressions — Amazon DynamoDB Developer Guide
2026-07-28 に、boto3 1.43.58 / botocore 1.43.58 で、ポート 9000 の DynamoDB Local(amazon/dynamodb-local)に対して再現しました。上記の例外テキスト、レスポンスのフィールド、キャパシティの数値は、取得した出力をそのまま逐語で写したものです。