Escritura condicional en DynamoDB en Python (boto3)

boto3 es el único SDK donde una escritura condicional tiene una clase de excepción con nombre que capturar, y también es el único donde el Item devuelto se esconde en un sitio que no adivinarías. La expresión en sí funciona igual en todas partes; las expresiones de condición de DynamoDB cubren las funciones y el patrón de bloqueo optimista.

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"))

Explicación

  • ConditionalCheckFailedException es una clase modelada, así que except client.exceptions.… funciona. La mayoría de los errores de DynamoDB no lo son: ValidationException no tiene clase alguna y hay que emparejarlo con e.response["Error"]["Code"]. La clase modelada sigue heredando de ClientError, así que un except ClientError amplio más arriba se la tragará si ordenas tus manejadores sin cuidado.
  • El Item devuelto es una clave de primer nivel de e.response, no de e.response["Error"]. Por eso el bloque lee e.response.get("Item"). Es fácil buscarlo bajo ["Error"] junto a Code y Message, no encontrar nada y concluir que el parámetro no funcionó.
  • El Item vuelve en DynamoDB JSON aunque estés acostumbrado a valores nativos, porque este es el cliente de bajo nivel. boto3.dynamodb.types.TypeDeserializer lo convierte si quieres Python plano.
  • La API de recursos expresa la misma guarda con objetos, ConditionExpression=Attr("Version").eq(7) & Attr("Artist").exists(), con valores nativos y sin mapas de marcadores. Lanza la misma excepción, así que el manejo de abajo no cambia.
  • Una comprobación fallida también factura una escritura. La Developer Guide es explícita en que una condición falsa consume capacidad de escritura, dimensionada según el mayor entre el Item antiguo y el nuevo, así que un reintento sin límite sobre una clave disputada cuesta dinero real mientras no avanza.

Dónde pone boto3 el Item devuelto

Ejecuta el bloque contra una Version almacenada de 9 e imprime las claves de la respuesta de la excepción. 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'}}

Quita ReturnValuesOnConditionCheckFailure y el mismo fallo da ['Error', 'ResponseMetadata']. La clave Item no está, y e.response.get("Item") devuelve None en vez de lanzar. Esa es la versión de este bug que sobrevive a la revisión de código y empieza a registrar None en producción.

Por qué cada nombre de la expresión lleva alias

El bloque escribe #version y #cond0 en vez de Version y Artist, lo que parece excesivo para dos palabras corrientes. Lo es, para estas dos. Version no es una palabra reservada de DynamoDB y, usada tal cual, pasa la validación de nombres.

Year sí es reservada, y la misma tabla tiene una. Usa una guarda directamente sobre ella y obtienes:

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

Hay 573 palabras en esa lista, incluidas Name, Status, Size, Count, Data, Owner, Timestamp e Items. Poner alias a todo es la forma en que el código generado evita tener que saber cuál es cuál. Pega tus nombres de atributo en el comprobador de palabras reservadas y te devuelve el mapa ExpressionAttributeNames para los que lo necesiten.

Para escribir estas guardas contra tus propias tablas con los alias resueltos por ti, descarga DynoTable.

Ejemplos relacionados

Referencias

Verificado por última vez el 2026-07-28 contra la documentación oficial de AWS enlazada arriba.

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.