Repository: getmoto/moto

Issue:
mock_dynamodb2 query calls fail with a KeyError when either ExpressionAttributeNames or ExpressionAttributeValues is not passed as a parameter
moto==1.0.1
boto3==1.4.5
botocore==1.5.94

I'm attempting to test a query against a dynamodb database using the mock_dynamodb2 decorator. When I attempt to execute the following boto3 query:
```
query_results = self.db_client.query(TableName=self.error_table_name,
                                     Limit=1000,
                                     KeyConditionExpression='error_message=:error_message',
                                     ExpressionAttributeValues={':error_message': {'S': error_message}},
                                     FilterExpression='retried = false',
                                     ExclusiveStartKey=last_evaluated_key)
```

I get a key error because I haven't specified the 'ExpressionAttributeNames' parameter. Here is an example:

```
tests/test_error_resubmitter.py:78: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
reaper/error_resubmitter.py:76: in get_all_paginated_error_results
    ExclusiveStartKey=last_evaluated_key)
/usr/local/lib/python2.7/site-packages/botocore/client.py:310: in _api_call
    return self._make_api_call(operation_name, kwargs)
/usr/local/lib/python2.7/site-packages/botocore/client.py:586: in _make_api_call
    operation_model, request_dict)
/usr/local/lib/python2.7/site-packages/botocore/endpoint.py:141: in make_request
    return self._send_request(request_dict, operation_model)
/usr/local/lib/python2.7/site-packages/botocore/endpoint.py:170: in _send_request
    success_response, exception):
/usr/local/lib/python2.7/site-packages/botocore/endpoint.py:249: in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
/usr/local/lib/python2.7/site-packages/botocore/hooks.py:227: in emit
    return self._emit(event_name, kwargs)
/usr/local/lib/python2.7/site-packages/botocore/hooks.py:210: in _emit
    response = handler(**kwargs)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:183: in __call__
    if self._checker(attempts, response, caught_exception):
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:251: in __call__
    caught_exception)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:269: in _should_retry
    return self._checker(attempt_number, response, caught_exception)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:317: in __call__
    caught_exception)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:223: in __call__
    attempt_number, caught_exception)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <botocore.retryhandler.ExceptionRaiser object at 0x10f616ed0>, attempt_number = 1, caught_exception = KeyError(u'ExpressionAttributeNames',)

    def _check_caught_exception(self, attempt_number, caught_exception):
        # This is implementation specific, but this class is useful by
        # coordinating with the MaxAttemptsDecorator.
        # The MaxAttemptsDecorator has a list of exceptions it should catch
        # and retry, but something needs to come along and actually raise the
        # caught_exception.  That's what this class is being used for.  If
        # the MaxAttemptsDecorator is not interested in retrying the exception
        # then this exception just propogates out past the retry code.
>       raise caught_exception
E       KeyError: u'ExpressionAttributeNames'

/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:359: KeyError
```

This same problem happens when I don't specify the 'ExpressionAttributeValues' parameter. For example, this code:

```
error_message = 'someErrorMessage'
query_results = self.db_client.query(TableName=self.error_table_name,
                                     Limit=1000,
                                     KeyConditionExpression='errror_message=' + error_message)
```

Causes this stack trace:

```
tests/test_error_resubmitter.py:78: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
reaper/error_resubmitter.py:62: in get_all_paginated_error_results
    KeyConditionExpression='errror_message=' + error_message)
/usr/local/lib/python2.7/site-packages/botocore/client.py:310: in _api_call
    return self._make_api_call(operation_name, kwargs)
/usr/local/lib/python2.7/site-packages/botocore/client.py:586: in _make_api_call
    operation_model, request_dict)
/usr/local/lib/python2.7/site-packages/botocore/endpoint.py:141: in make_request
    return self._send_request(request_dict, operation_model)
/usr/local/lib/python2.7/site-packages/botocore/endpoint.py:170: in _send_request
    success_response, exception):
/usr/local/lib/python2.7/site-packages/botocore/endpoint.py:249: in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
/usr/local/lib/python2.7/site-packages/botocore/hooks.py:227: in emit
    return self._emit(event_name, kwargs)
/usr/local/lib/python2.7/site-packages/botocore/hooks.py:210: in _emit
    response = handler(**kwargs)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:183: in __call__
    if self._checker(attempts, response, caught_exception):
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:251: in __call__
    caught_exception)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:269: in _should_retry
    return self._checker(attempt_number, response, caught_exception)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:317: in __call__
    caught_exception)
/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:223: in __call__
    attempt_number, caught_exception)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <botocore.retryhandler.ExceptionRaiser object at 0x10f456e50>, attempt_number = 1, caught_exception = KeyError(u'ExpressionAttributeValues',)

    def _check_caught_exception(self, attempt_number, caught_exception):
        # This is implementation specific, but this class is useful by
        # coordinating with the MaxAttemptsDecorator.
        # The MaxAttemptsDecorator has a list of exceptions it should catch
        # and retry, but something needs to come along and actually raise the
        # caught_exception.  That's what this class is being used for.  If
        # the MaxAttemptsDecorator is not interested in retrying the exception
        # then this exception just propogates out past the retry code.
>       raise caught_exception
E       KeyError: u'ExpressionAttributeValues'

/usr/local/lib/python2.7/site-packages/botocore/retryhandler.py:359: KeyError
```

This issue looks like it's cause by directly accessing the self.body object using indexing. From  moto/moto/dynamodb2/responses.py :
```
def query(self):
    name = self.body['TableName']
    # {u'KeyConditionExpression': u'#n0 = :v0', u'ExpressionAttributeValues': {u':v0': {u'S': u'johndoe'}}, u'ExpressionAttributeNames': {u'#n0': u'username'}}
    key_condition_expression = self.body.get('KeyConditionExpression')
    filter_kwargs = {}
    if key_condition_expression:
        value_alias_map = self.body['ExpressionAttributeValues']
```

Then later on 'ExpressionAttributeNames' is also accessed directly:

```
reverse_attribute_lookup = dict((v, k) for k, v in six.iteritems(self.body['ExpressionAttributeNames']))
```



Discussion:
running into the same issue.  Has anybody found a workaround?  I'm loving this project but may have to resort to using local dynamo and compose
Does this still apply to version `1.1.22`?
sorry for the late reply.  I had moved over to compose but will look at updating moto and testing again with an updated version today.
yes I'm getting that same error with moto==1.1.24

Resolving patch:
diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py
--- a/moto/dynamodb2/responses.py
+++ b/moto/dynamodb2/responses.py
@@ -308,7 +308,7 @@ def query(self):
 
         filter_kwargs = {}
         if key_condition_expression:
-            value_alias_map = self.body['ExpressionAttributeValues']
+            value_alias_map = self.body.get('ExpressionAttributeValues', {})
 
             table = self.dynamodb_backend.get_table(name)
 
@@ -333,7 +333,7 @@ def query(self):
                 index = table.schema
 
             reverse_attribute_lookup = dict((v, k) for k, v in
-                                            six.iteritems(self.body['ExpressionAttributeNames']))
+                                            six.iteritems(self.body.get('ExpressionAttributeNames', {})))
 
             if " AND " in key_condition_expression:
                 expressions = key_condition_expression.split(" AND ", 1)
@@ -372,7 +372,8 @@ def query(self):
                 range_values = []
 
             hash_key_value_alias = hash_key_expression.split("=")[1].strip()
-            hash_key = value_alias_map[hash_key_value_alias]
+            # Temporary fix until we get proper KeyConditionExpression function
+            hash_key = value_alias_map.get(hash_key_value_alias, {'S': hash_key_value_alias})
         else:
             # 'KeyConditions': {u'forum_name': {u'ComparisonOperator': u'EQ', u'AttributeValueList': [{u'S': u'the-key'}]}}
             key_conditions = self.body.get('KeyConditions')


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
@@ -926,3 +926,31 @@ def test_set_ttl():
 
     resp = client.describe_time_to_live(TableName='test1')
     resp['TimeToLiveDescription']['TimeToLiveStatus'].should.equal('DISABLED')
+
+
+# https://github.com/spulec/moto/issues/1043
+@mock_dynamodb2
+def test_query_missing_expr_names():
+    client = boto3.client('dynamodb', region_name='us-east-1')
+
+    # Create the DynamoDB table.
+    client.create_table(
+        TableName='test1',
+        AttributeDefinitions=[{'AttributeName': 'client', 'AttributeType': 'S'}, {'AttributeName': 'app', 'AttributeType': 'S'}],
+        KeySchema=[{'AttributeName': 'client', 'KeyType': 'HASH'}, {'AttributeName': 'app', 'KeyType': 'RANGE'}],
+        ProvisionedThroughput={'ReadCapacityUnits': 123, 'WriteCapacityUnits': 123}
+    )
+    client.put_item(TableName='test1', Item={'client': {'S': 'test1'}, 'app': {'S': 'test1'}})
+    client.put_item(TableName='test1', Item={'client': {'S': 'test2'}, 'app': {'S': 'test2'}})
+
+    resp = client.query(TableName='test1', KeyConditionExpression='client=:client',
+                        ExpressionAttributeValues={':client': {'S': 'test1'}})
+
+    resp['Count'].should.equal(1)
+    resp['Items'][0]['client']['S'].should.equal('test1')
+
+    resp = client.query(TableName='test1', KeyConditionExpression=':name=test2',
+                        ExpressionAttributeNames={':name': 'client'})
+
+    resp['Count'].should.equal(1)
+    resp['Items'][0]['client']['S'].should.equal('test2')