DynamoDB: fix: support top level lists for Dynamo Item to_json (#7380)

This commit is contained in:
Samuel Atkins-Turkish 2024-02-23 19:55:37 +00:00 committed by GitHub
parent 082e6c2fc0
commit 6505971ecb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 164 additions and 8 deletions

View File

@ -316,14 +316,20 @@ class Item(BaseModel):
return sum(bytesize(key) + value.size() for key, value in self.attrs.items()) return sum(bytesize(key) + value.size() for key, value in self.attrs.items())
def to_json(self) -> Dict[str, Any]: def to_json(self) -> Dict[str, Any]:
attributes = {} attributes: Dict[str, Any] = {}
for attribute_key, attribute in self.attrs.items(): for attribute_key, attribute in self.attrs.items():
if isinstance(attribute.value, dict): if isinstance(attribute.value, dict):
attr_value = { attr_dict_value = {
key: value.to_regular_json() key: value.to_regular_json()
for key, value in attribute.value.items() for key, value in attribute.value.items()
} }
attributes[attribute_key] = {attribute.type: attr_value} attributes[attribute_key] = {attribute.type: attr_dict_value}
elif isinstance(attribute.value, list):
attr_list_value = [
value.to_regular_json() if isinstance(value, DynamoType) else value
for value in attribute.value
]
attributes[attribute_key] = {attribute.type: attr_list_value}
else: else:
attributes[attribute_key] = {attribute.type: attribute.value} attributes[attribute_key] = {attribute.type: attribute.value}

View File

@ -964,12 +964,11 @@ class DynamoHandler(BaseResponse):
if len(changed) != len(original): if len(changed) != len(original):
return changed return changed
else: else:
return [ any_element_has_changed = any(
self._build_updated_new_attributes( changed[index] != original[index]
original[index], changed[index]
)
for index in range(len(changed)) for index in range(len(changed))
] )
return changed if any_element_has_changed else original
else: else:
return changed return changed

View File

@ -3,6 +3,7 @@ from decimal import Decimal
import boto3 import boto3
import pytest import pytest
from botocore.exceptions import ClientError
from moto import mock_aws from moto import mock_aws
@ -421,3 +422,153 @@ def test_condition_expression_with_reserved_keyword_as_attr_name():
item = table.get_item(Key={"id": "key-0"})["Item"] item = table.get_item(Key={"id": "key-0"})["Item"]
assert item == {"id": "key-0", "first": {}} assert item == {"id": "key-0", "first": {}}
@mock_aws
def test_condition_check_failure_exception_is_raised_when_values_are_returned_for_an_item_with_a_top_level_list():
# This explicitly tests for a failure in handling JSONification of DynamoType
# when lists are at the top level of an item.
# This exception should not be raised:
# TypeError: Object of type DynamoType is not JSON serializable
dynamodb_client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb_client.create_table(
TableName="example_table",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
record = {
"id": {"S": "example_id"},
"some_list": {"L": [{"M": {"hello": {"S": "h"}}}]},
}
dynamodb_client.put_item(
TableName="example_table",
Item=record,
)
with pytest.raises(ClientError) as error:
dynamodb_client.update_item(
TableName="example_table",
Key={"id": {"S": "example_id"}},
UpdateExpression="set some_list=list_append(some_list, :w)",
ExpressionAttributeValues={
":w": {"L": [{"M": {"world": {"S": "w"}}}]},
":id": {"S": "incorrect id"},
},
ConditionExpression="id = :id",
ReturnValuesOnConditionCheckFailure="ALL_OLD",
)
assert error.type.__name__ == "ConditionalCheckFailedException"
assert error.value.response["Error"] == {
"Message": "The conditional request failed",
"Code": "ConditionalCheckFailedException",
}
assert error.value.response["Item"] == {
"id": {"S": "example_id"},
"some_list": {"L": [{"M": {"hello": {"S": "h"}}}]},
}
@mock_aws
def test_condition_check_failure_exception_is_raised_when_values_are_returned_for_an_item_with_a_top_level_string_set():
# This explicitly tests for a failure in handling JSONification of DynamoType
# when string sets are at the top level of an item.
# These exception should not be raised:
# TypeError: Object of type DynamoType is not JSON serializable
# AttributeError: 'str' object has no attribute 'to_regular_json'
dynamodb_client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb_client.create_table(
TableName="example_table",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
record = {
"id": {"S": "example_id"},
"some_list": {"SS": ["hello"]},
}
dynamodb_client.put_item(
TableName="example_table",
Item=record,
)
with pytest.raises(ClientError) as error:
dynamodb_client.update_item(
TableName="example_table",
Key={"id": {"S": "example_id"}},
UpdateExpression="set some_list=list_append(some_list, :w)",
ExpressionAttributeValues={
":w": {"SS": ["world"]},
":id": {"S": "incorrect id"},
},
ConditionExpression="id = :id",
ReturnValuesOnConditionCheckFailure="ALL_OLD",
)
assert error.type.__name__ == "ConditionalCheckFailedException"
assert error.value.response["Error"] == {
"Message": "The conditional request failed",
"Code": "ConditionalCheckFailedException",
}
assert error.value.response["Item"] == {
"id": {"S": "example_id"},
"some_list": {"SS": ["hello"]},
}
@mock_aws
def test_condition_check_failure_exception_is_raised_when_values_are_returned_for_an_item_with_a_list_in_a_map():
# This explicitly tests for a failure in handling JSONification of DynamoType
# when lists are inside a map
dynamodb_client = boto3.client("dynamodb", region_name="us-east-1")
dynamodb_client.create_table(
TableName="example_table",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
record = {
"id": {"S": "example_id"},
"some_list_in_a_map": {
"M": {"some_list": {"L": [{"M": {"hello": {"S": "h"}}}]}}
},
}
dynamodb_client.put_item(
TableName="example_table",
Item=record,
)
with pytest.raises(ClientError) as error:
dynamodb_client.update_item(
TableName="example_table",
Key={"id": {"S": "example_id"}},
UpdateExpression="set some_list_in_a_map.some_list=list_append(some_list_in_a_map.some_list, :w)",
ExpressionAttributeValues={
":w": {"L": [{"M": {"world": {"S": "w"}}}]},
":id": {"S": "incorrect id"},
},
ConditionExpression="id = :id",
ReturnValuesOnConditionCheckFailure="ALL_OLD",
)
assert error.type.__name__ == "ConditionalCheckFailedException"
assert error.value.response["Error"] == {
"Message": "The conditional request failed",
"Code": "ConditionalCheckFailedException",
}
assert error.value.response["Item"] == {
"id": {"S": "example_id"},
"some_list_in_a_map": {
"M": {"some_list": {"L": [{"M": {"hello": {"S": "h"}}}]}}
},
}