DynamoDB PutItem en Python (boto3)
put_item écrit un élément entier et remplace tout élément existant portant la même clé primaire (les actions par élément expliquent en quoi cela diffère d'update_item). Avec le client bas niveau, chaque attribut est passé en JSON DynamoDB, et boto3 vérifie cette forme localement avant que quoi que ce soit ne parte.
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:
raiseExplication
{"N": 1994} n'atteint jamais AWS, et except ClientError ne l'attrapera pas. Botocore valide d'abord la requête contre son propre modèle de service, et un int Python là où le type N attend une chaîne échoue à ce niveau :
ParamValidationError: Parameter validation failed:
Invalid type for parameter Item.Year.N, value: 1994, type: <class 'int'>, valid types: <class 'str'>ParamValidationError descend de BotoCoreError, pas de ClientError : le handler du snippet ci-dessus la laisse donc passer. C'est en général ce que tu veux, puisqu'il s'agit d'un bug et non d'un résultat métier, mais ça signifie qu'un try/except ClientError autour d'une écriture n'attrape pas tout. L'avantage, c'est que l'erreur nomme le chemin exact, Item.Year.N, ce qui vaut mieux qu'une ValidationException côté serveur pour déboguer. Plus de détails dans "Parameter validation failed".
La surface complète d'une condition échouée. En attrapant deux fois le même put conditionnel et en affichant tout ce que porte l'exception :
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 failedDeux conséquences. Sur botocore 1.43.58, l'objet est une sous-classe modélisée : except client.exceptions.ConditionalCheckFailedException fonctionne donc à côté du contrôle sur err.response["Error"]["Code"] qu'utilise le snippet ; choisis-en un et sois cohérent. Et str(e) est une phrase formatée, pas le message du service : ne la compare donc jamais à un littéral.
Une condition échouée facture quand même une écriture. AWS : "if the expression evaluates to false, DynamoDB still consumes write capacity units from the table" (consulté le 2026-07-28). Une boucle de reprise en « création seule » paie chaque tentative rejetée. Pour l'ordre de grandeur, un put réussi d'un élément d'environ 15 KB a rapporté "CapacityUnits": 15 sous ReturnConsumedCapacity="TOTAL" ; les écritures sont arrondies au supérieur par tranches de 1 KB, pas de 4 KB comme les lectures.
L'API resource est un contrat différent, et c'est avec float que tu le découvres. boto3.resource("dynamodb").Table("Music").put_item(Item={...}) prend du Python ordinaire et marshalle pour toi, mais elle refuse catégoriquement les flottants binaires :
TypeError: Float types are not supported. Use Decimal types instead.Enveloppe la valeur dans un decimal.Decimal("4.5"), construit depuis une chaîne plutôt que depuis un float, sinon l'imprécision est déjà figée avant que Decimal ne la voie. Relire via la même API renvoie chaque nombre en Decimal, ce qui est un vrai changement dans ton code, pas un détail de formatage. Voir "Float types are not supported".
Mélanger les deux API est le piège dont aucune ne prévient. Le client bas niveau accepte volontiers un {"N": "1.5"}, une valeur que l'API resource aurait rejetée comme float. Une base de code qui écrit avec l'une et lit avec l'autre récupère du Decimal à partir de données qui ne sont jamais passées par Decimal à l'aller.
Les alias #cond0 ne sont pas cosmétiques. Ils se résolvent en Artist/SongTitle via ExpressionAttributeNames. Les noms d'attributs en clair fonctionnent jusqu'au jour où l'un d'eux entre en collision avec un mot réservé, et là l'expression échoue sur un nom que tu n'as pas touché.
Le faire visuellement
Les expressions de condition sont l'endroit où l'écriture à la main dérape en premier, parce qu'une expression fausse échoue sous forme d'écriture rejetée plutôt que d'erreur de syntaxe. Le DynamoDB Expression Builder gratuit assemble la ConditionExpression avec ses maps de noms et de valeurs, et produit l'appel boto3 prêt à coller.
Pour écrire et modifier des éléments dans tes propres tables — un formulaire par attribut, des sélecteurs de type, le résultat recopié en boto3 — télécharge DynoTable.
Guides liés
- Les expressions de condition DynamoDB —
attribute_not_exists, verrouillage optimiste, et plus encore. - Les types de données DynamoDB — comment chaque type d'attribut s'écrit en JSON DynamoDB.
- DynamoDB ConditionalCheckFailedException — ce que lève la condition « création seule » quand l'élément existe déjà.
- DynamoDB ValidationException — le fourre-tout pour un élément ou une expression malformés.
Références
- 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
Reproduit le 2026-07-28 avec boto3 1.43.58 / botocore 1.43.58 sur DynamoDB Local (amazon/dynamodb-local) sur le port 9000. Le texte de l'exception, les champs de la réponse et le relevé de capacité sont la sortie capturée, copiée telle quelle.