Skip to content

Commit 8a4ee61

Browse files
committed
Merge branch 'master' into 170-query_namespaces
2 parents 3b582a7 + 83e8262 commit 8a4ee61

File tree

4 files changed

+88
-36
lines changed

4 files changed

+88
-36
lines changed

gcloud/datastore/connection.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ def commit(self, dataset_id, mutation_pb):
338338
def save_entity(self, dataset_id, key_pb, properties):
339339
"""Save an entity to the Cloud Datastore with the provided properties.
340340
341+
.. note::
342+
Any existing properties for the entity identified by 'key_pb'
343+
will be replaced by those passed in 'properties'; properties
344+
not passed in 'properties' no longer be set for the entity.
345+
341346
:type dataset_id: string
342347
:param dataset_id: The dataset in which to save the entity.
343348

gcloud/datastore/entity.py

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
from gcloud.datastore.key import Key
2020

2121

22+
class NoKey(RuntimeError):
23+
"""Exception raised by Entity methods which require a key."""
24+
25+
2226
class Entity(dict): # pylint: disable=too-many-public-methods
2327
""":type dataset: :class:`gcloud.datastore.dataset.Dataset`
2428
:param dataset: The dataset in which this entity belongs.
@@ -82,8 +86,8 @@ def dataset(self):
8286
be `None`. It also means that if you change the key on the entity,
8387
this will refer to that key's dataset.
8488
"""
85-
if self.key():
86-
return self.key().dataset()
89+
if self._key:
90+
return self._key.dataset()
8791

8892
def key(self, key=None):
8993
"""Get or set the :class:`.datastore.key.Key` on the current entity.
@@ -117,8 +121,8 @@ def kind(self):
117121
which knows its Kind.
118122
"""
119123

120-
if self.key():
121-
return self.key().kind()
124+
if self._key:
125+
return self._key.kind()
122126

123127
@classmethod
124128
def from_key(cls, key):
@@ -162,6 +166,18 @@ def from_protobuf(cls, pb, dataset=None): # pylint: disable=invalid-name
162166

163167
return entity
164168

169+
@property
170+
def _must_key(self):
171+
"""Return our key, or raise NoKey if not set.
172+
173+
:rtype: :class:`gcloud.datastore.key.Key`.
174+
:returns: our key
175+
:raises: NoKey if key is None
176+
"""
177+
if self._key is None:
178+
raise NoKey('no key')
179+
return self._key
180+
165181
def reload(self):
166182
"""Reloads the contents of this entity from the datastore.
167183
@@ -174,11 +190,8 @@ def reload(self):
174190
exists remotely, however it will *not* override any properties that
175191
exist only locally.
176192
"""
177-
178-
# Note that you must have a valid key, otherwise this makes no sense.
179-
# pylint: disable=maybe-no-member
180-
entity = self.dataset().get_entity(self.key().to_protobuf())
181-
# pylint: enable=maybe-no-member
193+
key = self._must_key
194+
entity = key.dataset().get_entity(key.to_protobuf())
182195

183196
if entity:
184197
self.update(entity)
@@ -187,30 +200,33 @@ def reload(self):
187200
def save(self):
188201
"""Save the entity in the Cloud Datastore.
189202
203+
.. note::
204+
Any existing properties for the entity will be replaced by those
205+
currently set on this instance. Already-stored properties which do
206+
not correspond to keys set on this instance will be removed from
207+
the datastore.
208+
190209
:rtype: :class:`gcloud.datastore.entity.Entity`
191210
:returns: The entity with a possibly updated Key.
192211
"""
193-
# pylint: disable=maybe-no-member
194-
key_pb = self.dataset().connection().save_entity(
195-
dataset_id=self.dataset().id(),
196-
key_pb=self.key().to_protobuf(), properties=dict(self))
197-
# pylint: enable=maybe-no-member
212+
key = self._must_key
213+
dataset = key.dataset()
214+
connection = dataset.connection()
215+
key_pb = connection.save_entity(
216+
dataset_id=dataset.id(),
217+
key_pb=key.to_protobuf(),
218+
properties=dict(self))
198219

199220
# If we are in a transaction and the current entity needs an
200221
# automatically assigned ID, tell the transaction where to put that.
201-
transaction = self.dataset().connection().transaction()
202-
# pylint: disable=maybe-no-member
203-
if transaction and self.key().is_partial():
222+
transaction = connection.transaction()
223+
if transaction and key.is_partial():
204224
transaction.add_auto_id_entity(self)
205-
# pylint: enable=maybe-no-member
206225

207226
if isinstance(key_pb, datastore_pb.Key):
208227
updated_key = Key.from_protobuf(key_pb)
209228
# Update the path (which may have been altered).
210-
# pylint: disable=maybe-no-member
211-
key = self.key().path(updated_key.path())
212-
# pylint: enable=maybe-no-member
213-
self.key(key)
229+
self._key = key.path(updated_key.path())
214230

215231
return self
216232

@@ -222,20 +238,14 @@ def delete(self):
222238
set on the entity. Whatever is stored remotely using the key on the
223239
entity will be deleted.
224240
"""
225-
# NOTE: pylint thinks key() is an Entity, hence key().to_protobuf()
226-
# is not defined. This is because one branch of the return
227-
# in the key() definition returns self.
228-
# pylint: disable=maybe-no-member
229-
self.dataset().connection().delete_entity(
230-
dataset_id=self.dataset().id(), key_pb=self.key().to_protobuf())
231-
# pylint: enable=maybe-no-member
241+
key = self._must_key
242+
dataset = key.dataset()
243+
dataset.connection().delete_entity(
244+
dataset_id=dataset.id(), key_pb=key.to_protobuf())
232245

233246
def __repr__(self):
234-
# An entity should have a key all the time (even if it's partial).
235-
if self.key():
236-
# pylint: disable=maybe-no-member
237-
return '<Entity%s %s>' % (self.key().path(),
247+
if self._key:
248+
return '<Entity%s %s>' % (self._key.path(),
238249
super(Entity, self).__repr__())
239-
# pylint: enable=maybe-no-member
240250
else:
241251
return '<Entity %s>' % (super(Entity, self).__repr__())

gcloud/datastore/query.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Create / interact with gcloud datastore queries."""
22

33
import base64
4-
import copy
54

65
from gcloud.datastore import datastore_v1_pb2 as datastore_pb
76
from gcloud.datastore import _helpers

gcloud/datastore/test_entity.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ def test_from_protobuf(self):
8585
self.assertEqual(key.kind(), _KIND)
8686
self.assertEqual(key.id(), _ID)
8787

88+
def test_reload_no_key(self):
89+
from gcloud.datastore.entity import NoKey
90+
91+
entity = self._makeOne(None, None)
92+
entity['foo'] = 'Foo'
93+
self.assertRaises(NoKey, entity.reload)
94+
8895
def test_reload_miss(self):
8996
dataset = _Dataset()
9097
key = _Key(dataset)
@@ -105,6 +112,13 @@ def test_reload_hit(self):
105112
self.assertTrue(entity.reload() is entity)
106113
self.assertEqual(entity['foo'], 'Bar')
107114

115+
def test_save_no_key(self):
116+
from gcloud.datastore.entity import NoKey
117+
118+
entity = self._makeOne(None, None)
119+
entity['foo'] = 'Foo'
120+
self.assertRaises(NoKey, entity.save)
121+
108122
def test_save_wo_transaction_wo_auto_id_wo_returned_key(self):
109123
connection = _Connection()
110124
dataset = _Dataset(connection)
@@ -167,6 +181,13 @@ def test_save_w_returned_key(self):
167181
(_DATASET_ID, 'KEY', {'foo': 'Foo'}))
168182
self.assertEqual(key._path, [{'kind': _KIND, 'id': _ID}])
169183

184+
def test_delete_no_key(self):
185+
from gcloud.datastore.entity import NoKey
186+
187+
entity = self._makeOne(None, None)
188+
entity['foo'] = 'Foo'
189+
self.assertRaises(NoKey, entity.delete)
190+
170191
def test_delete(self):
171192
connection = _Connection()
172193
dataset = _Dataset(connection)
@@ -177,8 +198,23 @@ def test_delete(self):
177198
self.assertTrue(entity.delete() is None)
178199
self.assertEqual(connection._deleted, (_DATASET_ID, 'KEY'))
179200

201+
def test___repr___no_key_empty(self):
202+
entity = self._makeOne(None, None)
203+
self.assertEqual(repr(entity), '<Entity {}>')
204+
205+
def test___repr___w_key_non_empty(self):
206+
connection = _Connection()
207+
dataset = _Dataset(connection)
208+
key = _Key(dataset)
209+
key.path('/bar/baz')
210+
entity = self._makeOne()
211+
entity.key(key)
212+
entity['foo'] = 'Foo'
213+
self.assertEqual(repr(entity), "<Entity/bar/baz {'foo': 'Foo'}>")
214+
180215

181216
class _Key(object):
217+
_MARKER = object()
182218
_key = 'KEY'
183219
_partial = False
184220
_path = None
@@ -195,7 +231,9 @@ def to_protobuf(self):
195231
def is_partial(self):
196232
return self._partial
197233

198-
def path(self, path):
234+
def path(self, path=_MARKER):
235+
if path is self._MARKER:
236+
return self._path
199237
self._path = path
200238

201239

0 commit comments

Comments
 (0)