Merge pull request #2809 from bblommers/feature/dynamodb-transact-get-items
Feature - DynamoDB: transact_get_items
This commit is contained in:
commit
77b1cc2321
@ -2237,7 +2237,7 @@
|
|||||||
- [ ] verify_trust
|
- [ ] verify_trust
|
||||||
|
|
||||||
## dynamodb
|
## dynamodb
|
||||||
17% implemented
|
24% implemented
|
||||||
- [ ] batch_get_item
|
- [ ] batch_get_item
|
||||||
- [ ] batch_write_item
|
- [ ] batch_write_item
|
||||||
- [ ] create_backup
|
- [ ] create_backup
|
||||||
@ -2268,7 +2268,7 @@
|
|||||||
- [ ] restore_table_to_point_in_time
|
- [ ] restore_table_to_point_in_time
|
||||||
- [X] scan
|
- [X] scan
|
||||||
- [ ] tag_resource
|
- [ ] tag_resource
|
||||||
- [ ] transact_get_items
|
- [X] transact_get_items
|
||||||
- [ ] transact_write_items
|
- [ ] transact_write_items
|
||||||
- [ ] untag_resource
|
- [ ] untag_resource
|
||||||
- [ ] update_continuous_backups
|
- [ ] update_continuous_backups
|
||||||
|
@ -10,6 +10,9 @@ from .exceptions import InvalidIndexNameError, InvalidUpdateExpression, ItemSize
|
|||||||
from .models import dynamodb_backends, dynamo_json_dump
|
from .models import dynamodb_backends, dynamo_json_dump
|
||||||
|
|
||||||
|
|
||||||
|
TRANSACTION_MAX_ITEMS = 25
|
||||||
|
|
||||||
|
|
||||||
def has_empty_keys_or_values(_dict):
|
def has_empty_keys_or_values(_dict):
|
||||||
if _dict == "":
|
if _dict == "":
|
||||||
return True
|
return True
|
||||||
@ -826,3 +829,67 @@ class DynamoHandler(BaseResponse):
|
|||||||
ttl_spec = self.dynamodb_backend.describe_ttl(name)
|
ttl_spec = self.dynamodb_backend.describe_ttl(name)
|
||||||
|
|
||||||
return json.dumps({"TimeToLiveDescription": ttl_spec})
|
return json.dumps({"TimeToLiveDescription": ttl_spec})
|
||||||
|
|
||||||
|
def transact_get_items(self):
|
||||||
|
transact_items = self.body["TransactItems"]
|
||||||
|
responses = list()
|
||||||
|
|
||||||
|
if len(transact_items) > TRANSACTION_MAX_ITEMS:
|
||||||
|
msg = "1 validation error detected: Value '["
|
||||||
|
err_list = list()
|
||||||
|
request_id = 268435456
|
||||||
|
for _ in transact_items:
|
||||||
|
request_id += 1
|
||||||
|
hex_request_id = format(request_id, "x")
|
||||||
|
err_list.append(
|
||||||
|
"com.amazonaws.dynamodb.v20120810.TransactGetItem@%s"
|
||||||
|
% hex_request_id
|
||||||
|
)
|
||||||
|
msg += ", ".join(err_list)
|
||||||
|
msg += (
|
||||||
|
"'] at 'transactItems' failed to satisfy constraint: "
|
||||||
|
"Member must have length less than or equal to %s"
|
||||||
|
% TRANSACTION_MAX_ITEMS
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.error("ValidationException", msg)
|
||||||
|
|
||||||
|
ret_consumed_capacity = self.body.get("ReturnConsumedCapacity", "NONE")
|
||||||
|
consumed_capacity = dict()
|
||||||
|
|
||||||
|
for transact_item in transact_items:
|
||||||
|
|
||||||
|
table_name = transact_item["Get"]["TableName"]
|
||||||
|
key = transact_item["Get"]["Key"]
|
||||||
|
try:
|
||||||
|
item = self.dynamodb_backend.get_item(table_name, key)
|
||||||
|
except ValueError:
|
||||||
|
er = "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
|
||||||
|
return self.error(er, "Requested resource not found")
|
||||||
|
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item_describe = item.describe_attrs(False)
|
||||||
|
responses.append(item_describe)
|
||||||
|
|
||||||
|
table_capacity = consumed_capacity.get(table_name, {})
|
||||||
|
table_capacity["TableName"] = table_name
|
||||||
|
capacity_units = table_capacity.get("CapacityUnits", 0) + 2.0
|
||||||
|
table_capacity["CapacityUnits"] = capacity_units
|
||||||
|
read_capacity_units = table_capacity.get("ReadCapacityUnits", 0) + 2.0
|
||||||
|
table_capacity["ReadCapacityUnits"] = read_capacity_units
|
||||||
|
consumed_capacity[table_name] = table_capacity
|
||||||
|
|
||||||
|
if ret_consumed_capacity == "INDEXES":
|
||||||
|
table_capacity["Table"] = {
|
||||||
|
"CapacityUnits": capacity_units,
|
||||||
|
"ReadCapacityUnits": read_capacity_units,
|
||||||
|
}
|
||||||
|
|
||||||
|
result = dict()
|
||||||
|
result.update({"Responses": responses})
|
||||||
|
if ret_consumed_capacity != "NONE":
|
||||||
|
result.update({"ConsumedCapacity": [v for v in consumed_capacity.values()]})
|
||||||
|
|
||||||
|
return dynamo_json_dump(result)
|
||||||
|
@ -6,8 +6,9 @@ import six
|
|||||||
import boto
|
import boto
|
||||||
import boto3
|
import boto3
|
||||||
from boto3.dynamodb.conditions import Attr, Key
|
from boto3.dynamodb.conditions import Attr, Key
|
||||||
import sure # noqa
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
import sure # noqa
|
||||||
from moto import mock_dynamodb2, mock_dynamodb2_deprecated
|
from moto import mock_dynamodb2, mock_dynamodb2_deprecated
|
||||||
from moto.dynamodb2 import dynamodb_backend2, dynamodb_backends2
|
from moto.dynamodb2 import dynamodb_backend2, dynamodb_backends2
|
||||||
from boto.exception import JSONResponseError
|
from boto.exception import JSONResponseError
|
||||||
@ -3810,3 +3811,218 @@ def test_query_catches_when_no_filters():
|
|||||||
ex.exception.response["Error"]["Message"].should.equal(
|
ex.exception.response["Error"]["Message"].should.equal(
|
||||||
"Either KeyConditions or QueryFilter should be present"
|
"Either KeyConditions or QueryFilter should be present"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_invalid_transact_get_items():
|
||||||
|
|
||||||
|
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
|
||||||
|
dynamodb.create_table(
|
||||||
|
TableName="test1",
|
||||||
|
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
|
||||||
|
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
||||||
|
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||||
|
)
|
||||||
|
table = dynamodb.Table("test1")
|
||||||
|
table.put_item(
|
||||||
|
Item={"id": "1", "val": "1",}
|
||||||
|
)
|
||||||
|
|
||||||
|
table.put_item(
|
||||||
|
Item={"id": "1", "val": "2",}
|
||||||
|
)
|
||||||
|
|
||||||
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as ex:
|
||||||
|
client.transact_get_items(
|
||||||
|
TransactItems=[
|
||||||
|
{"Get": {"Key": {"id": {"S": "1"}}, "TableName": "test1"}}
|
||||||
|
for i in range(26)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.exception.response["Error"]["Message"].should.match(
|
||||||
|
r"failed to satisfy constraint: Member must have length less than or equal to 25",
|
||||||
|
re.I,
|
||||||
|
)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as ex:
|
||||||
|
client.transact_get_items(
|
||||||
|
TransactItems=[
|
||||||
|
{"Get": {"Key": {"id": {"S": "1"},}, "TableName": "test1"}},
|
||||||
|
{"Get": {"Key": {"id": {"S": "1"},}, "TableName": "non_exists_table"}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
ex.exception.response["Error"]["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.exception.response["Error"]["Message"].should.equal(
|
||||||
|
"Requested resource not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_valid_transact_get_items():
|
||||||
|
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
|
||||||
|
dynamodb.create_table(
|
||||||
|
TableName="test1",
|
||||||
|
KeySchema=[
|
||||||
|
{"AttributeName": "id", "KeyType": "HASH"},
|
||||||
|
{"AttributeName": "sort_key", "KeyType": "RANGE"},
|
||||||
|
],
|
||||||
|
AttributeDefinitions=[
|
||||||
|
{"AttributeName": "id", "AttributeType": "S"},
|
||||||
|
{"AttributeName": "sort_key", "AttributeType": "S"},
|
||||||
|
],
|
||||||
|
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||||
|
)
|
||||||
|
table1 = dynamodb.Table("test1")
|
||||||
|
table1.put_item(
|
||||||
|
Item={"id": "1", "sort_key": "1",}
|
||||||
|
)
|
||||||
|
|
||||||
|
table1.put_item(
|
||||||
|
Item={"id": "1", "sort_key": "2",}
|
||||||
|
)
|
||||||
|
|
||||||
|
dynamodb.create_table(
|
||||||
|
TableName="test2",
|
||||||
|
KeySchema=[
|
||||||
|
{"AttributeName": "id", "KeyType": "HASH"},
|
||||||
|
{"AttributeName": "sort_key", "KeyType": "RANGE"},
|
||||||
|
],
|
||||||
|
AttributeDefinitions=[
|
||||||
|
{"AttributeName": "id", "AttributeType": "S"},
|
||||||
|
{"AttributeName": "sort_key", "AttributeType": "S"},
|
||||||
|
],
|
||||||
|
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||||
|
)
|
||||||
|
table2 = dynamodb.Table("test2")
|
||||||
|
table2.put_item(
|
||||||
|
Item={"id": "1", "sort_key": "1",}
|
||||||
|
)
|
||||||
|
|
||||||
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||||
|
res = client.transact_get_items(
|
||||||
|
TransactItems=[
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "non_exists_key"}, "sort_key": {"S": "2"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
res["Responses"][0]["Item"].should.equal({"id": {"S": "1"}, "sort_key": {"S": "1"}})
|
||||||
|
len(res["Responses"]).should.equal(1)
|
||||||
|
|
||||||
|
res = client.transact_get_items(
|
||||||
|
TransactItems=[
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "2"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
|
||||||
|
"TableName": "test2",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
res["Responses"][0]["Item"].should.equal({"id": {"S": "1"}, "sort_key": {"S": "1"}})
|
||||||
|
|
||||||
|
res["Responses"][1]["Item"].should.equal({"id": {"S": "1"}, "sort_key": {"S": "2"}})
|
||||||
|
|
||||||
|
res["Responses"][2]["Item"].should.equal({"id": {"S": "1"}, "sort_key": {"S": "1"}})
|
||||||
|
|
||||||
|
res = client.transact_get_items(
|
||||||
|
TransactItems=[
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "2"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
|
||||||
|
"TableName": "test2",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ReturnConsumedCapacity="TOTAL",
|
||||||
|
)
|
||||||
|
|
||||||
|
res["ConsumedCapacity"][0].should.equal(
|
||||||
|
{"TableName": "test1", "CapacityUnits": 4.0, "ReadCapacityUnits": 4.0}
|
||||||
|
)
|
||||||
|
|
||||||
|
res["ConsumedCapacity"][1].should.equal(
|
||||||
|
{"TableName": "test2", "CapacityUnits": 2.0, "ReadCapacityUnits": 2.0}
|
||||||
|
)
|
||||||
|
|
||||||
|
res = client.transact_get_items(
|
||||||
|
TransactItems=[
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "2"}},
|
||||||
|
"TableName": "test1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Get": {
|
||||||
|
"Key": {"id": {"S": "1"}, "sort_key": {"S": "1"}},
|
||||||
|
"TableName": "test2",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ReturnConsumedCapacity="INDEXES",
|
||||||
|
)
|
||||||
|
|
||||||
|
res["ConsumedCapacity"][0].should.equal(
|
||||||
|
{
|
||||||
|
"TableName": "test1",
|
||||||
|
"CapacityUnits": 4.0,
|
||||||
|
"ReadCapacityUnits": 4.0,
|
||||||
|
"Table": {"CapacityUnits": 4.0, "ReadCapacityUnits": 4.0,},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res["ConsumedCapacity"][1].should.equal(
|
||||||
|
{
|
||||||
|
"TableName": "test2",
|
||||||
|
"CapacityUnits": 2.0,
|
||||||
|
"ReadCapacityUnits": 2.0,
|
||||||
|
"Table": {"CapacityUnits": 2.0, "ReadCapacityUnits": 2.0,},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user