DynamoDB TransactWriteItems en Python (boto3)
Las transacciones son uno de los sitios donde las dos API de boto3 más divergen: transact_write_items solo existe en el cliente de bajo nivel, así que la comodidad de los tipos nativos de Python que te da Table aquí no está disponible. Y cuando la transacción falla, lo que necesitas está en un rincón de la excepción que casi ningún código de boto3 mira. (Lo que te aporta una transacción es igual en todos los SDK.)
Código
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']Explicación
TransactItems— una lista de diccionariosPut,Update,DeleteyConditionCheck, cada valor en JSON de DynamoDB, sin excepciones. Esta es la única llamada de boto3 donde la forma tipada no es opcional, y por eso existe la sección del final de esta página. Los topes están en la página de la CLI.CancellationReasonsno está dentro deError. botocore eleva los campos de error modelados al nivel superior del diccionario de respuesta, así que la excepción capturada lleva une.responsecon las clavesCancellationReasons,Error,MessageyResponseMetadatauna al lado de otra. Buscarlo bajoe.response["Error"]no encuentra nada, ye.response["Error"]solo contiene el código y el mensaje de resumen.- Sin
"Message"en las entradasNone— el motivo de una acción correcta es el diccionario de una sola clave{"Code": "None"}, así que el natural[r["Message"] for r in reasons]lanzaKeyError: 'Message'justo en las acciones que funcionaron. Usar.get("Message"). - Una clase de excepción generada — botocore construye
client.exceptions.TransactionCanceledExceptiona partir del modelo de servicio en tiempo de ejecución, y por eso cuelga de la instancia del cliente y por eso no puedes hacerlefrom botocore.exceptions import .... En una función auxiliar que no tenga el cliente a mano, capturabotocore.exceptions.ClientErrory ramifica segúne.response["Error"]["Code"]; la clase generada es una subclase suya. - Los errores estructurales no llegan como cancelaciones, así que la cláusula
exceptdel fragmento nunca los ve. Dos acciones apuntando al mismo elemento lanzan unClientErrorpelado cuyo código esValidationExceptiony cuyoe.responseno tiene claveCancellationReasons, ya que la transacción se rechazó antes de ejecutar ninguna acción. CapturaClientErroren el borde exterior si quieres registrarlos con el mismo contexto. ReturnValuesOnConditionCheckFailure: "ALL_OLD"en una acción pone el elemento perdedor bajo una claveItemen el motivo de esa acción, en JSON de DynamoDB, ahorrándote elget_itemde seguimiento cuando ya has perdido la carrera.- boto3 rellena
ClientRequestTokenpor ti. Capturadas en la red, dos llamadas idénticas atransact_write_itemssalieron con dos UUID distintos, así que el token cubre una única llamada y no tu propio bucle de capturar y reintentar. Pasa uno estable tú mismo si el reintento puede sobrevivir al proceso. - Reintenta con
TransactionConflict, nunca conConditionalCheckFailed— el primero dice que otro retuvo el elemento un momento; el segundo dice que tu precondición es falsa y seguirá siéndolo la próxima vez. Esos son los dos únicos códigos que la mayoría de los handlers necesitan separar, y el conjunto completo está descifrado en la página de TransactionCanceledException. - Coste — una escritura transaccional factura aproximadamente el doble que la misma escritura fuera de una, medido en la página de la CLI. Si solo necesitas atomicidad sobre un único elemento, una escritura condicional te la da a mitad de precio.
No hay versión de esto en la API de recursos
boto3.resource("dynamodb").Table(...) no tiene el atributo transact_write_items; solo lo tiene resource.meta.client. Así que una base de código que se haya asentado en Table y tipos nativos de Python tiene que volver al JSON de DynamoDB tipado para sus transacciones, o serializar a mano con 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 aplica las mismas reglas que la API de recursos, lo que significa que rechaza float y espera decimal.Decimal para cualquier cosa fraccionaria. El conversor de JSON de DynamoDB hace la misma conversión en el navegador cuando solo necesitas pegar un literal en un script. Para editar los elementos que toca una transacción sin escribir ninguna de las dos formas a mano, descarga DynoTable.
Ejemplos relacionados
- DynamoDB TransactWriteItems en Node.js — la misma transacción con AWS SDK v3.
- DynamoDB TransactWriteItems con la AWS CLI — la misma transacción desde el shell.
- Escritura condicional en DynamoDB con Python — atomicidad sobre un solo elemento sin el coste 2×.
- Transacciones en DynamoDB — aislamiento, idempotencia y cuándo compensan las transacciones.
- DynamoDB TransactionCanceledException — todos los códigos de motivo de cancelación, descifrados.
- "Too many actions in a TransactWriteItems call" — los límites de 100 acciones y 4 MB por transacción.
- "Transaction request cannot include multiple operations on one item" — una acción por elemento y por transacción.
Referencias
- 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
Verificado por última vez el 2026-07-28 contra la documentación oficial de AWS enlazada arriba.