Merge pull request #371 from apjaffe/master

Adds the ConditionalCheckFailedException to put_item
This commit is contained in:
Steve Pulec 2015-07-08 20:54:53 -04:00
commit af45424b9e
4 changed files with 141 additions and 4 deletions

View File

@ -191,7 +191,7 @@ class Table(object):
keys.append(key['AttributeName']) keys.append(key['AttributeName'])
return keys return keys
def put_item(self, item_attrs): def put_item(self, item_attrs, expected = None, overwrite = False):
hash_value = DynamoType(item_attrs.get(self.hash_key_attr)) hash_value = DynamoType(item_attrs.get(self.hash_key_attr))
if self.has_range_key: if self.has_range_key:
range_value = DynamoType(item_attrs.get(self.range_key_attr)) range_value = DynamoType(item_attrs.get(self.range_key_attr))
@ -200,6 +200,35 @@ class Table(object):
item = Item(hash_value, self.hash_key_type, range_value, self.range_key_type, item_attrs) item = Item(hash_value, self.hash_key_type, range_value, self.range_key_type, item_attrs)
if not overwrite:
if expected is None:
expected = {}
lookup_range_value = range_value
else:
expected_range_value = expected.get(self.range_key_attr, {}).get("Value")
if(expected_range_value is None):
lookup_range_value = range_value
else:
lookup_range_value = DynamoType(expected_range_value)
current = self.get_item(hash_value, lookup_range_value)
if current is None:
current_attr = {}
elif hasattr(current,'attrs'):
current_attr = current.attrs
else:
current_attr = current
for key, val in expected.items():
if 'Exists' in val and val['Exists'] == False:
if key in current_attr:
raise ValueError("The conditional request failed")
elif key not in current_attr:
raise ValueError("The conditional request failed")
elif DynamoType(val['Value']).value != current_attr[key].value:
raise ValueError("The conditional request failed")
if range_value: if range_value:
self.items[hash_value][range_value] = item self.items[hash_value][range_value] = item
else: else:
@ -317,11 +346,11 @@ class DynamoDBBackend(BaseBackend):
table.throughput = throughput table.throughput = throughput
return table return table
def put_item(self, table_name, item_attrs): def put_item(self, table_name, item_attrs, expected = None, overwrite = False):
table = self.tables.get(table_name) table = self.tables.get(table_name)
if not table: if not table:
return None return None
return table.put_item(item_attrs) return table.put_item(item_attrs, expected, overwrite)
def get_table_keys_name(self, table_name, keys): def get_table_keys_name(self, table_name, keys):
""" """

View File

@ -134,7 +134,17 @@ class DynamoHandler(BaseResponse):
def put_item(self): def put_item(self):
name = self.body['TableName'] name = self.body['TableName']
item = self.body['Item'] item = self.body['Item']
result = dynamodb_backend2.put_item(name, item) overwrite = 'Expected' not in self.body
if not overwrite:
expected = self.body['Expected']
else:
expected = None
try:
result = dynamodb_backend2.put_item(name, item, expected, overwrite)
except Exception:
er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException'
return self.error(er)
if result: if result:
item_dict = result.to_json() item_dict = result.to_json()

View File

@ -10,6 +10,7 @@ try:
from boto.dynamodb2.fields import GlobalAllIndex, HashKey, RangeKey from boto.dynamodb2.fields import GlobalAllIndex, HashKey, RangeKey
from boto.dynamodb2.table import Item, Table from boto.dynamodb2.table import Item, Table
from boto.dynamodb2.exceptions import ValidationException from boto.dynamodb2.exceptions import ValidationException
from boto.dynamodb2.exceptions import ConditionalCheckFailedException
except ImportError: except ImportError:
pass pass
@ -553,3 +554,52 @@ def test_lookup():
message = table.lookup(hash_key, range_key) message = table.lookup(hash_key, range_key)
message.get('test_hash').should.equal(Decimal(hash_key)) message.get('test_hash').should.equal(Decimal(hash_key))
message.get('test_range').should.equal(Decimal(range_key)) message.get('test_range').should.equal(Decimal(range_key))
@mock_dynamodb2
def test_failed_overwrite():
from decimal import Decimal
table = Table.create('messages', schema=[
HashKey('id'),
RangeKey('range'),
], throughput={
'read': 7,
'write': 3,
})
data1 = {'id': '123', 'range': 'abc', 'data':'678'}
table.put_item(data=data1)
data2 = {'id': '123', 'range': 'abc', 'data':'345'}
table.put_item(data=data2, overwrite = True)
data3 = {'id': '123', 'range': 'abc', 'data':'812'}
table.put_item.when.called_with(data=data3).should.throw(ConditionalCheckFailedException)
returned_item = table.lookup('123', 'abc')
dict(returned_item).should.equal(data2)
data4 = {'id': '123', 'range': 'ghi', 'data':812}
table.put_item(data=data4)
returned_item = table.lookup('123', 'ghi')
dict(returned_item).should.equal(data4)
@mock_dynamodb2
def test_conflicting_writes():
table = Table.create('messages', schema=[
HashKey('id'),
RangeKey('range'),
])
item_data = {'id': '123', 'range':'abc', 'data':'678'}
item1 = Item(table, item_data)
item2 = Item(table, item_data)
item1.save()
item1['data'] = '579'
item2['data'] = '912'
item1.save()
item2.save.when.called_with().should.throw(ConditionalCheckFailedException)

View File

@ -10,6 +10,7 @@ try:
from boto.dynamodb2.fields import HashKey from boto.dynamodb2.fields import HashKey
from boto.dynamodb2.table import Table from boto.dynamodb2.table import Table
from boto.dynamodb2.table import Item from boto.dynamodb2.table import Item
from boto.dynamodb2.exceptions import ConditionalCheckFailedException
except ImportError: except ImportError:
pass pass
@ -437,3 +438,50 @@ def test_update_item_set():
'foo': 'bar', 'foo': 'bar',
'blah': 'baz', 'blah': 'baz',
}) })
@mock_dynamodb2
def test_failed_overwrite():
from decimal import Decimal
table = Table.create('messages', schema=[
HashKey('id'),
], throughput={
'read': 7,
'write': 3,
})
data1 = {'id': '123', 'data':'678'}
table.put_item(data=data1)
data2 = {'id': '123', 'data':'345'}
table.put_item(data=data2, overwrite = True)
data3 = {'id': '123', 'data':'812'}
table.put_item.when.called_with(data=data3).should.throw(ConditionalCheckFailedException)
returned_item = table.lookup('123')
dict(returned_item).should.equal(data2)
data4 = {'id': '124', 'data':812}
table.put_item(data=data4)
returned_item = table.lookup('124')
dict(returned_item).should.equal(data4)
@mock_dynamodb2
def test_conflicting_writes():
table = Table.create('messages', schema=[
HashKey('id'),
])
item_data = {'id': '123', 'data':'678'}
item1 = Item(table, item_data)
item2 = Item(table, item_data)
item1.save()
item1['data'] = '579'
item2['data'] = '912'
item1.save()
item2.save.when.called_with().should.throw(ConditionalCheckFailedException)