Skip to content

Commit bfac8a8

Browse files
authored
DynamoDB: put_item() now returns old item for ConditionalCheckFailed exceptions (#7068)
1 parent 807dca6 commit bfac8a8

File tree

4 files changed

+56
-1
lines changed

4 files changed

+56
-1
lines changed

moto/dynamodb/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ def put_item(
225225
expression_attribute_names: Optional[Dict[str, Any]] = None,
226226
expression_attribute_values: Optional[Dict[str, Any]] = None,
227227
overwrite: bool = False,
228+
return_values_on_condition_check_failure: Optional[str] = None,
228229
) -> Item:
229230
table = self.get_table(table_name)
230231
return table.put_item(
@@ -234,6 +235,7 @@ def put_item(
234235
expression_attribute_names,
235236
expression_attribute_values,
236237
overwrite,
238+
return_values_on_condition_check_failure,
237239
)
238240

239241
def get_table_keys_name(

moto/dynamodb/models/table.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ def put_item(
517517
expression_attribute_names: Optional[Dict[str, str]] = None,
518518
expression_attribute_values: Optional[Dict[str, Any]] = None,
519519
overwrite: bool = False,
520+
return_values_on_condition_check_failure: Optional[str] = None,
520521
) -> Item:
521522
if self.hash_key_attr not in item_attrs.keys():
522523
raise MockValidationException(
@@ -571,7 +572,13 @@ def put_item(
571572
expression_attribute_values,
572573
)
573574
if not condition_op.expr(current):
574-
raise ConditionalCheckFailed
575+
if (
576+
return_values_on_condition_check_failure == "ALL_OLD"
577+
and current is not None
578+
):
579+
raise ConditionalCheckFailed(item=current.to_json()["Attributes"])
580+
else:
581+
raise ConditionalCheckFailed
575582

576583
if range_value:
577584
self.items[hash_value][range_value] = item

moto/dynamodb/responses.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,9 @@ def put_item(self) -> str:
456456
name = self.body["TableName"]
457457
item = self.body["Item"]
458458
return_values = self.body.get("ReturnValues", "NONE")
459+
return_values_on_condition_check_failure = self.body.get(
460+
"ReturnValuesOnConditionCheckFailure"
461+
)
459462

460463
if return_values not in ("ALL_OLD", "NONE"):
461464
raise MockValidationException("Return values set to invalid value")
@@ -498,6 +501,7 @@ def put_item(self) -> str:
498501
expression_attribute_names,
499502
expression_attribute_values,
500503
overwrite,
504+
return_values_on_condition_check_failure,
501505
)
502506

503507
item_dict = result.to_json()

tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,48 @@ def test_put_item_empty_set():
674674
)
675675

676676

677+
@mock_dynamodb
678+
def test_put_item_returns_old_item():
679+
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
680+
table = dynamodb.create_table(
681+
TableName="test-table",
682+
KeySchema=[{"AttributeName": "pk", "KeyType": "HASH"}],
683+
AttributeDefinitions=[{"AttributeName": "pk", "AttributeType": "S"}],
684+
BillingMode="PAY_PER_REQUEST",
685+
)
686+
687+
table.put_item(Item={"pk": "foo", "bar": "baz"})
688+
689+
with pytest.raises(ClientError) as exc:
690+
table.put_item(
691+
Item={"pk": "foo", "bar": "quuz"},
692+
ConditionExpression="attribute_not_exists(pk)",
693+
)
694+
resp = exc.value.response
695+
assert resp["Error"] == {
696+
"Message": "The conditional request failed",
697+
"Code": "ConditionalCheckFailedException",
698+
}
699+
assert resp["message"] == "The conditional request failed"
700+
assert "Item" not in resp
701+
702+
table.put_item(Item={"pk": "foo", "bar": "baz"})
703+
704+
with pytest.raises(ClientError) as exc:
705+
table.put_item(
706+
Item={"pk": "foo", "bar": "quuz"},
707+
ReturnValuesOnConditionCheckFailure="ALL_OLD",
708+
ConditionExpression="attribute_not_exists(pk)",
709+
)
710+
resp = exc.value.response
711+
assert resp["Error"] == {
712+
"Message": "The conditional request failed",
713+
"Code": "ConditionalCheckFailedException",
714+
}
715+
assert "message" not in resp
716+
assert resp["Item"] == {"pk": {"S": "foo"}, "bar": {"S": "baz"}}
717+
718+
677719
@mock_dynamodb
678720
def test_update_expression_with_trailing_comma():
679721
resource = boto3.resource(service_name="dynamodb", region_name="us-east-1")

0 commit comments

Comments
 (0)