8000 Implements group_by, projection and offset on datastore Query. · googleapis/google-cloud-python@8dcf9f4 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 8dcf9f4

Browse files
committed
Implements group_by, projection and offset on datastore Query.
In addition, __eq__ was implemented on datastore.key.Key to allow for easy comparison.
1 parent bcf7a1e commit 8dcf9f4

File tree

4 files changed

+189
-0
lines changed

4 files changed

+189
-0
lines changed

gcloud/datastore/key.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,14 @@ def parent(self):
288288

289289
def __repr__(self):
290290
return '<Key%s>' % self.path()
291+
292+
def __eq__(self, other):
293+
if self is other:
294+
return True
295+
296+
return (self.dataset() == other.dataset() and
297+
self.namespace() == other.namespace() and
298+
self.path() == other.path())
299+
300+
def __ne__(self, other):
301+
return not self.__eq__(other)

gcloud/datastore/query.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def __init__(self, kind=None, dataset=None, namespace=None):
6060
self._namespace = namespace
6161
self._pb = datastore_pb.Query()
6262
self._cursor = None
63+
self._projection = []
64+
self._offset = 0
65+
self._group_by = []
6366

6467
if kind:
6568
self._pb.kind.add().name = kind
@@ -404,3 +407,104 @@ def order(self, *properties):
404407
property_order.direction = property_order.ASCENDING
405408

406409
return clone
410+
411+
def projection(self, projection=None):
412+
"""Adds a projection to the query.
413+
414+
This is a hybrid getter / setter, used as::
415+
416+
>>> query = Query('Person')
417+
>>> query.projection() # Get the projection for this query.
418+
[]
419+
>>> query = query.projection(['name'])
420+
>>> query.projection() # Get the projection for this query.
421+
['name']
422+
423+
:type projection: sequence of strings
424+
:param projection: Each value is a string giving the name of a
425+
property to be included in the projection query.
426+
427+
:rtype: :class:`Query` or `list` of strings.
428+
:returns: If no arguments, returns the current projection.
429+
If a projection is provided, returns a clone of the
430+
:class:`Query` with that projection set.
431+
"""
432+
if projection is None:
433+
return self._projection
434+
435+
clone = self._clone()
436+
clone._projection = projection
437+
438+
# Reset projection values to empty.
439+
clone._pb.projection._values = []
440+
441+
# Add each name to list of projections.
442+
for projection_name in projection:
443+
clone._pb.projection.add().property.name = projection_name
444+
return clone
445+
446+
def offset(self, offset=None):
447+
"""Adds offset to the query to allow pagination.
448+
449+
NOTE: Paging with cursors should be preferred to using an offset.
450+
451+
This is a hybrid getter / setter, used as::
452+
453+
>>> query = Query('Person')
454+
>>> query.offset() # Get the offset for this query.
455+
0
456+
>>> query = query.offset(10)
457+
>>> query.offset() # Get the offset for this query.
458+
10
459+
460+
:type offset: non-negative integer.
461+
:param offset: Value representing where to start a query for
462+
a given kind.
463+
464+
:rtype: :class:`Query` or `int`.
465+
:returns: If no arguments, returns the current offset.
466+
If an offset is provided, returns a clone of the
467+
:class:`Query` with that offset set.
468+
"""
469+
if offset is None:
470+
return self._offset
471+
472+
clone = self._clone()
473+
clone._offset = offset
474+
clone._pb.offset = offset
475+
return clone
476+
477+
def group_by(self, group_by=None):
478+
"""Adds a group_by to the query.
479+
480+
This is a hybrid getter / setter, used as::
481+
482+
>>> query = Query('Person')
483+
>>> query.group_by() # Get the group_by for this query.
484+
[]
485+
>>> query = query.group_by(['name'])
486+
>>> query.group_by() # Get the group_by for this query.
487+
['name']
488+
489+
:type group_by: sequence of strings
490+
:param group_by: Each value is a string giving the name of a
491+
property to use to group results together.
492+
493+
:rtype: :class:`Query` or `list` of strings.
494+
:returns: If no arguments, returns the current group_by.
495+
If a list of group by properties is provided, returns a clone
496+
of the :class:`Query` with that list of values set.
497+
"""
498+
if group_by is None:
499+
return self._group_by
500+
501+
clone = self._clone()
502+
clone._group_by = group_by
503+
504+
# Reset group_by values to empty.
505+
clone._pb.group_by._values = []
506+
507+
# Add each name to list of group_bys.
508+
for group_by_name in group_by:
509+
clone._pb.group_by.add().name = group_by_name
510+
return clone

gcloud/datastore/test_key.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,13 @@ def test_parent_explicit_top_level(self):
407407
def test_parent_explicit_nested(self):
408408
key = self._getTargetClass().from_path('abc', 'def', 'ghi', 123)
409409
self.assertEqual(key.parent().path(), [{'kind': 'abc', 'name': 'def'}])
410+
411+
def test_key___eq__(self):
412+
key1 = self._getTargetClass().from_path('abc', 'def')
413+
key2 = self._getTargetClass().from_path('abc', 'def')
414+
self.assertFalse(key1 is key2)
415+
self.assertEqual(key1, key2)
416+
417+
self.assertEqual(key1, key1)
418+
key3 = self._getTargetClass().from_path('abc', 'ghi')
419+
self.assertNotEqual(key1, key3)

gcloud/datas F438 tore/test_query.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,70 @@ def test_order_multiple(self):
415415
self.assertEqual(prop_pb.property.name, 'bar')
416416
self.assertEqual(prop_pb.direction, prop_pb.DESCENDING)
417417

418+
def test_projection_empty(self):
419+
_KIND = 'KIND'
420+
before = self._makeOne(_KIND)
421+
after = before.projection([])
422+
self.assertFalse(after is before)
423+
self.assertTrue(isinstance(after, self._getTargetClass()))
424+
self.assertEqual(before.to_protobuf(), after.to_protobuf())
425+
426+
def test_projection_non_empty(self):
427+
_KIND = 'KIND'
428+
before = self._makeOne(_KIND)
429+
after = before.projection(['field1', 'field2'])
430+
projection_pb = list(after.to_protobuf().projection)
431+
self.assertEqual(len(projection_pb), 2)
432+
prop_pb1 = projection_pb[0]
433+
self.assertEqual(prop_pb1.property.name, 'field1')
434+
prop_pb2 = projection_pb[1]
435+
self.assertEqual(prop_pb2.property.name, 'field2')
436+
437+
def test_get_projection_non_empty(self):
438+
_KIND = 'KIND'
439+
_PROJECTION = ['field1', 'field2']
440+
after = self._makeOne(_KIND).projection(_PROJECTION)
441+
self.assertEqual(after.projection(), _PROJECTION)
442+
443+
def test_set_offset(self):
444+
_KIND = 'KIND'
445+
_OFFSET = 42
446+
before = self._makeOne(_KIND)
447+
after = before.offset(_OFFSET)
448+
offset_pb = after.to_protobuf().offset
449+
self.assertEqual(offset_pb, _OFFSET)
450+
451+
def test_get_offset(self):
452+
_KIND = 'KIND'
453+
_OFFSET = 10
454+
after = self._makeOne(_KIND).offset(_OFFSET)
455+
self.assertEqual(after.offset(), _OFFSET)
456+
457+
def test_group_by_empty(self):
458+
_KIND = 'KIND'
459+
before = self._makeOne(_KIND)
460+
after = before.group_by([])
461+
self.assertFalse(after is before)
462+
self.assertTrue(isinstance(after, self._getTargetClass()))
463+
self.assertEqual(before.to_protobuf(), after.to_protobuf())
464+
465+
def test_group_by_non_empty(self):
466+
_KIND = 'KIND'
467+
before = self._makeOne(_KIND)
468+
after = before.group_by(['field1', 'field2'])
469+
group_by_pb = list(after.to_protobuf().group_by)
470+
self.assertEqual(len(group_by_pb), 2)
471+
prop_pb1 = group_by_pb[0]
472+
self.assertEqual(prop_pb1.name, 'field1')
473+
prop_pb2 = group_by_pb[1]
474+
self.assertEqual(prop_pb2.name, 'field2')
475+
476+
def test_get_group_by_non_empty(self):
477+
_KIND = 'KIND'
478+
_GROUP_BY = ['field1', 'field2']
479+
after = self._makeOne(_KIND).group_by(_GROUP_BY)
480+
self.assertEqual(after.group_by(), _GROUP_BY)
481+
418482

419483
class _Dataset(object):
420484

0 commit comments

Comments
 (0)
0