DynamoDB Conditional Write in Python (boto3)

boto3 ist das eine SDK, in dem ein Conditional Write eine benannte Exception-Klasse zum Abfangen hat — und zugleich das eine, in dem sich das zurückgelieferte Item an einer Stelle versteckt, die du nicht erraten würdest. Die Expression selbst funktioniert überall gleich; DynamoDB Condition Expressions behandelt die Funktionen und das Optimistic-Locking-Muster.

Code

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

Erklärung

  • ConditionalCheckFailedException ist eine modellierte Klasse, except client.exceptions.… funktioniert also. Die meisten DynamoDB-Fehler sind es nicht: ValidationException hat überhaupt keine Klasse und muss über e.response["Error"]["Code"] erkannt werden. Die modellierte Klasse erbt weiterhin von ClientError — ein weit gefasstes except ClientError weiter oben verschluckt sie also, wenn du deine Handler unbedacht anordnest.
  • Das zurückgelieferte Item ist ein Top-Level-Key von e.response, nicht von e.response["Error"]. Deshalb liest der Block e.response.get("Item"). Es ist leicht, unter ["Error"] neben Code und Message zu suchen, nichts zu finden und zu schließen, der Parameter habe nicht funktioniert.
  • Das Item kommt als DynamoDB JSON zurück, auch wenn du native Werte gewohnt sein magst, denn das hier ist der Low-Level-Client. boto3.dynamodb.types.TypeDeserializer konvertiert es, wenn du reines Python willst.
  • Die Resource-API drückt denselben Guard als Objekte aus, ConditionExpression=Attr("Version").eq(7) & Attr("Artist").exists(), mit nativen Werten und ohne Platzhalter-Maps. Sie wirft dieselbe Exception, die Behandlung unten bleibt also unverändert.
  • Eine fehlgeschlagene Prüfung wird trotzdem als Write abgerechnet. Der Developer Guide sagt ausdrücklich, dass eine false-Bedingung Write-Kapazität verbraucht, bemessen am größeren von altem und neuem Item — ein unbegrenzter Retry auf einem umkämpften Key kostet also echtes Geld, ohne voranzukommen.

Wo boto3 das zurückgelieferte Item ablegt

Führe den Block gegen eine gespeicherte Version von 9 aus und gib die Response-Keys der Exception aus. 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'}}

Lässt du ReturnValuesOnConditionCheckFailure weg, liefert derselbe Fehlschlag ['Error', 'ResponseMetadata']. Der Item-Key fehlt, und e.response.get("Item") gibt None zurück, statt zu werfen. Das ist die Variante dieses Bugs, die das Code-Review überlebt und anfängt, in Produktion None zu loggen.

Warum jeder Name in der Expression aliasiert ist

Der Block schreibt #version und #cond0 statt Version und Artist, was für zwei gewöhnliche Wörter übertrieben aussieht. Für diese zwei ist es das auch. Version ist kein reserviertes DynamoDB-Wort und besteht die Namensvalidierung auch nackt.

Year ist reserviert, und dieselbe Tabelle hat eins. Prüfe direkt darauf und du bekommst:

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

573 Wörter stehen auf dieser Liste, darunter Name, Status, Size, Count, Data, Owner, Timestamp und Items. Alles zu aliasieren ist die Art, wie generierter Code es vermeidet, jemals wissen zu müssen, was was ist. Füg deine Attributnamen in den Reserved-Words-Checker ein und er gibt dir die ExpressionAttributeNames-Map für die zurück, die sie brauchen.

Um diese Guards gegen deine eigenen Tabellen zu schreiben, mit erledigtem Aliasing, lade DynoTable herunter.

Verwandte Beispiele

Referenzen

Zuletzt verifiziert am 2026-07-28 gegen die oben verlinkte offizielle AWS-Dokumentation.

Mit DynamoDB ohne die Console arbeiten

Ein schneller DynamoDB-Desktop-Client, der das echte SQL ausführt, das DynamoDB nicht kann — JOINs, GROUP BY, Aggregationen — mit visueller Bearbeitung und einem KI-Agenten auf deinen eigenen Bedrock-Schlüsseln.

30 Tage kostenlos testen, keine Kreditkarte — danach der Kostenlos-Tarif ohne Zeitlimit.