Repository: googleapis/google-cloud-python

Issue:
datastore: Query does not allow inequality filter on __key__ property.
Datastore query raises a ValueError for inequality(non-equality to be correct) operators on `__key__` property. 
https://github.com/GoogleCloudPlatform/gcloud-python/blob/3e86aaed3c7e192a17d9b790f90a41cc2b7baa6a/gcloud/datastore/query.py#L223

According to Google Cloud Datastore documentation inquality operators on `__key__` property are supported, and should not raise exception.
https://cloud.google.com/datastore/docs/concepts/queries#Datastore_Key_filters

It is a critical part of our data-model.



Discussion:
For those who are facing the same issue; Workaround is to pass filters for `__key__` property at the time of query construction.
Example:
Instead of:

``` python
query = datastore.Query('Kind')
query.add_filter('__key__', '>=', datastore.Key('Kind', '<startname>'))
query.add_filter('__key__', '<', datastore.Key('Kind', '<endname>'))
entities = query.fetch()
```

Use:

``` python
start_key = datastore.Key('Kind', '<startname>')
end_key = datastore.Key('Kind', '<endname>')
queries = datastore.Query('Kind', filters=[('__key__', '>=', start_key), ('__key__', '<', end_key)])
entities = query.fetch()
```

Thanks @vinays 

I'm not sure why we limit in

``` python
        if property_name == '__key__':
            if not isinstance(value, Key):
                raise ValueError('Invalid key: "%s"' % value)
            if operator != '=':
                raise ValueError('Invalid operator for key: "%s"' % operator)
```

It also points out that the initial filters from the constructor aren't being verified.

---

I really appreciate it and will be making a fix ASAP!


Resolving patch:
diff --git a/gcloud/datastore/query.py b/gcloud/datastore/query.py
--- a/gcloud/datastore/query.py
+++ b/gcloud/datastore/query.py
@@ -90,7 +90,10 @@ def __init__(self,
         self._kind = kind
         self._namespace = namespace
         self._ancestor = ancestor
-        self._filters = list(filters)
+        self._filters = []
+        # Verify filters passed in.
+        for property_name, operator, value in filters:
+            self.add_filter(property_name, operator, value)
         self._projection = _ensure_tuple_or_list('projection', projection)
         self._order = _ensure_tuple_or_list('order', order)
         self._group_by = _ensure_tuple_or_list('group_by', group_by)
@@ -216,11 +219,8 @@ def add_filter(self, property_name, operator, value):
             choices_message = 'Please use one of: =, <, <=, >, >=.'
             raise ValueError(error_message, choices_message)
 
-        if property_name == '__key__':
-            if not isinstance(value, Key):
-                raise ValueError('Invalid key: "%s"' % value)
-            if operator != '=':
-                raise ValueError('Invalid operator for key: "%s"' % operator)
+        if property_name == '__key__' and not isinstance(value, Key):
+            raise ValueError('Invalid key: "%s"' % value)
 
         self._filters.append((property_name, operator, value))
 


Test patch:
diff --git a/gcloud/datastore/test_query.py b/gcloud/datastore/test_query.py
--- a/gcloud/datastore/test_query.py
+++ b/gcloud/datastore/test_query.py
@@ -100,6 +100,13 @@ def test_ctor_bad_group_by(self):
         self.assertRaises(TypeError, self._makeOne, _KIND, _DATASET,
                           group_by=BAD_GROUP_BY)
 
+    def test_ctor_bad_filters(self):
+        _DATASET = 'DATASET'
+        _KIND = 'KIND'
+        FILTERS_CANT_UNPACK = [('one', 'two')]
+        self.assertRaises(ValueError, self._makeOne, _KIND, _DATASET,
+                          filters=FILTERS_CANT_UNPACK)
+
     def test_namespace_setter_w_non_string(self):
         _DATASET = 'DATASET'
         query = self._makeOne(dataset_id=_DATASET)
@@ -224,12 +231,13 @@ def test_add_filter___key__valid_key(self):
         query.add_filter('__key__', '=', key)
         self.assertEqual(query.filters, [('__key__', '=', key)])
 
-    def test_filter___key__invalid_operator(self):
+    def test_filter___key__not_equal_operator(self):
         from gcloud.datastore.key import Key
         _DATASET = 'DATASET'
         key = Key('Foo', dataset_id='DATASET')
         query = self._makeOne(dataset_id=_DATASET)
-        self.assertRaises(ValueError, query.add_filter, '__key__', '<', key)
+        query.add_filter('__key__', '<', key)
+        self.assertEqual(query.filters, [('__key__', '<', key)])
 
     def test_filter___key__invalid_value(self):
         _DATASET = 'DATASET'