add support for dynamodb transact_get_items
This commit is contained in:
parent
1aa99bb405
commit
b74625db0c
@ -2237,7 +2237,7 @@
|
||||
- [ ] verify_trust
|
||||
|
||||
## dynamodb
|
||||
17% implemented
|
||||
24% implemented
|
||||
- [ ] batch_get_item
|
||||
- [ ] batch_write_item
|
||||
- [ ] create_backup
|
||||
@ -2268,7 +2268,7 @@
|
||||
- [ ] restore_table_to_point_in_time
|
||||
- [X] scan
|
||||
- [ ] tag_resource
|
||||
- [ ] transact_get_items
|
||||
- [X] transact_get_items
|
||||
- [ ] transact_write_items
|
||||
- [ ] untag_resource
|
||||
- [ ] update_continuous_backups
|
||||
|
@ -10,6 +10,9 @@ from .exceptions import InvalidIndexNameError, InvalidUpdateExpression, ItemSize
|
||||
from .models import dynamodb_backends, dynamo_json_dump
|
||||
|
||||
|
||||
TRANSACTION_MAX_ITEMS = 10
|
||||
|
||||
|
||||
def has_empty_keys_or_values(_dict):
|
||||
if _dict == "":
|
||||
return True
|
||||
@ -828,3 +831,69 @@ class DynamoHandler(BaseResponse):
|
||||
ttl_spec = self.dynamodb_backend.describe_ttl(name)
|
||||
|
||||
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)
|
||||
|
||||
dedup_list = [i for n, i in enumerate(transact_items) if i not in transact_items[n + 1:]]
|
||||
if len(transact_items) != len(dedup_list):
|
||||
er = 'com.amazon.coral.validate#ValidationException'
|
||||
return self.error(er, 'Transaction request cannot include multiple operations on one item')
|
||||
|
||||
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 as e:
|
||||
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 boto3
|
||||
from boto3.dynamodb.conditions import Attr, Key
|
||||
import sure # noqa
|
||||
import re
|
||||
import requests
|
||||
import sure # noqa
|
||||
from moto import mock_dynamodb2, mock_dynamodb2_deprecated
|
||||
from moto.dynamodb2 import dynamodb_backend2, dynamodb_backends2
|
||||
from boto.exception import JSONResponseError
|
||||
@ -3792,3 +3793,308 @@ def test_query_catches_when_no_filters():
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"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'
|
||||
}
|
||||
},
|
||||
{
|
||||
'Get': {
|
||||
'Key': {
|
||||
'id': {'S': '1'},
|
||||
},
|
||||
'TableName': 'test1'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
ex.exception.response['Error']['Code'].should.equal('ValidationException')
|
||||
ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||
ex.exception.response['Error']['Message'].should.equal(
|
||||
'Transaction request cannot include multiple operations on one item'
|
||||
)
|
||||
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.transact_get_items(TransactItems=[
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
{'Get': {'Key': {'id': {'S': '1'}}, 'TableName': 'test1'}},
|
||||
])
|
||||
|
||||
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 10', 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