From 79b4d5aa76c8d154417889b4b0e42203ea43c610 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 3 Nov 2014 15:58:02 -0500 Subject: [PATCH] Add 'exclude_from_indexes' method to Entity. Set it via ctor argument. Pass it to 'Connection.save_entity'. Fields in the sequence will have the 'indexed' field set False in the corrsponding protobuf. Fixes #83. --- gcloud/datastore/connection.py | 9 +++++- gcloud/datastore/entity.py | 13 +++++++-- gcloud/datastore/test_connection.py | 45 +++++++++++++++++++++++++++++ gcloud/datastore/test_entity.py | 28 +++++++++++------- 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/gcloud/datastore/connection.py b/gcloud/datastore/connection.py index 5bcdcccd5ca16..2840c787616f0 100644 --- a/gcloud/datastore/connection.py +++ b/gcloud/datastore/connection.py @@ -371,7 +371,8 @@ def allocate_ids(self, dataset_id, key_pbs): datastore_pb.AllocateIdsResponse) return list(response.key) - def save_entity(self, dataset_id, key_pb, properties): + def save_entity(self, dataset_id, key_pb, properties, + exclude_from_indexes=()): """Save an entity to the Cloud Datastore with the provided properties. .. note:: @@ -387,6 +388,9 @@ def save_entity(self, dataset_id, key_pb, properties): :type properties: dict :param properties: The properties to store on the entity. + + :type exclude_from_indexes: sequence of str + :param exclude_from_indexes: Names of properties *not* to be indexed. """ mutation = self.mutation() @@ -410,6 +414,9 @@ def save_entity(self, dataset_id, key_pb, properties): # Set the appropriate value. helpers._set_protobuf_value(prop.value, value) + if name in exclude_from_indexes: + prop.value.indexed = False + # If this is in a transaction, we should just return True. The # transaction will handle assigning any keys as necessary. if self.transaction(): diff --git a/gcloud/datastore/entity.py b/gcloud/datastore/entity.py index 0d75a5c9b4138..72eec2c9c5020 100644 --- a/gcloud/datastore/entity.py +++ b/gcloud/datastore/entity.py @@ -70,13 +70,14 @@ class Entity(dict): """ - def __init__(self, dataset=None, kind=None): + def __init__(self, dataset=None, kind=None, exclude_from_indexes=()): super(Entity, self).__init__() self._dataset = dataset if kind: self._key = Key().kind(kind) else: self._key = None + self._exclude_from_indexes = set(exclude_from_indexes) def dataset(self): """Get the :class:`.dataset.Dataset` in which this entity belongs. @@ -130,6 +131,13 @@ def kind(self): if self._key: return self._key.kind() + def exclude_from_indexes(self): + """Return field names which are *not* to be indexed. + + :rtype: list(str) + """ + return frozenset(self._exclude_from_indexes) + @classmethod def from_key(cls, key, dataset=None): """Create entity based on :class:`.datastore.key.Key`. @@ -213,7 +221,8 @@ def save(self): key_pb = connection.save_entity( dataset_id=dataset.id(), key_pb=key.to_protobuf(), - properties=dict(self)) + properties=dict(self), + exclude_from_indexes=self.exclude_from_indexes()) # If we are in a transaction and the current entity needs an # automatically assigned ID, tell the transaction where to put that. diff --git a/gcloud/datastore/test_connection.py b/gcloud/datastore/test_connection.py index 53db7d65a8a9f..d425d46541841 100644 --- a/gcloud/datastore/test_connection.py +++ b/gcloud/datastore/test_connection.py @@ -665,6 +665,51 @@ def test_save_entity_wo_transaction_w_upsert(self): self.assertEqual(len(props), 1) self.assertEqual(props[0].name, 'foo') self.assertEqual(props[0].value.string_value, u'Foo') + self.assertEqual(props[0].value.indexed, True) + self.assertEqual(len(mutation.delete), 0) + self.assertEqual(request.mode, rq_class.NON_TRANSACTIONAL) + + def test_save_entity_w_exclude_from_indexes(self): + from gcloud.datastore.connection import datastore_pb + from gcloud.datastore.key import Key + + DATASET_ID = 'DATASET' + key_pb = Key(path=[{'kind': 'Kind', 'id': 1234}]).to_protobuf() + rsp_pb = datastore_pb.CommitResponse() + conn = self._makeOne() + URI = '/'.join([ + conn.API_BASE_URL, + 'datastore', + conn.API_VERSION, + 'datasets', + DATASET_ID, + 'commit', + ]) + http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString()) + result = conn.save_entity(DATASET_ID, key_pb, {'foo': u'Foo'}, + exclude_from_indexes=['foo']) + self.assertEqual(result, True) + cw = http._called_with + self.assertEqual(cw['uri'], URI) + self.assertEqual(cw['method'], 'POST') + self.assertEqual(cw['headers']['Content-Type'], + 'application/x-protobuf') + self.assertEqual(cw['headers']['User-Agent'], conn.USER_AGENT) + rq_class = datastore_pb.CommitRequest + request = rq_class() + request.ParseFromString(cw['body']) + self.assertEqual(request.transaction, '') + mutation = request.mutation + self.assertEqual(len(mutation.insert_auto_id), 0) + upserts = list(mutation.upsert) + self.assertEqual(len(upserts), 1) + upsert = upserts[0] + self.assertEqual(upsert.key, key_pb) + props = list(upsert.property) + self.assertEqual(len(props), 1) + self.assertEqual(props[0].name, 'foo') + self.assertEqual(props[0].value.string_value, u'Foo') + self.assertEqual(props[0].value.indexed, False) self.assertEqual(len(mutation.delete), 0) self.assertEqual(request.mode, rq_class.NON_TRANSACTIONAL) diff --git a/gcloud/datastore/test_entity.py b/gcloud/datastore/test_entity.py index d53f32e6e7d07..6f796622593f9 100644 --- a/gcloud/datastore/test_entity.py +++ b/gcloud/datastore/test_entity.py @@ -13,13 +13,13 @@ def _getTargetClass(self): return Entity - def _makeOne(self, dataset=_MARKER, kind=_KIND): + def _makeOne(self, dataset=_MARKER, kind=_KIND, exclude_from_indexes=()): from gcloud.datastore.dataset import Dataset klass = self._getTargetClass() if dataset is _MARKER: dataset = Dataset(_DATASET_ID) - return klass(dataset, kind) + return klass(dataset, kind, exclude_from_indexes) def test_ctor_defaults(self): klass = self._getTargetClass() @@ -27,13 +27,17 @@ def test_ctor_defaults(self): self.assertEqual(entity.key(), None) self.assertEqual(entity.dataset(), None) self.assertEqual(entity.kind(), None) + self.assertEqual(sorted(entity.exclude_from_indexes()), []) def test_ctor_explicit(self): from gcloud.datastore.dataset import Dataset dataset = Dataset(_DATASET_ID) - entity = self._makeOne(dataset, _KIND) + _EXCLUDE_FROM_INDEXES = ['foo', 'bar'] + entity = self._makeOne(dataset, _KIND, _EXCLUDE_FROM_INDEXES) self.assertTrue(entity.dataset() is dataset) + self.assertEqual(sorted(entity.exclude_from_indexes()), + sorted(_EXCLUDE_FROM_INDEXES)) def test_key_getter(self): from gcloud.datastore.key import Key @@ -132,7 +136,7 @@ def test_save_wo_transaction_wo_auto_id_wo_returned_key(self): self.assertTrue(entity.save() is entity) self.assertEqual(entity['foo'], 'Foo') self.assertEqual(connection._saved, - (_DATASET_ID, 'KEY', {'foo': 'Foo'})) + (_DATASET_ID, 'KEY', {'foo': 'Foo'}, ())) self.assertEqual(key._path, None) def test_save_w_transaction_wo_partial_key(self): @@ -146,7 +150,7 @@ def test_save_w_transaction_wo_partial_key(self): self.assertTrue(entity.save() is entity) self.assertEqual(entity['foo'], 'Foo') self.assertEqual(connection._saved, - (_DATASET_ID, 'KEY', {'foo': 'Foo'})) + (_DATASET_ID, 'KEY', {'foo': 'Foo'}, ())) self.assertEqual(transaction._added, ()) self.assertEqual(key._path, None) @@ -162,11 +166,11 @@ def test_save_w_transaction_w_partial_key(self): self.assertTrue(entity.save() is entity) self.assertEqual(entity['foo'], 'Foo') self.assertEqual(connection._saved, - (_DATASET_ID, 'KEY', {'foo': 'Foo'})) + (_DATASET_ID, 'KEY', {'foo': 'Foo'}, ())) self.assertEqual(transaction._added, (entity,)) self.assertEqual(key._path, None) - def test_save_w_returned_key(self): + def test_save_w_returned_key_exclude_from_indexes(self): from gcloud.datastore import datastore_v1_pb2 as datastore_pb key_pb = datastore_pb.Key() key_pb.partition_id.dataset_id = _DATASET_ID @@ -175,13 +179,13 @@ def test_save_w_returned_key(self): connection._save_result = key_pb dataset = _Dataset(connection) key = _Key() - entity = self._makeOne(dataset) + entity = self._makeOne(dataset, exclude_from_indexes=['foo']) entity.key(key) entity['foo'] = 'Foo' self.assertTrue(entity.save() is entity) self.assertEqual(entity['foo'], 'Foo') self.assertEqual(connection._saved, - (_DATASET_ID, 'KEY', {'foo': 'Foo'})) + (_DATASET_ID, 'KEY', {'foo': 'Foo'}, ('foo',))) self.assertEqual(key._path, [{'kind': _KIND, 'id': _ID}]) def test_delete_no_key(self): @@ -257,8 +261,10 @@ class _Connection(object): def transaction(self): return self._transaction - def save_entity(self, dataset_id, key_pb, properties): - self._saved = (dataset_id, key_pb, properties) + def save_entity(self, dataset_id, key_pb, properties, + exclude_from_indexes=()): + self._saved = (dataset_id, key_pb, properties, + tuple(exclude_from_indexes)) return self._save_result def delete_entities(self, dataset_id, key_pbs):