DynamoDB TransactWriteItems en Python (boto3)
Les transactions sont l'un des endroits où les deux API de boto3 divergent le plus : transact_write_items n'existe que sur le client bas niveau, donc le confort des types Python natifs que te donne Table est hors jeu ici. Et quand la transaction échoue, ce dont tu as besoin se trouve dans un recoin de l'exception que la plupart du code boto3 ne regarde jamais. (Ce qu'une transaction t'apporte est identique dans tous les SDK.)
Code
import boto3
client = boto3.client("dynamodb")
# Move one award between two songs — atomically. If the first song has no
# award to give, NEITHER update happens.
try:
client.transact_write_items(
TransactItems=[
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
"UpdateExpression": "SET #upd0 = #upd0 - :one",
"ConditionExpression": "#upd0 >= :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}},
}
},
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
"UpdateExpression": "SET #upd0 = if_not_exists(#upd0, :zero) + :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}, ":zero": {"N": "0"}},
}
},
]
)
print("Transaction committed")
except client.exceptions.TransactionCanceledException as e:
# One reason per action, in TransactItems order. Code "None" means that
# action was fine — some OTHER action sank the transaction.
codes = [reason["Code"] for reason in e.response["CancellationReasons"]]
print(f"Transaction canceled: {codes}") # e.g. ['ConditionalCheckFailed', 'None']Explication
TransactItems— une liste de dictsPut,Update,DeleteetConditionCheck, chaque valeur en JSON DynamoDB, sans exception. C'est le seul appel boto3 où la forme typée n'est pas optionnelle, et c'est pour ça que la section à la fin de cette page existe. Les plafonds sont sur la page CLI.CancellationReasonsn'est pas dansError. botocore remonte les champs d'erreur modélisés au premier niveau du dict de réponse : l'exception attrapée porte donce.responseavec les clésCancellationReasons,Error,MessageetResponseMetadatacôte à côte. La chercher souse.response["Error"]ne donne rien, ete.response["Error"]ne contient que le code et le message de résumé.- Aucun
"Message"sur les entréesNone— la raison d'une action réussie est le dict à clé unique{"Code": "None"}: le naturel[r["Message"] for r in reasons]lève donc unKeyError: 'Message'sur exactement les actions qui ont fonctionné. Utiliser.get("Message"). - Une classe d'exception générée — botocore construit
client.exceptions.TransactionCanceledExceptionà partir du modèle de service à l'exécution, d'où le fait qu'elle pende à l'instance du client et que tu ne puisses pas l'importer avecfrom botocore.exceptions import .... Dans un helper qui n'a pas le client sous la main, attrapebotocore.exceptions.ClientErroret branche sure.response["Error"]["Code"]; la classe générée en est une sous-classe. - Les erreurs de structure n'arrivent pas sous forme d'annulations, si bien que la clause
exceptdu snippet ne les voit jamais. Deux actions visant le même élément lèvent unClientErrornu dont le code estValidationExceptionet dont l'e.responsen'a aucune cléCancellationReasons, puisque la transaction a été rejetée avant qu'aucune action ne s'exécute. AttrapeClientErrorau niveau extérieur si tu veux les journaliser avec le même contexte. ReturnValuesOnConditionCheckFailure: "ALL_OLD"sur une action place l'élément perdant sous une cléItemdans la raison de cette action, en JSON DynamoDB, ce qui t'épargne leget_itemde suivi après avoir déjà perdu la course.- boto3 remplit
ClientRequestTokenpour toi. Capturés sur le réseau, deux appelstransact_write_itemsidentiques sont partis avec deux UUID différents : le token couvre donc un seul appel, et pas ta propre boucle attraper-puis-réessayer. Passe-en un stable toi-même si la reprise peut survivre au processus. - Réessaie sur
TransactionConflict, jamais surConditionalCheckFailed— le premier dit que quelqu'un d'autre a tenu l'élément un instant ; le second dit que ta précondition est fausse et le sera encore la prochaine fois. Ce sont les deux seuls codes que la plupart des handlers ont besoin de distinguer, et l'ensemble complet est décodé sur la page TransactionCanceledException. - Coût — une écriture transactionnelle est facturée environ le double de la même écriture hors transaction, mesuré sur la page CLI. Si tu n'as besoin d'atomicité que sur un seul élément, une écriture conditionnelle te l'offre à moitié prix.
Il n'existe pas de version resource de cet appel
boto3.resource("dynamodb").Table(...) n'a aucun attribut transact_write_items ; seul resource.meta.client en a un. Une base de code qui s'est fixée sur Table et les types Python natifs doit donc redescendre vers le JSON DynamoDB typé pour ses transactions, ou sérialiser à la main avec boto3.dynamodb.types.TypeSerializer :
from boto3.dynamodb.types import TypeSerializer
serialize = TypeSerializer().serialize
values = {k: serialize(v) for k, v in {":one": 1, ":zero": 0}.items()}TypeSerializer applique les mêmes règles que l'API resource, ce qui veut dire qu'il rejette float et attend un decimal.Decimal pour tout ce qui est fractionnaire. Le convertisseur JSON DynamoDB fait la même conversion dans le navigateur quand tu as seulement besoin de coller un littéral dans un script. Pour modifier les éléments qu'une transaction touche sans écrire ni l'une ni l'autre forme à la main, télécharge DynoTable.
Exemples liés
- DynamoDB TransactWriteItems en Node.js — la même transaction avec l'AWS SDK v3.
- DynamoDB TransactWriteItems avec l'AWS CLI — la même transaction depuis le shell.
- Écriture conditionnelle DynamoDB en Python — l'atomicité sur un seul élément sans le surcoût de 2×.
- Les transactions DynamoDB — isolation, idempotence, et quand les transactions en valent la peine.
- DynamoDB TransactionCanceledException — chaque code de raison d'annulation, décodé.
- "Too many actions in a TransactWriteItems call" — les limites de 100 actions et 4 MB par transaction.
- "Transaction request cannot include multiple operations on one item" — une action par élément, par transaction.
Références
- TransactWriteItems — Amazon DynamoDB API Reference
- DynamoDB.Client.transact_write_items — Boto3 documentation
- Amazon DynamoDB transactions: how it works — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
Dernière vérification le 2026-07-28 par rapport à la documentation officielle AWS liée ci-dessus.