DynamoDB: fix: support top level lists for Dynamo Item to_json (#7380)
This commit is contained in:
parent
082e6c2fc0
commit
6505971ecb
@ -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}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"}}}]}}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user