Merge pull request #2598 from bblommers/feature/dynamodb_nested_list_append
Dynamodb: nested list_append
This commit is contained in:
commit
90f5f7159d
@ -174,8 +174,13 @@ class DynamoType(object):
|
|||||||
|
|
||||||
Returns DynamoType or None.
|
Returns DynamoType or None.
|
||||||
"""
|
"""
|
||||||
if isinstance(key, six.string_types) and self.is_map() and key in self.value:
|
if isinstance(key, six.string_types) and self.is_map():
|
||||||
return DynamoType(self.value[key])
|
if "." in key and key.split(".")[0] in self.value:
|
||||||
|
return self.value[key.split(".")[0]].child_attr(
|
||||||
|
".".join(key.split(".")[1:])
|
||||||
|
)
|
||||||
|
elif "." not in key and key in self.value:
|
||||||
|
return DynamoType(self.value[key])
|
||||||
|
|
||||||
if isinstance(key, int) and self.is_list():
|
if isinstance(key, int) and self.is_list():
|
||||||
idx = key
|
idx = key
|
||||||
@ -418,7 +423,14 @@ class Item(BaseModel):
|
|||||||
list_append_re = re.match("list_append\\((.+),(.+)\\)", value)
|
list_append_re = re.match("list_append\\((.+),(.+)\\)", value)
|
||||||
if list_append_re:
|
if list_append_re:
|
||||||
new_value = expression_attribute_values[list_append_re.group(2).strip()]
|
new_value = expression_attribute_values[list_append_re.group(2).strip()]
|
||||||
old_list = self.attrs[list_append_re.group(1)]
|
old_list_key = list_append_re.group(1)
|
||||||
|
# Get the existing value
|
||||||
|
old_list = self.attrs[old_list_key.split(".")[0]]
|
||||||
|
if "." in old_list_key:
|
||||||
|
# Value is nested inside a map - find the appropriate child attr
|
||||||
|
old_list = old_list.child_attr(
|
||||||
|
".".join(old_list_key.split(".")[1:])
|
||||||
|
)
|
||||||
if not old_list.is_list():
|
if not old_list.is_list():
|
||||||
raise ParamValidationError
|
raise ParamValidationError
|
||||||
old_list.value.extend(new_value["L"])
|
old_list.value.extend(new_value["L"])
|
||||||
|
@ -3237,6 +3237,7 @@ def test_update_supports_complex_expression_attribute_values():
|
|||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
def test_update_supports_list_append():
|
def test_update_supports_list_append():
|
||||||
|
# Verify whether the list_append operation works as expected
|
||||||
client = boto3.client("dynamodb", region_name="us-east-1")
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||||
|
|
||||||
client.create_table(
|
client.create_table(
|
||||||
@ -3270,6 +3271,132 @@ def test_update_supports_list_append():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_update_supports_nested_list_append():
|
||||||
|
# Verify whether we can append a list that's inside a map
|
||||||
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||||
|
|
||||||
|
client.create_table(
|
||||||
|
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
||||||
|
TableName="TestTable",
|
||||||
|
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
|
||||||
|
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||||
|
)
|
||||||
|
client.put_item(
|
||||||
|
TableName="TestTable",
|
||||||
|
Item={
|
||||||
|
"id": {"S": "nested_list_append"},
|
||||||
|
"a": {"M": {"b": {"L": [{"S": "bar1"}]}}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update item using list_append expression
|
||||||
|
client.update_item(
|
||||||
|
TableName="TestTable",
|
||||||
|
Key={"id": {"S": "nested_list_append"}},
|
||||||
|
UpdateExpression="SET a.#b = list_append(a.#b, :i)",
|
||||||
|
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
|
||||||
|
ExpressionAttributeNames={"#b": "b"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify item is appended to the existing list
|
||||||
|
result = client.get_item(
|
||||||
|
TableName="TestTable", Key={"id": {"S": "nested_list_append"}}
|
||||||
|
)["Item"]
|
||||||
|
result.should.equal(
|
||||||
|
{
|
||||||
|
"id": {"S": "nested_list_append"},
|
||||||
|
"a": {"M": {"b": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_update_supports_multiple_levels_nested_list_append():
|
||||||
|
# Verify whether we can append a list that's inside a map that's inside a map (Inception!)
|
||||||
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||||
|
|
||||||
|
client.create_table(
|
||||||
|
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
||||||
|
TableName="TestTable",
|
||||||
|
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
|
||||||
|
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||||
|
)
|
||||||
|
client.put_item(
|
||||||
|
TableName="TestTable",
|
||||||
|
Item={
|
||||||
|
"id": {"S": "nested_list_append"},
|
||||||
|
"a": {"M": {"b": {"M": {"c": {"L": [{"S": "bar1"}]}}}}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update item using list_append expression
|
||||||
|
client.update_item(
|
||||||
|
TableName="TestTable",
|
||||||
|
Key={"id": {"S": "nested_list_append"}},
|
||||||
|
UpdateExpression="SET a.#b.c = list_append(a.#b.#c, :i)",
|
||||||
|
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
|
||||||
|
ExpressionAttributeNames={"#b": "b", "#c": "c"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify item is appended to the existing list
|
||||||
|
result = client.get_item(
|
||||||
|
TableName="TestTable", Key={"id": {"S": "nested_list_append"}}
|
||||||
|
)["Item"]
|
||||||
|
result.should.equal(
|
||||||
|
{
|
||||||
|
"id": {"S": "nested_list_append"},
|
||||||
|
"a": {"M": {"b": {"M": {"c": {"L": [{"S": "bar1"}, {"S": "bar2"}]}}}}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_update_supports_nested_list_append_onto_another_list():
|
||||||
|
# Verify whether we can take the contents of one list, and use that to fill another list
|
||||||
|
# Note that the contents of the other list is completely overwritten
|
||||||
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||||
|
|
||||||
|
client.create_table(
|
||||||
|
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
||||||
|
TableName="TestTable",
|
||||||
|
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
|
||||||
|
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||||
|
)
|
||||||
|
client.put_item(
|
||||||
|
TableName="TestTable",
|
||||||
|
Item={
|
||||||
|
"id": {"S": "list_append_another"},
|
||||||
|
"a": {"M": {"b": {"L": [{"S": "bar1"}]}, "c": {"L": [{"S": "car1"}]}}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update item using list_append expression
|
||||||
|
client.update_item(
|
||||||
|
TableName="TestTable",
|
||||||
|
Key={"id": {"S": "list_append_another"}},
|
||||||
|
UpdateExpression="SET a.#c = list_append(a.#b, :i)",
|
||||||
|
ExpressionAttributeValues={":i": {"L": [{"S": "bar2"}]}},
|
||||||
|
ExpressionAttributeNames={"#b": "b", "#c": "c"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify item is appended to the existing list
|
||||||
|
result = client.get_item(
|
||||||
|
TableName="TestTable", Key={"id": {"S": "list_append_another"}}
|
||||||
|
)["Item"]
|
||||||
|
result.should.equal(
|
||||||
|
{
|
||||||
|
"id": {"S": "list_append_another"},
|
||||||
|
"a": {
|
||||||
|
"M": {
|
||||||
|
"b": {"L": [{"S": "bar1"}]},
|
||||||
|
"c": {"L": [{"S": "bar1"}, {"S": "bar2"}]},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
def test_update_catches_invalid_list_append_operation():
|
def test_update_catches_invalid_list_append_operation():
|
||||||
client = boto3.client("dynamodb", region_name="us-east-1")
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user