DynamoDB PutItem em Python (boto3)

put_item grava um item inteiro e substitui qualquer item existente com a mesma chave primária (ações baseadas em item cobre como isso difere de update_item). Com o client de baixo nível, cada atributo é passado como JSON do DynamoDB, e o boto3 confere esse formato localmente antes de qualquer coisa ser enviada.

Código

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:
        raise

Explicação

{"N": 1994} nunca chega à AWS, e except ClientError não vai capturá-lo. O botocore valida a requisição contra o seu próprio modelo de serviço primeiro, e um int do Python onde o tipo N quer uma string falha ali:

ParamValidationError: Parameter validation failed:
Invalid type for parameter Item.Year.N, value: 1994, type: <class 'int'>, valid types: <class 'str'>

ParamValidationError descende de BotoCoreError, não de ClientError, então o handler do trecho acima o deixa passar. Normalmente é isso que você quer, já que é um bug e não um resultado de negócio, mas significa que um try/except ClientError em volta de uma escrita não é um pega-tudo. O lado bom é que o erro nomeia o caminho exato, Item.Year.N, o que é melhor para depurar do que um ValidationException do lado do servidor. Mais sobre ele em "Parameter validation failed".

A superfície completa de uma condição malsucedida. Capturar o mesmo put condicional duas vezes e imprimir tudo o que há na exceção deu:

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 failed

Duas coisas decorrem disso. No botocore 1.43.58 o objeto é uma subclasse modelada, então except client.exceptions.ConditionalCheckFailedException funciona junto com a verificação de err.response["Error"]["Code"] que o trecho usa; escolha uma e seja consistente. E str(e) é uma frase formatada, não a mensagem do serviço, então nunca a compare com um literal.

Uma condição malsucedida ainda cobra uma escrita. A AWS: "if the expression evaluates to false, DynamoDB still consumes write capacity units from the table" (consultada em 2026-07-28). Um loop de retry de criar-somente paga por cada tentativa rejeitada. Para dar escala, um put bem-sucedido de um item de ~15 KB reportou "CapacityUnits": 15 sob ReturnConsumedCapacity="TOTAL"; escritas arredondam para cima a cada 1 KB, e não os 4 KB que as leituras usam.

A API de resource é um contrato diferente, e float é onde você descobre isso. boto3.resource("dynamodb").Table("Music").put_item(Item={...}) aceita Python puro e faz o marshalling por você, mas recusa ponto flutuante binário de plano:

TypeError: Float types are not supported. Use Decimal types instead.

Envolva o valor em decimal.Decimal("4.5"), a partir de uma string e não de um float, ou a imprecisão já está embutida antes de o Decimal vê-la. Ler de volta pela mesma API retorna todo número como Decimal, o que é uma mudança real no seu código, não um detalhe de formatação. Veja "Float types are not supported".

Misturar as duas APIs é a armadilha sobre a qual nenhuma delas avisa. O client de baixo nível aceita alegremente {"N": "1.5"}, um valor que a API de resource teria rejeitado como float. Uma base de código que grava com uma e lê com a outra recebe Decimal de volta de dados que nunca passaram por Decimal na entrada.

Os aliases #cond0 não são cosméticos. Eles resolvem para Artist/SongTitle via ExpressionAttributeNames. Nomes de atributo inline funcionam até o momento em que um colide com uma palavra reservada, e então a expressão falha em um nome que você não mudou.

Faça isso visualmente

Expressões de condição são onde escrever à mão dá errado primeiro, porque uma errada falha como uma escrita rejeitada e não como um erro de sintaxe. O DynamoDB Expression Builder gratuito monta a ConditionExpression com os seus mapas de nomes e valores e emite a chamada boto3 pronta para colar.

Para gravar e editar itens contra as suas próprias tabelas — um formulário por atributo, seletores de tipo, copiar o resultado de volta como boto3 — baixe o DynoTable.

Guias relacionados

Referências

Reproduzido em 2026-07-28 com boto3 1.43.58 / botocore 1.43.58 contra o DynamoDB Local (amazon/dynamodb-local) na porta 9000. O texto da exceção, os campos da resposta e a leitura de capacidade são saída capturada, copiada literalmente.

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.