Skip to content

Commit 0e41dc3

Browse files
committed
Merge pull request #668 from dhermes/lazy-loading-attempt-5
Adding lazy loading support for datastore connection.
2 parents 277ba80 + 7b0fc6e commit 0e41dc3

File tree

7 files changed

+164
-130
lines changed

7 files changed

+164
-130
lines changed

gcloud/datastore/__init__.py

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@
4646
when race conditions may occur.
4747
"""
4848

49-
from gcloud import credentials
50-
from gcloud.datastore import _implicit_environ
49+
from gcloud.datastore._implicit_environ import SCOPE
50+
from gcloud.datastore._implicit_environ import get_connection
5151
from gcloud.datastore._implicit_environ import get_default_connection
5252
from gcloud.datastore._implicit_environ import get_default_dataset_id
53+
from gcloud.datastore._implicit_environ import set_default_connection
5354
from gcloud.datastore._implicit_environ import set_default_dataset_id
5455
from gcloud.datastore.api import allocate_ids
5556
from gcloud.datastore.api import delete
@@ -63,21 +64,6 @@
6364
from gcloud.datastore.transaction import Transaction
6465

6566

66-
SCOPE = ('https://www.googleapis.com/auth/datastore',
67-
'https://www.googleapis.com/auth/userinfo.email')
68-
"""The scopes required for authenticating as a Cloud Datastore consumer."""
69-
70-
71-
def set_default_connection(connection=None):
72-
"""Set default connection either explicitly or implicitly as fall-back.
73-
74-
:type connection: :class:`gcloud.datastore.connection.Connection`
75-
:param connection: A connection provided to be the default.
76-
"""
77-
connection = connection or get_connection()
78-
_implicit_environ._DEFAULTS.connection = connection
79-
80-
8167
def set_defaults(dataset_id=None, connection=None):
8268
"""Set defaults either explicitly or implicitly as fall-back.
8369
@@ -96,25 +82,3 @@ def set_defaults(dataset_id=None, connection=None):
9682
"""
9783
set_default_dataset_id(dataset_id=dataset_id)
9884
set_default_connection(connection=connection)
99-
100-
101-
def get_connection():
102-
"""Shortcut method to establish a connection to the Cloud Datastore.
103-
104-
Use this if you are going to access several datasets
105-
with the same set of credentials (unlikely):
106-
107-
>>> from gcloud import datastore
108-
109-
>>> connection = datastore.get_connection()
110-
>>> key1 = datastore.Key('Kind', 1234, dataset_id='dataset1')
111-
>>> key2 = datastore.Key('Kind', 1234, dataset_id='dataset2')
112-
>>> entity1 = datastore.get(key1, connection=connection)
113-
>>> entity2 = datastore.get(key2, connection=connection)
114-
115-
:rtype: :class:`gcloud.datastore.connection.Connection`
116-
:returns: A connection defined with the proper credentials.
117-
"""
118-
implicit_credentials = credentials.get_credentials()
119-
scoped_credentials = implicit_credentials.create_scoped(SCOPE)
120-
return Connection(credentials=scoped_credentials)

gcloud/datastore/_implicit_environ.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
except ImportError:
2929
app_identity = None
3030

31+
from gcloud import credentials
32+
from gcloud.datastore.connection import Connection
33+
34+
35+
SCOPE = ('https://www.googleapis.com/auth/datastore',
36+
'https://www.googleapis.com/auth/userinfo.email')
37+
"""The scopes required for authenticating as a Cloud Datastore consumer."""
3138

3239
_DATASET_ENV_VAR_NAME = 'GCLOUD_DATASET_ID'
3340
_GCD_DATASET_ENV_VAR_NAME = 'DATASTORE_DATASET'
@@ -143,6 +150,38 @@ def get_default_dataset_id():
143150
return _DEFAULTS.dataset_id
144151

145152

153+
def get_connection():
154+
"""Shortcut method to establish a connection to the Cloud Datastore.
155+
156+
Use this if you are going to access several datasets
157+
with the same set of credentials (unlikely):
158+
159+
>>> from gcloud import datastore
160+
161+
>>> connection = datastore.get_connection()
162+
>>> key1 = datastore.Key('Kind', 1234, dataset_id='dataset1')
163+
>>> key2 = datastore.Key('Kind', 1234, dataset_id='dataset2')
164+
>>> entity1 = datastore.get(key1, connection=connection)
165+
>>> entity2 = datastore.get(key2, connection=connection)
166+
167+
:rtype: :class:`gcloud.datastore.connection.Connection`
168+
:returns: A connection defined with the proper credentials.
169+
"""
170+
implicit_credentials = credentials.get_credentials()
171+
scoped_credentials = implicit_credentials.create_scoped(SCOPE)
172+
return Connection(credentials=scoped_credentials)
173+
174+
175+
def set_default_connection(connection=None):
176+
"""Set default connection either explicitly or implicitly as fall-back.
177+
178+
:type connection: :class:`gcloud.datastore.connection.Connection`
179+
:param connection: A connection provided to be the default.
180+
"""
181+
connection = connection or get_connection()
182+
_DEFAULTS.connection = connection
183+
184+
146185
def get_default_connection():
147186
"""Get default connection.
148187
@@ -211,8 +250,15 @@ def dataset_id():
211250
"""Return the implicit default dataset ID."""
212251
return _determine_default_dataset_id()
213252

253+
@_lazy_property_deco
254+
@staticmethod
255+
def connection():
256+
"""Return the implicit default connection.."""
257+
return get_connection()
258+
214259
def __init__(self, connection=None, dataset_id=None, implicit=False):
215-
self.connection = connection
260+
if connection is not None or not implicit:
261+
self.connection = connection
216262
if dataset_id is not None or not implicit:
217263
self.dataset_id = dataset_id
218264

gcloud/datastore/batch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ def delete(self, key):
189189
if not _dataset_ids_equal(self._dataset_id, key.dataset_id):
190190
raise ValueError("Key must be from same dataset as batch")
191191

192-
key_pb = key.to_protobuf()
193-
helpers._add_keys_to_request(self.mutation.delete, [key_pb])
192+
key_pb = helpers._prepare_key_for_request(key.to_protobuf())
193+
self.mutation.delete.add().CopyFrom(key_pb)
194194

195195
def begin(self):
196196
"""No-op

gcloud/datastore/connection.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from gcloud import connection
2020
from gcloud.exceptions import make_exception
2121
from gcloud.datastore import _datastore_v1_pb2 as datastore_pb
22-
from gcloud.datastore import helpers
2322

2423

2524
_GCD_HOST_ENV_VAR_NAME = 'DATASTORE_HOST'
@@ -183,7 +182,7 @@ def lookup(self, dataset_id, key_pbs,
183182
"""
184183
lookup_request = datastore_pb.LookupRequest()
185184
_set_read_options(lookup_request, eventual, transaction_id)
186-
helpers._add_keys_to_request(lookup_request.key, key_pbs)
185+
_add_keys_to_request(lookup_request.key, key_pbs)
187186

188187
lookup_response = self._rpc(dataset_id, 'lookup', lookup_request,
189188
datastore_pb.LookupResponse)
@@ -363,7 +362,7 @@ def allocate_ids(self, dataset_id, key_pbs):
363362
:returns: An equal number of keys, with IDs filled in by the backend.
364363
"""
365364
request = datastore_pb.AllocateIdsRequest()
366-
helpers._add_keys_to_request(request.key, key_pbs)
365+
_add_keys_to_request(request.key, key_pbs)
367366
# Nothing to do with this response, so just execute the method.
368367
response = self._rpc(dataset_id, 'allocateIds', request,
369368
datastore_pb.AllocateIdsResponse)
@@ -386,3 +385,39 @@ def _set_read_options(request, eventual, transaction_id):
386385
opts.read_consistency = datastore_pb.ReadOptions.EVENTUAL
387386
elif transaction_id:
388387
opts.transaction = transaction_id
388+
389+
390+
def _prepare_key_for_request(key_pb): # pragma: NO COVER copied from helpers
391+
"""Add protobuf keys to a request object.
392+
393+
.. note::
394+
This is copied from `helpers` to avoid a cycle:
395+
_implicit_environ -> connection -> helpers -> key -> _implicit_environ
396+
397+
:type key_pb: :class:`gcloud.datastore._datastore_v1_pb2.Key`
398+
:param key_pb: A key to be added to a request.
399+
400+
:rtype: :class:`gcloud.datastore._datastore_v1_pb2.Key`
401+
:returns: A key which will be added to a request. It will be the
402+
original if nothing needs to be changed.
403+
"""
404+
if key_pb.partition_id.HasField('dataset_id'):
405+
new_key_pb = datastore_pb.Key()
406+
new_key_pb.CopyFrom(key_pb)
407+
new_key_pb.partition_id.ClearField('dataset_id')
408+
key_pb = new_key_pb
409+
return key_pb
410+
411+
412+
def _add_keys_to_request(request_field_pb, key_pbs):
413+
"""Add protobuf keys to a request object.
414+
415+
:type request_field_pb: `RepeatedCompositeFieldContainer`
416+
:param request_field_pb: A repeated proto field that contains keys.
417+
418+
:type key_pbs: list of :class:`gcloud.datastore._datastore_v1_pb2.Key`
419+
:param key_pbs: The keys to add to a request.
420+
"""
421+
for key_pb in key_pbs:
422+
key_pb = _prepare_key_for_request(key_pb)
423+
request_field_pb.add().CopyFrom(key_pb)

gcloud/datastore/helpers.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -308,17 +308,3 @@ def _prepare_key_for_request(key_pb):
308308
new_key_pb.partition_id.ClearField('dataset_id')
309309
key_pb = new_key_pb
310310
return key_pb
311-
312-
313-
def _add_keys_to_request(request_field_pb, key_pbs):
314-
"""Add protobuf keys to a request object.
315-
316-
:type request_field_pb: `RepeatedCompositeFieldContainer`
317-
:param request_field_pb: A repeated proto field that contains keys.
318-
319-
:type key_pbs: list of :class:`gcloud.datastore._datastore_v1_pb2.Key`
320-
:param key_pbs: The keys to add to a request.
321-
"""
322-
for key_pb in key_pbs:
323-
key_pb = _prepare_key_for_request(key_pb)
324-
request_field_pb.add().CopyFrom(key_pb)

gcloud/datastore/test___init__.py

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,6 @@
1515
import unittest2
1616

1717

18-
class Test_set_default_connection(unittest2.TestCase):
19-
20-
def setUp(self):
21-
from gcloud.datastore._testing import _setup_defaults
22-
_setup_defaults(self)
23-
24-
def tearDown(self):
25-
from gcloud.datastore._testing import _tear_down_defaults
26-
_tear_down_defaults(self)
27-
28-
def _callFUT(self, connection=None):
29-
from gcloud.datastore import set_default_connection
30-
return set_default_connection(connection=connection)
31-
32-
def test_set_explicit(self):
33-
from gcloud.datastore import _implicit_environ
34-
35-
self.assertEqual(_implicit_environ.get_default_connection(), None)
36-
fake_cnxn = object()
37-
self._callFUT(connection=fake_cnxn)
38-
self.assertEqual(_implicit_environ.get_default_connection(), fake_cnxn)
39-
40-
def test_set_implicit(self):
41-
from gcloud._testing import _Monkey
42-
from gcloud import datastore
43-
from gcloud.datastore import _implicit_environ
44-
45-
self.assertEqual(_implicit_environ.get_default_connection(), None)
46-
47-
fake_cnxn = object()
48-
with _Monkey(datastore, get_connection=lambda: fake_cnxn):
49-
self._callFUT()
50-
51-
self.assertEqual(_implicit_environ.get_default_connection(), fake_cnxn)
52-
53-
5418
class Test_set_defaults(unittest2.TestCase):
5519

5620
def _callFUT(self, dataset_id=None, connection=None):
@@ -80,23 +44,3 @@ def call_set_connection(connection=None):
8044

8145
self.assertEqual(SET_DATASET_CALLED, [DATASET_ID])
8246
self.assertEqual(SET_CONNECTION_CALLED, [CONNECTION])
83-
84-
85-
class Test_get_connection(unittest2.TestCase):
86-
87-
def _callFUT(self):
88-
from gcloud.datastore import get_connection
89-
return get_connection()
90-
91-
def test_it(self):
92-
from gcloud import credentials
93-
from gcloud.datastore.connection import Connection
94-
from gcloud.test_credentials import _Client
95-
from gcloud._testing import _Monkey
96-
97-
client = _Client()
98-
with _Monkey(credentials, client=client):
99-
found = self._callFUT()
100-
self.assertTrue(isinstance(found, Connection))
101-
self.assertTrue(found._credentials is client._signed)
102-
self.assertTrue(client._get_app_default_called)

0 commit comments

Comments
 (0)