Repository: getmoto/moto

Issue:
dynamodb2 delete_item silently ignores ConditionExpression
This is with moto v1.3.13

The following script demonstrates that `update_item()` respects `ConditionExpression` while `delete_item` ignores it.

```python
import boto3
from moto import mock_dynamodb2

table_name = 'my-table'
table_schema = {
    'KeySchema': [{
        'AttributeName': 'partitionKey',
        'KeyType': 'HASH'
    }, {
        'AttributeName': 'sortKey',
        'KeyType': 'RANGE'
    }],
    'AttributeDefinitions': [{
        'AttributeName': 'partitionKey',
        'AttributeType': 'S'
    }, {
        'AttributeName': 'sortKey',
        'AttributeType': 'S'
    }],
}
item = {
    'partitionKey': 'pk',
    'sortKey': 'sk',
    'attr': 'org-value',
}

update_expression = 'SET attr = :nv'
condition_expression = 'attr = :ov'
expression_attribute_values = {
    ':wv': 'wrong-value',
    ':nv': 'new-values',
}

with mock_dynamodb2():
    dynamodb = boto3.resource('dynamodb')
    dynamodb.create_table(TableName=table_name, **table_schema)
    table = dynamodb.Table(table_name)

    # insert one item into the table
    table.put_item(Item=item)
    print('the item in the table', table.get_item(Key=item).get('Item'))

    # this update blocked by the conditional expression - works!
    try:
        table.update_item(Key=item, UpdateExpression=update_expression, ConditionExpression=condition_expression, ExpressionAttributeValues=expression_attribute_values)
    except Exception as err:
        pass # catches ConditionalCheckFailedException
    print('the item has not changed because the update was blocked -> ', table.get_item(Key=item).get('Item'))

    # this delete *not* blocked by the conditional expression - broken
    table.delete_item(Key=item, ConditionExpression=condition_expression, ExpressionAttributeValues=expression_attribute_values)
    print('the item has been deleted because the delete was *not* blocked -> ', table.get_item(Key=item).get('Item'))
```

Running this script gives the following output:

```
the item in the table ->  {'partitionKey': 'pk', 'sortKey': 'sk', 'attr': 'org-value'}
the item has not changed because the update was blocked ->  {'partitionKey': 'pk', 'sortKey': 'sk', 'attr': 'org-value'}
the item has been deleted because the delete was *not* blocked ->  None
```

Finally, the [source of `delete_item()`](https://github.com/spulec/moto/blob/cf95313b39a601ba0325a582b9428008e73594e0/moto/dynamodb2/responses.py#L543-L562) doesn't access `ConditionExpression` at all whereas in `update_item()` it is extracted and passed to the `dynamodb_backend`.


Discussion:
Thanks; setting this as a feature request to implement this behavior.

Resolving patch:
diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py
--- a/moto/dynamodb2/models.py
+++ b/moto/dynamodb2/models.py
@@ -1092,12 +1092,23 @@ def update_item(self, table_name, key, update_expression, attribute_updates, exp
             item.update_with_attribute_updates(attribute_updates)
         return item
 
-    def delete_item(self, table_name, keys):
+    def delete_item(self, table_name, key, expression_attribute_names=None, expression_attribute_values=None,
+                    condition_expression=None):
         table = self.get_table(table_name)
         if not table:
             return None
-        hash_key, range_key = self.get_keys_value(table, keys)
-        return table.delete_item(hash_key, range_key)
+
+        hash_value, range_value = self.get_keys_value(table, key)
+        item = table.get_item(hash_value, range_value)
+
+        condition_op = get_filter_expression(
+            condition_expression,
+            expression_attribute_names,
+            expression_attribute_values)
+        if not condition_op.expr(item):
+            raise ValueError('The conditional request failed')
+
+        return table.delete_item(hash_value, range_value)
 
     def update_ttl(self, table_name, ttl_spec):
         table = self.tables.get(table_name)
diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py
--- a/moto/dynamodb2/responses.py
+++ b/moto/dynamodb2/responses.py
@@ -579,7 +579,7 @@ def scan(self):
 
     def delete_item(self):
         name = self.body['TableName']
-        keys = self.body['Key']
+        key = self.body['Key']
         return_values = self.body.get('ReturnValues', 'NONE')
         if return_values not in ('ALL_OLD', 'NONE'):
             er = 'com.amazonaws.dynamodb.v20111205#ValidationException'
@@ -590,7 +590,21 @@ def delete_item(self):
             er = 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException'
             return self.error(er, 'A condition specified in the operation could not be evaluated.')
 
-        item = self.dynamodb_backend.delete_item(name, keys)
+        # Attempt to parse simple ConditionExpressions into an Expected
+        # expression
+        condition_expression = self.body.get('ConditionExpression')
+        expression_attribute_names = self.body.get('ExpressionAttributeNames', {})
+        expression_attribute_values = self.body.get('ExpressionAttributeValues', {})
+
+        try:
+            item = self.dynamodb_backend.delete_item(
+                name, key, expression_attribute_names, expression_attribute_values,
+                condition_expression
+            )
+        except ValueError:
+            er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException'
+            return self.error(er, 'A condition specified in the operation could not be evaluated.')
+
         if item and return_values == 'ALL_OLD':
             item_dict = item.to_json()
         else:


Test patch:
diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py
--- a/tests/test_dynamodb2/test_dynamodb.py
+++ b/tests/test_dynamodb2/test_dynamodb.py
@@ -1995,6 +1995,23 @@ def test_condition_expressions():
             },
         )
 
+    with assert_raises(client.exceptions.ConditionalCheckFailedException):
+        client.delete_item(
+            TableName = 'test1',
+            Key = {
+                'client': {'S': 'client1'},
+                'app': {'S': 'app1'},
+            },
+            ConditionExpression = 'attribute_not_exists(#existing)',
+            ExpressionAttributeValues = {
+                ':match': {'S': 'match'}
+            },
+            ExpressionAttributeNames = {
+                '#existing': 'existing',
+                '#match': 'match',
+            },
+        )
+
 
 @mock_dynamodb2
 def test_condition_expression__attr_doesnt_exist():