Skip to content

Commit 99e4d53

Browse files
committed
Merge pull request #263 from tseaver/233-support_blob_value
Address #233: support blob_value in protobuf
2 parents 67b8302 + d5642c9 commit 99e4d53

File tree

5 files changed

+53
-27
lines changed

5 files changed

+53
-27
lines changed

gcloud/datastore/_helpers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ def _get_protobuf_attribute_and_value(val):
2929
`gcloud.datastore.key.Key` into a Protobuf representation.
3030
This function handles that for you.
3131
32+
.. note::
33+
Values which are "text" ('unicode' in Python2, 'str' in Python3) map
34+
to 'string_value' in the datastore; values which are "bytes"
35+
('str' in Python2, 'bytes' in Python3) map to 'blob_value'.
36+
3237
For example:
3338
3439
>>> _get_protobuf_attribute_and_value(1234)
@@ -62,8 +67,10 @@ def _get_protobuf_attribute_and_value(val):
6267
elif isinstance(val, (int, long)):
6368
INT_VALUE_CHECKER.CheckValue(val) # Raise an exception if invalid.
6469
name, value = 'integer', long(val) # Always cast to a long.
65-
elif isinstance(val, basestring):
70+
elif isinstance(val, unicode):
6671
name, value = 'string', val
72+
elif isinstance(val, (bytes, str)):
73+
name, value = 'blob', val
6774
elif isinstance(val, Entity):
6875
name, value = 'entity', val
6976
elif isinstance(val, list):
@@ -112,6 +119,9 @@ def _get_value_from_value_pb(value_pb):
112119
elif value_pb.HasField('string_value'):
113120
result = value_pb.string_value
114121

122+
elif value_pb.HasField('blob_value'):
123+
result = value_pb.blob_value
124+
115125
elif value_pb.HasField('entity_value'):
116126
result = Entity.from_protobuf(value_pb.entity_value)
117127

gcloud/datastore/entity.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ def save(self):
208208
not correspond to keys set on this instance will be removed from
209209
the datastore.
210210
211+
.. note::
212+
Property values which are "text" ('unicode' in Python2, 'str' in
213+
Python3) map to 'string_value' in the datastore; values which are
214+
"bytes" ('str' in Python2, 'bytes' in Python3) map to 'blob_value'.
215+
211216
:rtype: :class:`gcloud.datastore.entity.Entity`
212217
:returns: The entity with a possibly updated Key.
213218
"""

gcloud/datastore/test__helpers.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,14 @@ def test_long_too_large(self):
7575

7676
def test_native_str(self):
7777
name, value = self._callFUT('str')
78-
self.assertEqual(name, 'string_value')
78+
self.assertEqual(name, 'blob_value')
7979
self.assertEqual(value, 'str')
8080

81+
def test_bytes(self):
82+
name, value = self._callFUT(b'bytes')
83+
self.assertEqual(name, 'blob_value')
84+
self.assertEqual(value, b'bytes')
85+
8186
def test_unicode(self):
8287
name, value = self._callFUT(u'str')
8388
self.assertEqual(name, 'string_value')
@@ -151,9 +156,9 @@ def test_int(self):
151156
pb = self._makePB('integer_value', 42)
152157
self.assertEqual(self._callFUT(pb), 42)
153158

154-
def test_native_str(self):
155-
pb = self._makePB('string_value', 'str')
156-
self.assertEqual(self._callFUT(pb), 'str')
159+
def test_bytes(self):
160+
pb = self._makePB('blob_value', b'str')
161+
self.assertEqual(self._callFUT(pb), b'str')
157162

158163
def test_unicode(self):
159164
pb = self._makePB('string_value', u'str')
@@ -272,9 +277,15 @@ def test_long(self):
272277
def test_native_str(self):
273278
pb = self._makePB()
274279
self._callFUT(pb, 'str')
275-
value = pb.string_value
280+
value = pb.blob_value
276281
self.assertEqual(value, 'str')
277282

283+
def test_bytes(self):
284+
pb = self._makePB()
285+
self._callFUT(pb, b'str')
286+
value = pb.blob_value
287+
self.assertEqual(value, b'str')
288+
278289
def test_unicode(self):
279290
pb = self._makePB()
280291
self._callFUT(pb, u'str')
@@ -299,18 +310,18 @@ def test_entity_w_key(self):
299310
pb = self._makePB()
300311
key = Key(path=[{'kind': 'KIND', 'id': 123}])
301312
entity = Entity().key(key)
302-
entity['foo'] = 'Foo'
313+
entity['foo'] = u'Foo'
303314
self._callFUT(pb, entity)
304315
value = pb.entity_value
305316
self.assertEqual(value.key, key.to_protobuf())
306317
props = list(value.property)
307318
self.assertEqual(len(props), 1)
308319
self.assertEqual(props[0].name, 'foo')
309-
self.assertEqual(props[0].value.string_value, 'Foo')
320+
self.assertEqual(props[0].value.string_value, u'Foo')
310321

311322
def test_list(self):
312323
pb = self._makePB()
313-
values = ['a', 0, 3.14]
324+
values = [u'a', 0, 3.14]
314325
self._callFUT(pb, values)
315326
marshalled = pb.list_value
316327
self.assertEqual(len(marshalled), len(values))

gcloud/datastore/test_connection.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ def test_commit_wo_transaction(self):
530530
insert.key.CopyFrom(key_pb)
531531
prop = insert.property.add()
532532
prop.name = 'foo'
533-
prop.value.string_value = 'Foo'
533+
prop.value.string_value = u'Foo'
534534
conn = self._makeOne()
535535
URI = '/'.join([
536536
conn.API_BASE_URL,
@@ -577,7 +577,7 @@ def id(self):
577577
insert.key.CopyFrom(key_pb)
578578
prop = insert.property.add()
579579
prop.name = 'foo'
580-
prop.value.string_value = 'Foo'
580+
prop.value.string_value = u'Foo'
581581
conn = self._makeOne()
582582
conn.transaction(Xact())
583583
URI = '/'.join([
@@ -627,7 +627,7 @@ def test_save_entity_wo_transaction_w_upsert(self):
627627
'commit',
628628
])
629629
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
630-
result = conn.save_entity(DATASET_ID, key_pb, {'foo': 'Foo'})
630+
result = conn.save_entity(DATASET_ID, key_pb, {'foo': u'Foo'})
631631
self.assertEqual(result, True)
632632
cw = http._called_with
633633
self.assertEqual(cw['uri'], URI)
@@ -651,7 +651,7 @@ def test_save_entity_wo_transaction_w_upsert(self):
651651
props = list(upsert.property)
652652
self.assertEqual(len(props), 1)
653653
self.assertEqual(props[0].name, 'foo')
654-
self.assertEqual(props[0].value.string_value, 'Foo')
654+
self.assertEqual(props[0].value.string_value, u'Foo')
655655
self.assertEqual(len(mutation.delete), 0)
656656
self.assertEqual(request.mode, rq_class.NON_TRANSACTIONAL)
657657

@@ -680,7 +680,7 @@ def test_save_entity_wo_transaction_w_auto_id(self):
680680
'commit',
681681
])
682682
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
683-
result = conn.save_entity(DATASET_ID, key_pb, {'foo': 'Foo'})
683+
result = conn.save_entity(DATASET_ID, key_pb, {'foo': u'Foo'})
684684
self.assertEqual(result, updated_key_pb)
685685
cw = http._called_with
686686
self.assertEqual(cw['uri'], URI)
@@ -702,7 +702,7 @@ def test_save_entity_wo_transaction_w_auto_id(self):
702702
props = list(insert.property)
703703
self.assertEqual(len(props), 1)
704704
self.assertEqual(props[0].name, 'foo')
705-
self.assertEqual(props[0].value.string_value, 'Foo')
705+
self.assertEqual(props[0].value.string_value, u'Foo')
706706
self.assertEqual(len(inserts), 1)
707707
upserts = list(mutation.upsert)
708708
self.assertEqual(len(upserts), 0)
@@ -726,7 +726,7 @@ def mutation(self):
726726
conn = self._makeOne()
727727
conn.transaction(Xact())
728728
http = conn._http = Http({'status': '200'}, rsp_pb.SerializeToString())
729-
result = conn.save_entity(DATASET_ID, key_pb, {'foo': 'Foo'})
729+
result = conn.save_entity(DATASET_ID, key_pb, {'foo': u'Foo'})
730730
self.assertEqual(result, True)
731731
self.assertEqual(http._called_with, None)
732732
mutation = conn.mutation()
@@ -745,7 +745,7 @@ def mutation(self):
745745
return mutation
746746
DATASET_ID = 'DATASET'
747747
nested = Entity()
748-
nested['bar'] = 'Bar'
748+
nested['bar'] = u'Bar'
749749
key_pb = Key(dataset=Dataset(DATASET_ID),
750750
path=[{'kind': 'Kind', 'id': 1234}]).to_protobuf()
751751
rsp_pb = datastore_pb.CommitResponse()

gcloud/datastore/test_query.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,23 @@ def test_filter_w_unknown_operator(self):
6969

7070
def test_filter_w_known_operator(self):
7171
query = self._makeOne()
72-
after = query.filter('firstname =', 'John')
72+
after = query.filter('firstname =', u'John')
7373
self.assertFalse(after is query)
7474
self.assertTrue(isinstance(after, self._getTargetClass()))
7575
q_pb = after.to_protobuf()
7676
self.assertEqual(q_pb.filter.composite_filter.operator, 1) # AND
7777
f_pb, = list(q_pb.filter.composite_filter.filter)
7878
p_pb = f_pb.property_filter
7979
self.assertEqual(p_pb.property.name, 'firstname')
80-
self.assertEqual(p_pb.value.string_value, 'John')
80+
self.assertEqual(p_pb.value.string_value, u'John')
8181

8282
def test_filter_w_known_operator_and_entity(self):
8383
import operator
8484
from gcloud.datastore.entity import Entity
8585
query = self._makeOne()
8686
other = Entity()
87-
other['firstname'] = 'John'
88-
other['lastname'] = 'Smith'
87+
other['firstname'] = u'John'
88+
other['lastname'] = u'Smith'
8989
after = query.filter('other =', other)
9090
self.assertFalse(after is query)
9191
self.assertTrue(isinstance(after, self._getTargetClass()))
@@ -98,9 +98,9 @@ def test_filter_w_known_operator_and_entity(self):
9898
props = sorted(other_pb.property, key=operator.attrgetter('name'))
9999
self.assertEqual(len(props), 2)
100100
self.assertEqual(props[0].name, 'firstname')
101-
self.assertEqual(props[0].value.string_value, 'John')
101+
self.assertEqual(props[0].value.string_value, u'John')
102102
self.assertEqual(props[1].name, 'lastname')
103-
self.assertEqual(props[1].value.string_value, 'Smith')
103+
self.assertEqual(props[1].value.string_value, u'Smith')
104104

105105
def test_ancestor_w_non_key_non_list(self):
106106
query = self._makeOne()
@@ -110,7 +110,7 @@ def test_ancestor_wo_existing_ancestor_query_w_key_and_propfilter(self):
110110
from gcloud.datastore.key import Key
111111
_KIND = 'KIND'
112112
_ID = 123
113-
_NAME = 'NAME'
113+
_NAME = u'NAME'
114114
key = Key(path=[{'kind': _KIND, 'id': _ID}])
115115
query = self._makeOne().filter('name =', _NAME)
116116
after = query.ancestor(key)
@@ -172,7 +172,7 @@ def test_ancestor_clears_existing_ancestor_query_w_only(self):
172172
def test_ancestor_clears_existing_ancestor_query_w_others(self):
173173
_KIND = 'KIND'
174174
_ID = 123
175-
_NAME = 'NAME'
175+
_NAME = u'NAME'
176176
query = self._makeOne().filter('name =', _NAME)
177177
between = query.ancestor([_KIND, _ID])
178178
after = between.ancestor(None)
@@ -251,7 +251,7 @@ def test_fetch_default_limit(self):
251251
path_element.id = _ID
252252
prop = entity_pb.property.add()
253253
prop.name = 'foo'
254-
prop.value.string_value = 'Foo'
254+
prop.value.string_value = u'Foo'
255255
connection = _Connection(entity_pb)
256256
dataset = _Dataset(_DATASET, connection)
257257
query = self._makeOne(_KIND, dataset)
@@ -279,7 +279,7 @@ def test_fetch_explicit_limit(self):
279279
path_element.id = _ID
280280
prop = entity_pb.property.add()
281281
prop.name = 'foo'
282-
prop.value.string_value = 'Foo'
282+
prop.value.string_value = u'Foo'
283283
connection = _Connection(entity_pb)
284284
connection._cursor = _CURSOR
285285
dataset = _Dataset(_DATASET, connection)

0 commit comments

Comments
 (0)