Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #80 - Added support for ancestor queries. #85

Merged
merged 1 commit into from
Apr 22, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions gcloud/datastore/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
from gcloud.datastore import helpers
from gcloud.datastore.entity import Entity
from gcloud.datastore.key import Key


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

def ancestor(self, ancestor):
"""Filter the query based on an ancestor.

This will return a clone of the current :class:`Query`
filtered by the ancestor provided.

For example::

>>> parent_key = Key.from_path('Person', '1')
>>> query = dataset.query('Person')
>>> filtered_query = query.ancestor(parent_key)

If you don't have a :class:`gcloud.datastore.key.Key` but just
know the path, you can provide that as well::

>>> query = dataset.query('Person')
>>> filtered_query = query.ancestor(['Person', '1'])

Each call to ``.ancestor()`` returns a cloned :class:`Query:,
however a query may only have one ancestor at a time.

:type ancestor: :class:`gcloud.datastore.key.Key` or list
:param ancestor: Either a Key or a path of the form
``['Kind', 'id or name', 'Kind', 'id or name', ...]``.

:rtype: :class:`Query`
:returns: A Query filtered by the ancestor provided.
"""

clone = self._clone()

# If an ancestor filter already exists, remove it.
for i, filter in enumerate(clone._pb.filter.composite_filter.filter):
property_filter = filter.property_filter
if property_filter.operator == datastore_pb.PropertyFilter.HAS_ANCESTOR:
del clone._pb.filter.composite_filter.filter[i]

# If we just deleted the last item, make sure to clear out the filter
# property all together.
if not clone._pb.filter.composite_filter.filter:
clone._pb.ClearField('filter')

# If the ancestor is None, just return (we already removed the filter).
if not ancestor:
return clone

# If a list was provided, turn it into a Key.
if isinstance(ancestor, list):
ancestor = Key.from_path(*ancestor)

# If we don't have a Key value by now, something is wrong.
if not isinstance(ancestor, Key):
raise TypeError('Expected list or Key, got %s.' % type(ancestor))

# Get the composite filter and add a new property filter.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

composite_filter = clone._pb.filter.composite_filter
composite_filter.operator = datastore_pb.CompositeFilter.AND

# Filter on __key__ HAS_ANCESTOR == ancestor.
ancestor_filter = composite_filter.filter.add().property_filter
ancestor_filter.property.name = '__key__'
ancestor_filter.operator = datastore_pb.PropertyFilter.HAS_ANCESTOR
ancestor_filter.value.key_value.CopyFrom(ancestor.to_protobuf())

return clone

def kind(self, *kinds):
"""Get or set the Kind of the Query.

Expand Down