Skip to content

Commit 1431fa2

Browse files
committed
Fix googleapis#80 - Added support for ancestor queries.
1 parent 4b56a23 commit 1431fa2

File tree

1 file changed

+67
-0
lines changed

1 file changed

+67
-0
lines changed

gcloud/datastore/query.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
44
from gcloud.datastore import helpers
55
from gcloud.datastore.entity import Entity
6+
from gcloud.datastore.key import Key
67

78

89
# TODO: Figure out how to properly handle namespaces.
@@ -132,6 +133,72 @@ def filter(self, expression, value):
132133
setattr(property_filter.value, attr_name, pb_value)
133134
return clone
134135

136+
def ancestor(self, ancestor):
137+
"""Filter the query based on an ancestor.
138+
139+
This will return a clone of the current :class:`Query`
140+
filtered by the ancestor provided.
141+
142+
For example::
143+
144+
>>> parent_key = Key.from_path('Person', '1')
145+
>>> query = dataset.query('Person')
146+
>>> filtered_query = query.ancestor(parent_key)
147+
148+
If you don't have a :class:`gcloud.datastore.key.Key` but just
149+
know the path, you can provide that as well::
150+
151+
>>> query = dataset.query('Person')
152+
>>> filtered_query = query.ancestor(['Person', '1'])
153+
154+
Each call to ``.ancestor()`` returns a cloned :class:`Query:,
155+
however a query may only have one ancestor at a time.
156+
157+
:type ancestor: :class:`gcloud.datastore.key.Key` or list
158+
:param ancestor: Either a Key or a path of the form
159+
``['Kind', 'id or name', 'Kind', 'id or name', ...]``.
160+
161+
:rtype: :class:`Query`
162+
:returns: A Query filtered by the ancestor provided.
163+
"""
164+
165+
clone = self._clone()
166+
167+
# If an ancestor filter already exists, remove it.
168+
for i, filter in enumerate(clone._pb.filter.composite_filter.filter):
169+
property_filter = filter.property_filter
170+
if property_filter.operator == datastore_pb.PropertyFilter.HAS_ANCESTOR:
171+
del clone._pb.filter.composite_filter.filter[i]
172+
173+
# If we just deleted the last item, make sure to clear out the filter
174+
# property all together.
175+
if len(clone._pb.filter.composite_filter.filter) == 0:
176+
clone._pb.ClearField('filter')
177+
178+
# If the ancestor is None, just return (we already removed the filter).
179+
if ancestor is None:
180+
return clone
181+
182+
# If a list was provided, turn it into a Key.
183+
if isinstance(ancestor, list):
184+
ancestor = Key.from_path(*ancestor)
185+
186+
# If we don't have a Key value by now, something is wrong.
187+
if not isinstance(ancestor, Key):
188+
raise TypeError('Expected list or Key, got %s.' % type(ancestor))
189+
190+
# Get the composite filter and add a new property filter.
191+
composite_filter = clone._pb.filter.composite_filter
192+
composite_filter.operator = datastore_pb.CompositeFilter.AND
193+
194+
# Filter on __key__ HAS_ANCESTOR == ancestor.
195+
ancestor_filter = composite_filter.filter.add().property_filter
196+
ancestor_filter.property.name = '__key__'
197+
ancestor_filter.operator = datastore_pb.PropertyFilter.HAS_ANCESTOR
198+
ancestor_filter.value.key_value.CopyFrom(ancestor.to_protobuf())
199+
200+
return clone
201+
135202
def kind(self, *kinds):
136203
"""Get or set the Kind of the Query.
137204

0 commit comments

Comments
 (0)