DynamoDB TransactWriteItems em Python (boto3)

Transações são um dos pontos em que as duas APIs do boto3 mais divergem: transact_write_items existe apenas no client de baixo nível, então a conveniência de tipos nativos do Python que você tem com Table está fora de questão aqui. E quando a transação falha, o que você precisa está em um canto da exceção para o qual a maior parte do código boto3 nunca olha. (O que uma transação te dá é o mesmo em todos os SDKs.)

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']

Explicação

  • TransactItems — uma lista de dicionários Put, Update, Delete e ConditionCheck, com todos os valores em JSON do DynamoDB, sem exceção. Esta é a única chamada do boto3 em que a forma tipada não é opcional, e é por isso que a seção no fim desta página existe. Os tetos estão na página da CLI.
  • CancellationReasons não está dentro de Error. O botocore eleva os campos de erro modelados para o topo do dicionário de resposta, então a exceção capturada carrega e.response com as chaves CancellationReasons, Error, Message e ResponseMetadata lado a lado. Procurá-la sob e.response["Error"] não encontra nada, e e.response["Error"] guarda apenas o código e a mensagem de resumo.
  • Nenhum "Message" nas entradas None — o motivo de uma ação bem-sucedida é o dicionário de chave única {"Code": "None"}, então o natural [r["Message"] for r in reasons] levanta KeyError: 'Message' exatamente nas ações que funcionaram. Use r.get("Message").
  • Uma classe de exceção gerada — o botocore constrói client.exceptions.TransactionCanceledException a partir do modelo de serviço em tempo de execução, e é por isso que ela pende da instância do client e por que você não consegue fazer from botocore.exceptions import ... dela. Em uma função auxiliar que não tem o client no escopo, capture botocore.exceptions.ClientError e ramifique em e.response["Error"]["Code"]; a classe gerada é uma subclasse dela.
  • Erros estruturais não chegam como cancelamentos, então a cláusula except do trecho nunca os vê. Duas ações mirando o mesmo item levantam um ClientError puro cujo código é ValidationException e cujo e.response não tem chave CancellationReasons, já que a transação foi rejeitada antes de qualquer ação rodar. Capture ClientError na borda externa se quiser esses casos logados com o mesmo contexto.
  • ReturnValuesOnConditionCheckFailure: "ALL_OLD" em uma ação coloca o item perdedor sob uma chave Item no motivo daquela ação, em JSON do DynamoDB, te poupando o get_item seguinte depois de você já ter perdido a corrida.
  • O boto3 preenche o ClientRequestToken por você. Capturadas no protocolo, duas chamadas idênticas de transact_write_items saíram com dois UUIDs diferentes, então o token cobre uma única chamada e não o seu próprio loop de capturar-e-repetir. Passe um estável você mesmo se o retry puder sobreviver ao processo.
  • Repita em TransactionConflict, nunca em ConditionalCheckFailed — o primeiro diz que outra pessoa segurou o item por um instante; o segundo diz que a sua pré-condição é falsa e continuará falsa na próxima vez. Esses são os únicos dois códigos que a maioria dos handlers precisa separar, e o conjunto completo está decodificado na página do TransactionCanceledException.
  • Custo — uma escrita transacional cobra cerca do dobro do que a mesma escrita custa fora de uma, medido na página da CLI. Se você só precisa de atomicidade em um único item, uma escrita condicional compra isso pela metade do preço.

Não existe versão disto na API de resource

boto3.resource("dynamodb").Table(...) não tem atributo transact_write_items; só resource.meta.client tem. Então uma base de código que já se acomodou em Table e tipos nativos do Python precisa recuar para JSON do DynamoDB tipado nas suas transações, ou serializar à mão com 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 as mesmas regras da API de resource, o que significa que ele rejeita float e espera decimal.Decimal para qualquer coisa fracionária. O conversor de JSON do DynamoDB faz a mesma conversão no navegador quando você só precisa colar um literal em um script. Para editar os itens que uma transação toca sem escrever nenhuma das duas formas à mão, baixe o DynoTable.

Exemplos relacionados

Referências

Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.

Trabalhe com o DynamoDB sem o Console

Um cliente desktop rápido para DynamoDB que roda o SQL de verdade que o DynamoDB não consegue — JOINs, GROUP BY, agregações — com edição visual e um agente de IA com suas próprias chaves do Bedrock.

Teste grátis de 30 dias, sem cartão de crédito — depois o plano Grátis sem limite de tempo.