DynamoDB PutItem in Python (boto3)

put_item schreibt ein ganzes Item und ersetzt jedes vorhandene Item mit demselben Primary Key (Item-basierte Aktionen behandelt, worin sich das von update_item unterscheidet). Beim Low-Level-Client wird jedes Attribut als DynamoDB JSON übergeben, und boto3 prüft diese Form lokal, bevor irgendetwas gesendet wird.

Code

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

Erklärung

{"N": 1994} erreicht AWS nie, und except ClientError fängt es nicht. Botocore validiert die Anfrage zuerst gegen sein eigenes Servicemodell, und ein Python-int dort, wo der N-Typ einen String will, scheitert genau da:

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

ParamValidationError stammt von BotoCoreError ab, nicht von ClientError — der Handler im Snippet oben lässt ihn also durch. Das ist meist genau das, was du willst, denn es ist ein Bug und kein geschäftliches Ergebnis, aber es heißt auch, dass ein try/except ClientError um einen Write herum kein Auffangnetz ist. Der Vorteil: Der Fehler nennt den exakten Pfad, Item.Year.N, was fürs Debuggen eine serverseitige ValidationException schlägt. Mehr dazu unter "Parameter validation failed".

Die vollständige Oberfläche einer fehlgeschlagenen Bedingung. Denselben bedingten Put zweimal zu fangen und alles auf der Exception auszugeben, ergab:

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

Daraus folgen zwei Dinge. Auf botocore 1.43.58 ist das Objekt eine modellierte Unterklasse, except client.exceptions.ConditionalCheckFailedException funktioniert also neben der err.response["Error"]["Code"]-Prüfung, die das Snippet nutzt; wähle eines und bleib dabei. Und str(e) ist ein formatierter Satz, nicht die Meldung des Dienstes — vergleiche ihn also nie mit einem Literal.

Eine fehlgeschlagene Bedingung wird trotzdem als Write abgerechnet. AWS: "if the expression evaluates to false, DynamoDB still consumes write capacity units from the table" (abgerufen am 2026-07-28). Eine Nur-Anlegen-Retry-Schleife zahlt für jeden abgelehnten Versuch. Zur Größenordnung: Ein erfolgreicher Put eines rund 15 KB großen Items meldete unter ReturnConsumedCapacity="TOTAL" "CapacityUnits": 15; Writes runden pro 1 KB auf, nicht pro 4 KB wie Reads.

Die Resource-API ist ein anderer Vertrag, und bei float merkst du es. boto3.resource("dynamodb").Table("Music").put_item(Item={...}) nimmt schlichtes Python entgegen und marshallt für dich, verweigert binäre Gleitkommazahlen aber rundheraus:

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

Verpacke den Wert in decimal.Decimal("4.5"), aus einem String statt aus einem Float, sonst steckt die Ungenauigkeit schon drin, bevor Decimal sie sieht. Beim Zurücklesen über dieselbe API kommt jede Zahl als Decimal — das ist eine echte Änderung an deinem Code, kein Formatierungsdetail. Siehe "Float types are not supported".

Die beiden APIs zu mischen ist die Falle, vor der keine von beiden warnt. Der Low-Level-Client nimmt bereitwillig ein {"N": "1.5"} an, einen Wert, den die Resource-API als Float abgelehnt hätte. Eine Codebasis, die mit der einen schreibt und mit der anderen liest, bekommt Decimal aus Daten zurück, die auf dem Hinweg nie durch Decimal gelaufen sind.

Die #cond0-Aliasse sind nicht kosmetisch. Sie lösen über ExpressionAttributeNames zu Artist/SongTitle auf. Inline geschriebene Attributnamen funktionieren genau so lange, bis einer mit einem reservierten Wort kollidiert — und dann scheitert die Expression an einem Namen, den du nicht angefasst hast.

Mach es visuell

Condition Expressions sind das, was beim Schreiben von Hand als Erstes schiefgeht, denn eine falsche scheitert als abgelehnter Write und nicht als Syntaxfehler. Der kostenlose DynamoDB Expression Builder baut die ConditionExpression samt Namens- und Wert-Maps zusammen und gibt den boto3-Aufruf fertig zum Einfügen aus.

Um Items gegen deine eigenen Tabellen zu schreiben und zu bearbeiten — ein Formular pro Attribut, Typ-Auswahl, das Ergebnis als boto3 zurückkopieren — lade DynoTable herunter.

Verwandte Leitfäden

Referenzen

Am 2026-07-28 mit boto3 1.43.58 / botocore 1.43.58 gegen DynamoDB Local (amazon/dynamodb-local) auf Port 9000 reproduziert. Der Exception-Text, die Antwortfelder und der Kapazitätswert sind aufgezeichnete Ausgabe, wortgetreu kopiert.

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.