Scrittura condizionale DynamoDB in Python (boto3)

boto3 è l'unico SDK in cui una scrittura condizionale ha una classe di eccezione dedicata da catturare, ed è anche quello in cui l'Item restituito si nasconde in un posto che non indovineresti. L'espressione in sé funziona allo stesso modo ovunque; le espressioni di condizione DynamoDB coprono le funzioni e il pattern di optimistic locking.

Codice

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

Spiegazione

  • ConditionalCheckFailedException è una classe modellata, quindi except client.exceptions.… funziona. La maggior parte degli errori DynamoDB non lo è: ValidationException non ha alcuna classe e va confrontata su e.response["Error"]["Code"]. La classe modellata è comunque una sottoclasse di ClientError, quindi un except ClientError generico più a monte se la mangerà se ordini i tuoi handler con leggerezza.
  • L'Item restituito è una chiave di primo livello di e.response, non di e.response["Error"]. Ecco perché il blocco legge e.response.get("Item"). È facile andare a cercarlo sotto ["Error"] accanto a Code e Message, non trovare nulla e concludere che il parametro non abbia funzionato.
  • L'Item torna in DynamoDB JSON anche se magari sei abituato ai valori nativi, perché questo è il client di basso livello. boto3.dynamodb.types.TypeDeserializer lo converte se vuoi Python semplice.
  • L'API resource esprime la stessa guardia come oggetti, ConditionExpression=Attr("Version").eq(7) & Attr("Artist").exists(), con valori nativi e senza mappe di placeholder. Solleva l'eccezione identica, quindi la gestione qui sotto resta invariata.
  • Un check fallito fattura comunque una scrittura. La Developer Guide è esplicita sul fatto che una condizione falsa consuma capacità di scrittura, dimensionata sul maggiore tra il vecchio e il nuovo Item, quindi un retry senza limiti su una chiave contesa costa soldi veri senza fare progressi.

Dove boto3 mette l'Item restituito

Esegui il blocco contro una Version memorizzata pari a 9 e stampa le chiavi della response dell'eccezione. 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'}}

Togli ReturnValuesOnConditionCheckFailure e lo stesso fallimento dà ['Error', 'ResponseMetadata']. La chiave Item è assente, e e.response.get("Item") restituisce None invece di sollevare un'eccezione. È la versione di questo bug che sopravvive alla code review e inizia a loggare None in produzione.

Perché ogni nome nell'espressione è aliasato

Il blocco scrive #version e #cond0 invece di Version e Artist, il che sembra eccessivo per due parole ordinarie. Lo è, per queste due. Version non è una parola riservata di DynamoDB, e usata nuda passa la validazione dei nomi.

Year è riservata, e la stessa tabella ne ha una. Metti una guardia direttamente su di essa e ottieni:

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

Su quella lista ci sono 573 parole, tra cui Name, Status, Size, Count, Data, Owner, Timestamp e Items. Aliasare tutto è il modo in cui il codice generato evita di dover sapere quale sia quale. Incolla i nomi dei tuoi attributi nel checker delle parole riservate e ti restituisce la mappa ExpressionAttributeNames per quelli che ne hanno bisogno.

Per scrivere queste guardie sulle tue tabelle con l'aliasing gestito al posto tuo, scarica DynoTable.

Esempi correlati

Riferimenti

Ultima verifica 2026-07-28 rispetto alla documentazione ufficiale AWS collegata sopra.

Lavora con DynamoDB senza la Console

Un client desktop veloce per DynamoDB che esegue il vero SQL che DynamoDB non può — JOINs, GROUP BY, aggregazioni — con modifica visuale e un agente AI sulle tue chiavi Bedrock.

Prova gratuita di 30 giorni, senza carta di credito — poi il piano Free senza limiti di tempo.