DynamoDB PutItem in Python (boto3)
put_item writes a whole item and replaces any existing item with the same primary key (item-based actions covers how that differs from update_item). With the low-level client every attribute is passed as DynamoDB JSON, and boto3 checks that shape locally before anything is sent.
Code
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:
raiseExplanation
{"N": 1994} never reaches AWS, and except ClientError will not catch it. Botocore validates the request against its own service model first, and a Python int where the N type wants a string fails there:
ParamValidationError: Parameter validation failed:
Invalid type for parameter Item.Year.N, value: 1994, type: <class 'int'>, valid types: <class 'str'>ParamValidationError descends from BotoCoreError, not ClientError, so the handler in the snippet above lets it through. That is usually what you want, since it is a bug rather than a business outcome, but it means a try/except ClientError around a write is not a catch-all. The upside is the error names the exact path, Item.Year.N, which beats a server-side ValidationException for debugging. More on it in "Parameter validation failed".
The full surface of a failed condition. Catching the same conditional put twice and printing everything on the exception gave:
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 failedTwo things follow. On botocore 1.43.58 the object is a modeled subclass, so except client.exceptions.ConditionalCheckFailedException works alongside the err.response["Error"]["Code"] check the snippet uses; pick one and be consistent. And str(e) is a formatted sentence, not the service message, so never compare it against a literal.
A failed condition still bills a write. AWS: "if the expression evaluates to false, DynamoDB still consumes write capacity units from the table" (fetched 2026-07-28). A create-only retry loop pays for every rejected attempt. For scale, a successful put of a ~15 KB item reported "CapacityUnits": 15 under ReturnConsumedCapacity="TOTAL"; writes round up per 1 KB, not the 4 KB reads use.
The resource API is a different contract, and float is where you find out. boto3.resource("dynamodb").Table("Music").put_item(Item={...}) takes plain Python and marshals for you, but it refuses binary floating point outright:
TypeError: Float types are not supported. Use Decimal types instead.Wrap the value in decimal.Decimal("4.5"), from a string rather than a float, or the imprecision is already baked in before Decimal sees it. Reading back through the same API returns every number as Decimal, which is a real change to your code, not a formatting detail. See "Float types are not supported".
Mixing the two APIs is the trap neither one warns about. The low-level client happily accepts {"N": "1.5"}, a value the resource API would have rejected as a float. A codebase that writes with one and reads with the other gets Decimal back from data that never went through Decimal on the way in.
The #cond0 aliases are not cosmetic. They resolve to Artist/SongTitle through ExpressionAttributeNames. Inline attribute names work right up until one collides with a reserved word, and then the expression fails on a name you did not change.
Do it visually
Condition expressions are where hand-writing goes wrong first, because a wrong one fails as a rejected write rather than a syntax error. The free DynamoDB Expression Builder assembles the ConditionExpression with its name and value maps and emits the boto3 call ready to paste.
To write and edit items against your own tables — a form per attribute, type pickers, copy the result back out as boto3 — download DynoTable.
Related guides
- DynamoDB condition expressions —
attribute_not_exists, optimistic locking, and more. - DynamoDB data types — how each attribute type is written in DynamoDB JSON.
- DynamoDB ConditionalCheckFailedException — what the create-only condition throws when the item already exists.
- DynamoDB ValidationException — the catch-all for a malformed item or expression.
References
- 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
Reproduced 2026-07-28 with boto3 1.43.58 / botocore 1.43.58 against DynamoDB Local (amazon/dynamodb-local) on port 9000. The exception text, the response fields and the capacity reading are captured output, copied verbatim.