DynamoDB PutItem en Python (boto3)

put_item escribe un elemento entero y reemplaza cualquier elemento existente con la misma clave principal (acciones sobre elementos cubre en qué se diferencia de update_item). Con el cliente de bajo nivel cada atributo se pasa como JSON de DynamoDB, y boto3 comprueba esa forma localmente antes de enviar nada.

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

Explicación

{"N": 1994} nunca llega a AWS, y except ClientError no lo capturará. Botocore valida primero la petición contra su propio modelo de servicio, y un int de Python donde el tipo N quiere una cadena falla ahí:

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

ParamValidationError desciende de BotoCoreError, no de ClientError, así que el handler del fragmento de arriba lo deja pasar. Eso suele ser lo que quieres, ya que es un bug y no un resultado de negocio, pero significa que un try/except ClientError alrededor de una escritura no lo captura todo. Lo bueno es que el error nombra la ruta exacta, Item.Year.N, lo que para depurar gana a una ValidationException del lado del servidor. Más sobre esto en "Parameter validation failed".

La superficie completa de una condición fallida. Capturar el mismo put condicional dos veces e imprimir todo lo que trae la excepción dio:

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

De ahí se siguen dos cosas. En botocore 1.43.58 el objeto es una subclase modelada, así que except client.exceptions.ConditionalCheckFailedException funciona igual que la comprobación de err.response["Error"]["Code"] que usa el fragmento; elige una y sé coherente. Y str(e) es una frase formateada, no el mensaje del servicio, así que nunca lo compares con un literal.

Una condición fallida factura una escritura igualmente. AWS: "if the expression evaluates to false, DynamoDB still consumes write capacity units from the table" (consultada el 2026-07-28). Un bucle de reintentos de solo-creación paga cada intento rechazado. Para hacerte una idea, un put correcto de un elemento de ~15 KB informó de "CapacityUnits": 15 con ReturnConsumedCapacity="TOTAL"; las escrituras redondean por 1 KB, no por los 4 KB que usan las lecturas.

La API de recursos es un contrato distinto, y float es donde te enteras. boto3.resource("dynamodb").Table("Music").put_item(Item={...}) acepta Python plano y hace el marshalling por ti, pero rechaza de plano el punto flotante binario:

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

Envuelve el valor en decimal.Decimal("4.5"), a partir de una cadena y no de un float, o la imprecisión ya está horneada antes de que Decimal la vea. Leer de vuelta por la misma API devuelve cada número como Decimal, lo que es un cambio real en tu código, no un detalle de formato. Consulta "Float types are not supported".

Mezclar las dos API es la trampa de la que ninguna avisa. El cliente de bajo nivel acepta encantado {"N": "1.5"}, un valor que la API de recursos habría rechazado por ser un float. Una base de código que escribe con una y lee con la otra recibe Decimal a partir de datos que nunca pasaron por Decimal a la ida.

Los alias #cond0 no son cosméticos. Se resuelven a Artist/SongTitle mediante ExpressionAttributeNames. Los nombres de atributo en línea funcionan hasta que uno choca con una palabra reservada, y entonces la expresión falla por un nombre que no cambiaste.

Hazlo visualmente

Las expresiones de condición son donde escribir a mano se tuerce primero, porque una equivocada falla como escritura rechazada y no como error de sintaxis. El DynamoDB Expression Builder gratuito monta la ConditionExpression con sus mapas de nombres y valores y emite la llamada de boto3 lista para pegar.

Para escribir y editar elementos contra tus propias tablas — un formulario por atributo, selectores de tipo, copiar el resultado de vuelta como boto3 — descarga DynoTable.

Guías relacionadas

Referencias

Reproducido el 2026-07-28 con boto3 1.43.58 / botocore 1.43.58 contra DynamoDB Local (amazon/dynamodb-local) en el puerto 9000. El texto de las excepciones, los campos de la respuesta y la lectura de capacidad son salida capturada, copiada literalmente.

Trabaja con DynamoDB sin la Consola

Un cliente de escritorio rápido para DynamoDB que ejecuta el SQL real que DynamoDB no puede — JOINs, GROUP BY, agregaciones — con edición visual y un agente de IA con tus propias claves de Bedrock.

Prueba gratuita de 30 días, sin tarjeta — después, el plan Free sin límite de tiempo.