Merge pull request #2598 from bblommers/feature/dynamodb_nested_list_append

Dynamodb: nested list_append
This commit is contained in:
Mike Grima 2019-12-09 14:10:00 -08:00 committed by GitHub
commit 90f5f7159d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 142 additions and 3 deletions

View File

@ -174,8 +174,13 @@ class DynamoType(object):
Returns DynamoType or None.
"""
if isinstance(key, six.string_types) and self.is_map() and key in self.value:
return DynamoType(self.value[key])
if isinstance(key, six.string_types) and self.is_map():
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():
idx = key
@ -418,7 +423,14 @@ class Item(BaseModel):
list_append_re = re.match("list_append\\((.+),(.+)\\)", value)
if list_append_re:
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():
raise ParamValidationError
old_list.value.extend(new_value["L"])

View File

@ -3237,6 +3237,7 @@ def test_update_supports_complex_expression_attribute_values():
@mock_dynamodb2
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.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
def test_update_catches_invalid_list_append_operation():
client = boto3.client("dynamodb", region_name="us-east-1")