def test_conditional_set_action(self): action = self.attribute.set(Path('bar') | 'baz') placeholder_names, expression_attribute_values = {}, {} expression = action.serialize(placeholder_names, expression_attribute_values) assert expression == "#0 = if_not_exists (#1, :0)" assert placeholder_names == {'foo': '#0', 'bar': '#1'} assert expression_attribute_values == {':0': {'S': 'baz'}}
def test_add_action(self): action = Path('foo').add(0) placeholder_names, expression_attribute_values = {}, {} expression = action.serialize(placeholder_names, expression_attribute_values) assert expression == "#0 :0" assert placeholder_names == {'foo': '#0'} assert expression_attribute_values == {':0': {'N': '0'}}
def test_prepend_action(self): action = self.attribute.set(Path('bar').prepend(['baz'])) placeholder_names, expression_attribute_values = {}, {} expression = action.serialize(placeholder_names, expression_attribute_values) assert expression == "#0 = list_append (:0, #1)" assert placeholder_names == {'foo': '#0', 'bar': '#1'} assert expression_attribute_values == {':0': {'L': [{'S': 'baz'}]}}
def test_decrement_action_value(self): action = self.attribute.set(Value(0) - Path('bar')) placeholder_names, expression_attribute_values = {}, {} expression = action.serialize(placeholder_names, expression_attribute_values) assert expression == "#0 = :0 - #1" assert placeholder_names == {'foo': '#0', 'bar': '#1'} assert expression_attribute_values == {':0': {'N': '0'}}
def test_increment_action(self): action = self.attribute.set(Path('bar') + 0) placeholder_names, expression_attribute_values = {}, {} expression = action.serialize(placeholder_names, expression_attribute_values) assert expression == "#0 = #1 + :0" assert placeholder_names == {'foo': '#0', 'bar': '#1'} assert expression_attribute_values == {':0': {'N': '0'}}
def test_sizes(self): condition = size(self.attribute) == size(Path('bar')) placeholder_names, expression_attribute_values = {}, {} expression = condition.serialize(placeholder_names, expression_attribute_values) assert expression == "size (#0) = size (#1)" assert placeholder_names == {'foo': '#0', 'bar': '#1'} assert expression_attribute_values == {}
def test_contains_attribute(self): condition = ListAttribute(attr_name='foo').contains(Path('bar')) placeholder_names, expression_attribute_values = {}, {} expression = condition.serialize(placeholder_names, expression_attribute_values) assert expression == "contains (#0, #1)" assert placeholder_names == {'foo': '#0', 'bar': '#1'} assert expression_attribute_values == {}
async def test_query(self): """ TableConnection.query """ conn = TableConnection(self.test_table_name) with patch(PATCH_METHOD) as req: req.return_value = DESCRIBE_TABLE_DATA await conn.describe_table() with patch(PATCH_METHOD) as req: req.return_value = {} await conn.query( "FooForum", Path('Subject').startswith('thread') ) params = { 'ReturnConsumedCapacity': 'TOTAL', 'KeyConditionExpression': '(#0 = :0 AND begins_with (#1, :1))', 'ExpressionAttributeNames': { '#0': 'ForumName', '#1': 'Subject' }, 'ExpressionAttributeValues': { ':0': { 'S': 'FooForum' }, ':1': { 'S': 'thread' } }, 'TableName': self.test_table_name } self.assertEqual(req.call_args[0][1], params) with patch(PATCH_METHOD) as req: req.return_value = {} await conn.query( "FooForum", key_conditions={'Subject': {'ComparisonOperator': 'BEGINS_WITH', 'AttributeValueList': ['thread']}} ) params = { 'ReturnConsumedCapacity': 'TOTAL', 'KeyConditionExpression': '(#0 = :0 AND begins_with (#1, :1))', 'ExpressionAttributeNames': { '#0': 'ForumName', '#1': 'Subject' }, 'ExpressionAttributeValues': { ':0': { 'S': 'FooForum' }, ':1': { 'S': 'thread' } }, 'TableName': self.test_table_name } self.assertEqual(req.call_args[0][1], params)
def __getitem__(self, item): if self._is_attribute_container(): return self.attribute_values[item] # If this instance is being used as an Attribute, treat item access like the map dereference operator. # This provides equivalence between DynamoDB's nested attribute access for map elements (MyMap.nestedField) # and Python's item access for dictionaries (MyMap['nestedField']). if self.is_raw(): return Path(self.attr_path + [str(item)]) elif item in self._attributes: return getattr(self, item) else: raise AttributeError("'{0}' has no attribute '{1}'".format( self.__class__.__name__, item))
def add(self, value): return Path(self).add(value)
def remove(self): return Path(self).remove()
def set(self, value): return Path(self).set(value)
def prepend(self, other): return Path(self).prepend(other)
def between(self, lower, upper): return Path(self).between(lower, upper)
async def test_put_item(self): """ TableConnection.put_item """ conn = TableConnection(self.test_table_name) with patch(PATCH_METHOD) as req: req.return_value = DESCRIBE_TABLE_DATA await conn.describe_table() with patch(PATCH_METHOD) as req: req.return_value = {} await conn.put_item( 'foo-key', range_key='foo-range-key', attributes={'ForumName': 'foo-value'} ) params = { 'ReturnConsumedCapacity': 'TOTAL', 'TableName': self.test_table_name, 'Item': {'ForumName': {'S': 'foo-value'}, 'Subject': {'S': 'foo-range-key'}} } self.assertEqual(req.call_args[0][1], params) with patch(PATCH_METHOD) as req: req.return_value = {} await conn.put_item( 'foo-key', range_key='foo-range-key', attributes={'ForumName': 'foo-value'} ) params = { 'ReturnConsumedCapacity': 'TOTAL', 'Item': { 'ForumName': { 'S': 'foo-value' }, 'Subject': { 'S': 'foo-range-key' } }, 'TableName': self.test_table_name } self.assertEqual(req.call_args[0][1], params) with patch(PATCH_METHOD) as req: req.return_value = HttpOK(), {} await conn.put_item( 'foo-key', range_key='foo-range-key', attributes={'ForumName': 'foo-value'}, condition=Path('ForumName').does_not_exist() ) params = { 'ReturnConsumedCapacity': 'TOTAL', 'Item': { 'ForumName': { 'S': 'foo-value' }, 'Subject': { 'S': 'foo-range-key' } }, 'TableName': self.test_table_name, 'ConditionExpression': 'attribute_not_exists (#0)', 'ExpressionAttributeNames': { '#0': 'ForumName' } } self.assertEqual(req.call_args[0][1], params) with patch(PATCH_METHOD) as req: req.return_value = HttpOK(), {} await conn.put_item( 'foo-key', range_key='foo-range-key', attributes={'ForumName': 'foo-value'}, conditional_operator='and', expected={ 'ForumName': { 'Exists': False } } ) params = { 'ReturnConsumedCapacity': 'TOTAL', 'Item': { 'ForumName': { 'S': 'foo-value' }, 'Subject': { 'S': 'foo-range-key' } }, 'TableName': self.test_table_name, 'ConditionExpression': 'attribute_not_exists (#0)', 'ExpressionAttributeNames': { '#0': 'ForumName' } } self.assertEqual(req.call_args[0][1], params)
def __eq__(self, other): if other is None or isinstance( other, Attribute): # handle object identity comparison return self is other return Path(self).__eq__(other)
def __radd__(self, other): return Path(self).__radd__(other)
def test_create_project_expression_with_attribute_names(self): attributes_to_get = [Path(['foo.bar'])[0]] placeholders = {} projection_expression = create_projection_expression(attributes_to_get, placeholders) assert projection_expression == "#0[0]" assert placeholders == {'foo.bar': '#0'}
def startswith(self, prefix): return Path(self).startswith(prefix)
def is_type(self): # What makes sense here? Are we using this to check if deserialization will be successful? return Path(self).is_type(ATTR_TYPE_MAP[self.attr_type])
def does_not_exist(self): return Path(self).does_not_exist()
def exists(self): return Path(self).exists()
def is_in(self, *values): return Path(self).is_in(*values)
def delete(self, value): return Path(self).delete(value)
def __rsub__(self, other): return Path(self).__rsub__(other)
def __or__(self, other): return Path(self).__or__(other)
async def test_update_item(self): """ TableConnection.update_item """ conn = TableConnection(self.test_table_name) with patch(PATCH_METHOD) as req: req.return_value = DESCRIBE_TABLE_DATA await conn.describe_table() attr_updates = { 'Subject': { 'Value': 'foo-subject', 'Action': 'PUT' }, } with patch(PATCH_METHOD) as req: req.return_value = HttpOK(), {} await conn.update_item( 'foo-key', actions=[Path('Subject').set('foo-subject')], range_key='foo-range-key', ) params = { 'Key': { 'ForumName': { 'S': 'foo-key' }, 'Subject': { 'S': 'foo-range-key' } }, 'UpdateExpression': 'SET #0 = :0', 'ExpressionAttributeNames': { '#0': 'Subject' }, 'ExpressionAttributeValues': { ':0': { 'S': 'foo-subject' } }, 'ReturnConsumedCapacity': 'TOTAL', 'TableName': 'ci-table' } self.assertEqual(req.call_args[0][1], params) with patch(PATCH_METHOD) as req: req.return_value = HttpOK(), {} await conn.update_item( 'foo-key', attribute_updates=attr_updates, range_key='foo-range-key', ) params = { 'Key': { 'ForumName': { 'S': 'foo-key' }, 'Subject': { 'S': 'foo-range-key' } }, 'UpdateExpression': 'SET #0 = :0', 'ExpressionAttributeNames': { '#0': 'Subject' }, 'ExpressionAttributeValues': { ':0': { 'S': 'foo-subject' } }, 'ReturnConsumedCapacity': 'TOTAL', 'TableName': 'ci-table' } self.assertEqual(req.call_args[0][1], params)
def append(self, other): return Path(self).append(other)
def contains(self, item): return Path(self).contains(item)
def __getitem__(self, idx): return Path(self).__getitem__(idx)