|
3 | 3 | from gcloud.datastore import datastore_v1_pb2 as datastore_pb
|
4 | 4 | from gcloud.datastore import helpers
|
5 | 5 | from gcloud.datastore.entity import Entity
|
| 6 | +from gcloud.datastore.key import Key |
6 | 7 |
|
7 | 8 |
|
8 | 9 | # TODO: Figure out how to properly handle namespaces.
|
@@ -132,6 +133,72 @@ def filter(self, expression, value):
|
132 | 133 | setattr(property_filter.value, attr_name, pb_value)
|
133 | 134 | return clone
|
134 | 135 |
|
| 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 | + |
135 | 202 | def kind(self, *kinds):
|
136 | 203 | """Get or set the Kind of the Query.
|
137 | 204 |
|
|
0 commit comments