Escrita condicional no DynamoDB em Python (boto3)

O boto3 é o único SDK em que uma escrita condicional tem uma classe de exceção nomeada para capturar, e também é aquele em que o item retornado se esconde em um lugar que você não adivinharia. A expressão em si funciona igual em todo lugar; expressões de condição do DynamoDB cobre as funções e o padrão de bloqueio otimista.

Código

import boto3

client = boto3.client("dynamodb")

# Update the item only if nobody changed it since we read version 7.
try:
    client.update_item(
        TableName="Music",
        Key={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
        UpdateExpression="SET #upd0 = :updValue0, #version = :newVersion",
        ConditionExpression="attribute_exists(#cond0) AND #version = :expectedVersion",
        ExpressionAttributeNames={"#upd0": "Genre", "#version": "Version", "#cond0": "Artist"},
        ExpressionAttributeValues={
            ":updValue0": {"S": "Latin Jazz"},
            ":expectedVersion": {"N": "7"},
            ":newVersion": {"N": "8"},
        },
        ReturnValuesOnConditionCheckFailure="ALL_OLD",
    )
    print("Updated to version 8")
except client.exceptions.ConditionalCheckFailedException as e:
    # With ReturnValuesOnConditionCheckFailure="ALL_OLD", the current item
    # rides back on the exception — no extra read to see what beat you.
    print("Lost the race — item is now:", e.response.get("Item"))

Explicação

  • ConditionalCheckFailedException é uma classe modelada, então except client.exceptions.… funciona. A maioria dos erros do DynamoDB não é: ValidationException não tem classe nenhuma e precisa ser identificado por e.response["Error"]["Code"]. A classe modelada ainda é subclasse de ClientError, então um except ClientError amplo mais acima vai engoli-la se você ordenar seus handlers sem cuidado.
  • O item retornado é uma chave de nível superior de e.response, não de e.response["Error"]. É por isso que o bloco usa e.response.get("Item"). É fácil procurar debaixo de ["Error"] junto de Code e Message, não encontrar nada e concluir que o parâmetro não funcionou.
  • O item volta em DynamoDB JSON mesmo que você esteja acostumado a valores nativos, porque este é o cliente de baixo nível. O boto3.dynamodb.types.TypeDeserializer converte se você quiser Python puro.
  • A API de recurso expressa a mesma proteção como objetos, ConditionExpression=Attr("Version").eq(7) & Attr("Artist").exists(), com valores nativos e sem mapas de placeholders. Ela lança a exceção idêntica, então o tratamento abaixo continua o mesmo.
  • Uma checagem que falha ainda cobra uma escrita. O Developer Guide é explícito ao dizer que uma condição falsa consome capacidade de escrita, dimensionada pelo maior entre o item antigo e o novo, então um retry sem limite em uma chave disputada custa dinheiro de verdade sem fazer progresso algum.

Onde o boto3 coloca o item retornado

Execute o bloco contra um Version armazenado igual a 9 e imprima as chaves da resposta da exceção. DynamoDB Local 3.3.0, boto3 1.43.58:

sorted(e.response.keys())  ->  ['Error', 'Item', 'ResponseMetadata']

e.response["Item"]  ->  {'Artist': {'S': 'Arturo Sandoval'},
                         'Year': {'N': '1994'},
                         'Version': {'N': '9'},
                         'SongTitle': {'S': 'Cubano Chant'},
                         'AlbumTitle': {'S': 'Danzon'}}

Remova ReturnValuesOnConditionCheckFailure e a mesma falha dá ['Error', 'ResponseMetadata']. A chave Item está ausente, e e.response.get("Item") retorna None em vez de lançar erro. Essa é a versão desse bug que sobrevive ao code review e começa a logar None em produção.

Por que todo nome na expressão tem alias

O bloco escreve #version e #cond0 em vez de Version e Artist, o que parece exagero para duas palavras comuns. E é, para essas duas. Version não é uma palavra reservada do DynamoDB, e usada diretamente ela passa na validação de nomes.

Year é reservada, e a mesma tabela tem uma. Faça a proteção diretamente sobre ela e você recebe:

ValidationException: Invalid ConditionExpression: Attribute name is a reserved keyword;
reserved keyword: Year

Há 573 palavras nessa lista, incluindo Name, Status, Size, Count, Data, Owner, Timestamp e Items. Dar alias a tudo é como o código gerado evita ter que saber qual é qual. Cole os nomes dos seus atributos no verificador de palavras reservadas e ele devolve o mapa ExpressionAttributeNames para os que precisam.

Para escrever essas proteções contra as suas próprias tabelas com o aliasing resolvido para você, 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.