Skip to content

Commit 4dd4415

Browse files
committed
Merge pull request #897 from dhermes/fix-701-2nd-attempt
Implementing get_multi in datastore.
2 parents 6cf0852 + d345186 commit 4dd4415

File tree

8 files changed

+190
-45
lines changed

8 files changed

+190
-45
lines changed

gcloud/datastore/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from gcloud.datastore.api import allocate_ids
5757
from gcloud.datastore.api import delete
5858
from gcloud.datastore.api import get
59+
from gcloud.datastore.api import get_multi
5960
from gcloud.datastore.api import put
6061
from gcloud.datastore.batch import Batch
6162
from gcloud.datastore.connection import SCOPE

gcloud/datastore/api.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ def _extended_lookup(connection, dataset_id, key_pbs,
166166
return results
167167

168168

169-
def get(keys, missing=None, deferred=None, connection=None, dataset_id=None):
169+
def get_multi(keys, missing=None, deferred=None,
170+
connection=None, dataset_id=None):
170171
"""Retrieves entities, along with their attributes.
171172
172173
:type keys: list of :class:`gcloud.datastore.key.Key`
@@ -234,6 +235,45 @@ def get(keys, missing=None, deferred=None, connection=None, dataset_id=None):
234235
return entities
235236

236237

238+
def get(key, missing=None, deferred=None, connection=None, dataset_id=None):
239+
"""Retrieves entity from a single key (if it exists).
240+
241+
.. note::
242+
243+
This is just a thin wrapper over :func:`gcloud.datastore.get_multi`.
244+
The backend API does not make a distinction between a single key or
245+
multiple keys in a lookup request.
246+
247+
:type key: :class:`gcloud.datastore.key.Key`
248+
:param key: The key to be retrieved from the datastore.
249+
250+
:type missing: an empty list or None.
251+
:param missing: If a list is passed, the key-only entities returned
252+
by the backend as "missing" will be copied into it.
253+
Use only as a keyword param.
254+
255+
:type deferred: an empty list or None.
256+
:param deferred: If a list is passed, the keys returned
257+
by the backend as "deferred" will be copied into it.
258+
Use only as a keyword param.
259+
260+
:type connection: :class:`gcloud.datastore.connection.Connection`
261+
:param connection: Optional. The connection used to connect to datastore.
262+
If not passed, inferred from the environment.
263+
264+
:type dataset_id: :class:`gcloud.datastore.connection.Connection`
265+
:param dataset_id: Optional. The dataset ID used to connect to datastore.
266+
If not passed, inferred from the environment.
267+
268+
:rtype: :class:`gcloud.datastore.entity.Entity` or ``NoneType``
269+
:returns: The requested entity if it exists.
270+
"""
271+
entities = get_multi([key], missing=missing, deferred=deferred,
272+
connection=connection, dataset_id=dataset_id)
273+
if entities:
274+
return entities[0]
275+
276+
237277
def put(entities, connection=None, dataset_id=None):
238278
"""Save the entities in the Cloud Datastore.
239279

gcloud/datastore/connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def lookup(self, dataset_id, key_pbs,
153153
154154
>>> from gcloud import datastore
155155
>>> key = datastore.Key('MyKind', 1234, dataset_id='dataset-id')
156-
>>> datastore.get([key])
156+
>>> datastore.get(key)
157157
[<Entity object>]
158158
159159
Using the ``connection`` class directly:

gcloud/datastore/dataset.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from gcloud.datastore.api import delete
1717
from gcloud.datastore.api import get
18+
from gcloud.datastore.api import get_multi
1819
from gcloud.datastore.api import put
1920
from gcloud.datastore.batch import Batch
2021
from gcloud.datastore.key import Key
@@ -38,14 +39,23 @@ def __init__(self, dataset_id, connection=None):
3839
self.dataset_id = dataset_id
3940
self.connection = connection
4041

41-
def get(self, keys, missing=None, deferred=None):
42+
def get(self, key, missing=None, deferred=None):
4243
"""Proxy to :func:`gcloud.datastore.api.get`.
4344
4445
Passes our ``dataset_id``.
4546
"""
46-
return get(keys, missing=missing, deferred=deferred,
47+
return get(key, missing=missing, deferred=deferred,
4748
connection=self.connection, dataset_id=self.dataset_id)
4849

50+
def get_multi(self, keys, missing=None, deferred=None):
51+
"""Proxy to :func:`gcloud.datastore.api.get_multi`.
52+
53+
Passes our ``dataset_id``.
54+
"""
55+
return get_multi(keys, missing=missing, deferred=deferred,
56+
connection=self.connection,
57+
dataset_id=self.dataset_id)
58+
4959
def put(self, entities):
5060
"""Proxy to :func:`gcloud.datastore.api.put`.
5161

gcloud/datastore/demo/demo.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@
3333
datastore.put([toy])
3434

3535
# If we look it up by its key, we should find it...
36-
print(datastore.get([toy.key]))
36+
print(datastore.get(toy.key))
3737

3838
# And we should be able to delete it...
3939
datastore.delete([toy.key])
4040

4141
# Since we deleted it, if we do another lookup it shouldn't be there again:
42-
print(datastore.get([toy.key]))
42+
print(datastore.get(toy.key))
4343

4444
# Now let's try a more advanced query.
4545
# First, let's create some entities.
@@ -104,7 +104,7 @@
104104
xact.rollback()
105105

106106
# Let's check if the entity was actually created:
107-
created = datastore.get([key])
107+
created = datastore.get(key)
108108
print('yes' if created else 'no')
109109

110110
# Remember, a key won't be complete until the transaction is commited.

gcloud/datastore/test_api.py

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@
1515
import unittest2
1616

1717

18+
def _make_entity_pb(dataset_id, kind, integer_id, name=None, str_val=None):
19+
from gcloud.datastore import _datastore_v1_pb2 as datastore_pb
20+
21+
entity_pb = datastore_pb.Entity()
22+
entity_pb.key.partition_id.dataset_id = dataset_id
23+
path_element = entity_pb.key.path_element.add()
24+
path_element.kind = kind
25+
path_element.id = integer_id
26+
if name is not None and str_val is not None:
27+
prop = entity_pb.property.add()
28+
prop.name = name
29+
prop.value.string_value = str_val
30+
31+
return entity_pb
32+
33+
1834
class Test__require_dataset_id(unittest2.TestCase):
1935

2036
_MARKER = object()
@@ -158,7 +174,7 @@ def test_implicit_set_passed_explicitly(self):
158174
self.assertTrue(self._callFUT(CONNECTION) is CONNECTION)
159175

160176

161-
class Test_get_function(unittest2.TestCase):
177+
class Test_get_multi_function(unittest2.TestCase):
162178

163179
def setUp(self):
164180
from gcloud.datastore._testing import _setup_defaults
@@ -170,25 +186,9 @@ def tearDown(self):
170186

171187
def _callFUT(self, keys, missing=None, deferred=None,
172188
connection=None, dataset_id=None):
173-
from gcloud.datastore.api import get
174-
return get(keys, missing=missing, deferred=deferred,
175-
connection=connection, dataset_id=dataset_id)
176-
177-
def _make_entity_pb(self, dataset_id, kind, integer_id,
178-
name=None, str_val=None):
179-
from gcloud.datastore import _datastore_v1_pb2 as datastore_pb
180-
181-
entity_pb = datastore_pb.Entity()
182-
entity_pb.key.partition_id.dataset_id = dataset_id
183-
path_element = entity_pb.key.path_element.add()
184-
path_element.kind = kind
185-
path_element.id = integer_id
186-
if name is not None and str_val is not None:
187-
prop = entity_pb.property.add()
188-
prop.name = name
189-
prop.value.string_value = str_val
190-
191-
return entity_pb
189+
from gcloud.datastore.api import get_multi
190+
return get_multi(keys, missing=missing, deferred=deferred,
191+
connection=connection, dataset_id=dataset_id)
192192

193193
def test_wo_connection(self):
194194
from gcloud.datastore.key import Key
@@ -398,8 +398,7 @@ def test_hit(self):
398398
PATH = [{'kind': KIND, 'id': ID}]
399399

400400
# Make a found entity pb to be returned from mock backend.
401-
entity_pb = self._make_entity_pb(DATASET_ID, KIND, ID,
402-
'foo', 'Foo')
401+
entity_pb = _make_entity_pb(DATASET_ID, KIND, ID, 'foo', 'Foo')
403402

404403
# Make a connection to return the entity pb.
405404
connection = _Connection(entity_pb)
@@ -426,8 +425,8 @@ def test_hit_multiple_keys_same_dataset(self):
426425
ID2 = 2345
427426

428427
# Make a found entity pb to be returned from mock backend.
429-
entity_pb1 = self._make_entity_pb(DATASET_ID, KIND, ID1)
430-
entity_pb2 = self._make_entity_pb(DATASET_ID, KIND, ID2)
428+
entity_pb1 = _make_entity_pb(DATASET_ID, KIND, ID1)
429+
entity_pb2 = _make_entity_pb(DATASET_ID, KIND, ID2)
431430

432431
# Make a connection to return the entity pbs.
433432
connection = _Connection(entity_pb1, entity_pb2)
@@ -469,8 +468,7 @@ def test_implicit_wo_transaction(self):
469468
PATH = [{'kind': KIND, 'id': ID}]
470469

471470
# Make a found entity pb to be returned from mock backend.
472-
entity_pb = self._make_entity_pb(DATASET_ID, KIND, ID,
473-
'foo', 'Foo')
471+
entity_pb = _make_entity_pb(DATASET_ID, KIND, ID, 'foo', 'Foo')
474472

475473
# Make a connection to return the entity pb.
476474
CUSTOM_CONNECTION = _Connection(entity_pb)
@@ -507,8 +505,7 @@ def test_w_transaction(self):
507505
TRANSACTION = 'TRANSACTION'
508506

509507
# Make a found entity pb to be returned from mock backend.
510-
entity_pb = self._make_entity_pb(DATASET_ID, KIND, ID,
511-
'foo', 'Foo')
508+
entity_pb = _make_entity_pb(DATASET_ID, KIND, ID, 'foo', 'Foo')
512509

513510
# Make a connection to return the entity pb.
514511
CUSTOM_CONNECTION = _Connection(entity_pb)
@@ -545,8 +542,7 @@ def test_max_loops(self):
545542
ID = 1234
546543

547544
# Make a found entity pb to be returned from mock backend.
548-
entity_pb = self._make_entity_pb(DATASET_ID, KIND, ID,
549-
'foo', 'Foo')
545+
entity_pb = _make_entity_pb(DATASET_ID, KIND, ID, 'foo', 'Foo')
550546

551547
# Make a connection to return the entity pb.
552548
connection = _Connection(entity_pb)
@@ -566,6 +562,61 @@ def test_max_loops(self):
566562
self.assertEqual(deferred, [])
567563

568564

565+
class Test_get_function(unittest2.TestCase):
566+
567+
def setUp(self):
568+
from gcloud.datastore._testing import _setup_defaults
569+
_setup_defaults(self)
570+
571+
def tearDown(self):
572+
from gcloud.datastore._testing import _tear_down_defaults
573+
_tear_down_defaults(self)
574+
575+
def _callFUT(self, key, missing=None, deferred=None,
576+
connection=None, dataset_id=None):
577+
from gcloud.datastore.api import get
578+
return get(key, missing=missing, deferred=deferred,
579+
connection=connection, dataset_id=dataset_id)
580+
581+
def test_hit(self):
582+
from gcloud.datastore.key import Key
583+
from gcloud.datastore.test_connection import _Connection
584+
585+
DATASET_ID = 'DATASET'
586+
KIND = 'Kind'
587+
ID = 1234
588+
PATH = [{'kind': KIND, 'id': ID}]
589+
590+
# Make a found entity pb to be returned from mock backend.
591+
entity_pb = _make_entity_pb(DATASET_ID, KIND, ID, 'foo', 'Foo')
592+
593+
# Make a connection to return the entity pb.
594+
connection = _Connection(entity_pb)
595+
596+
key = Key(KIND, ID, dataset_id=DATASET_ID)
597+
result = self._callFUT(key, connection=connection,
598+
dataset_id=DATASET_ID)
599+
new_key = result.key
600+
601+
# Check the returned value is as expected.
602+
self.assertFalse(new_key is key)
603+
self.assertEqual(new_key.dataset_id, DATASET_ID)
604+
self.assertEqual(new_key.path, PATH)
605+
self.assertEqual(list(result), ['foo'])
606+
self.assertEqual(result['foo'], 'Foo')
607+
608+
def test_miss(self):
609+
from gcloud.datastore.key import Key
610+
from gcloud.datastore.test_connection import _Connection
611+
612+
DATASET_ID = 'DATASET'
613+
connection = _Connection()
614+
key = Key('Kind', 1234, dataset_id=DATASET_ID)
615+
result = self._callFUT(key, connection=connection,
616+
dataset_id=DATASET_ID)
617+
self.assertTrue(result is None)
618+
619+
569620
class Test_put_function(unittest2.TestCase):
570621

571622
def setUp(self):

gcloud/datastore/test_dataset.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ def _get(*args, **kw):
5252
key = object()
5353

5454
with _Monkey(MUT, get=_get):
55-
dataset.get([key])
55+
dataset.get(key)
5656

57-
self.assertEqual(_called_with[0][0], ([key],))
57+
self.assertEqual(_called_with[0][0], (key,))
5858
self.assertTrue(_called_with[0][1]['missing'] is None)
5959
self.assertTrue(_called_with[0][1]['deferred'] is None)
6060
self.assertTrue(_called_with[0][1]['connection'] is None)
@@ -74,7 +74,50 @@ def _get(*args, **kw):
7474
key, missing, deferred = object(), [], []
7575

7676
with _Monkey(MUT, get=_get):
77-
dataset.get([key], missing, deferred)
77+
dataset.get(key, missing, deferred)
78+
79+
self.assertEqual(_called_with[0][0], (key,))
80+
self.assertTrue(_called_with[0][1]['missing'] is missing)
81+
self.assertTrue(_called_with[0][1]['deferred'] is deferred)
82+
self.assertTrue(_called_with[0][1]['connection'] is conn)
83+
self.assertEqual(_called_with[0][1]['dataset_id'], self.DATASET_ID)
84+
85+
def test_get_multi_defaults(self):
86+
from gcloud.datastore import dataset as MUT
87+
from gcloud._testing import _Monkey
88+
89+
_called_with = []
90+
91+
def _get_multi(*args, **kw):
92+
_called_with.append((args, kw))
93+
94+
dataset = self._makeOne()
95+
key = object()
96+
97+
with _Monkey(MUT, get_multi=_get_multi):
98+
dataset.get_multi([key])
99+
100+
self.assertEqual(_called_with[0][0], ([key],))
101+
self.assertTrue(_called_with[0][1]['missing'] is None)
102+
self.assertTrue(_called_with[0][1]['deferred'] is None)
103+
self.assertTrue(_called_with[0][1]['connection'] is None)
104+
self.assertEqual(_called_with[0][1]['dataset_id'], self.DATASET_ID)
105+
106+
def test_get_multi_explicit(self):
107+
from gcloud.datastore import dataset as MUT
108+
from gcloud._testing import _Monkey
109+
110+
_called_with = []
111+
112+
def _get_multi(*args, **kw):
113+
_called_with.append((args, kw))
114+
115+
conn = object()
116+
dataset = self._makeOne(connection=conn)
117+
key, missing, deferred = object(), [], []
118+
119+
with _Monkey(MUT, get_multi=_get_multi):
120+
dataset.get_multi([key], missing, deferred)
78121

79122
self.assertEqual(_called_with[0][0], ([key],))
80123
self.assertTrue(_called_with[0][1]['missing'] is missing)

0 commit comments

Comments
 (0)