@@ -73,6 +73,47 @@ def find_true_dataset_id(dataset_id, connection):
7373 return returned_pb .key .partition_id .dataset_id
7474
7575
76+ def _get_meaning (value_pb , is_list = False ):
77+ """Get the meaning from a protobuf value.
78+
79+ :type value_pb: :class:`gcloud.datastore._datastore_v1_pb2.Value`
80+ :param value_pb: The protobuf value to be checked for an
81+ associated meaning.
82+
83+ :type is_list: bool
84+ :param is_list: Boolean indicating if the ``value_pb`` contains
85+ a list value.
86+
87+ :rtype: int
88+ :returns: The meaning for the ``value_pb`` if one is set, else
89+ :data:`None`.
90+ :raises: :class:`ValueError <exceptions.ValueError>` if a list value
91+ has disagreeing meanings (in sub-elements) or has some
92+ elements with meanings and some without.
93+ """
94+ meaning = None
95+ if is_list :
96+ all_meaning = True
97+ for sub_value_pb in value_pb .list_value :
98+ if sub_value_pb .HasField ('meaning' ):
99+ if meaning is None :
100+ meaning = sub_value_pb .meaning
101+ elif meaning != sub_value_pb .meaning :
102+ raise ValueError ('Different meanings set on values '
103+ 'within a list_value' )
104+ else :
105+ all_meaning = False
106+
107+ if meaning is not None :
108+ if not all_meaning :
109+ raise ValueError ('A list_value contained some values with '
110+ 'and some without a meaning' )
111+ elif value_pb .HasField ('meaning' ):
112+ meaning = value_pb .meaning
113+
114+ return meaning
115+
116+
76117def entity_from_protobuf (pb ):
77118 """Factory method for creating an entity based on a protobuf.
78119
@@ -90,11 +131,19 @@ def entity_from_protobuf(pb):
90131 key = key_from_protobuf (pb .key )
91132
92133 entity_props = {}
134+ entity_meanings = {}
93135 exclude_from_indexes = []
94136
95137 for property_pb in pb .property :
96- value = _get_value_from_property_pb (property_pb )
97- entity_props [property_pb .name ] = value
138+ value = _get_value_from_value_pb (property_pb .value )
139+ prop_name = property_pb .name
140+ entity_props [prop_name ] = value
141+
142+ # Check if the property has an associated meaning.
143+ meaning = _get_meaning (property_pb .value ,
144+ is_list = isinstance (value , list ))
145+ if meaning is not None :
146+ entity_meanings [prop_name ] = (meaning , value )
98147
99148 # Check if property_pb.value was indexed. Lists need to be
100149 # special-cased and we require all `indexed` values in a list agree.
@@ -106,16 +155,67 @@ def entity_from_protobuf(pb):
106155 'be indexed or all excluded from indexes.' )
107156
108157 if not indexed_values .pop ():
109- exclude_from_indexes .append (property_pb . name )
158+ exclude_from_indexes .append (prop_name )
110159 else :
111160 if not property_pb .value .indexed :
112- exclude_from_indexes .append (property_pb . name )
161+ exclude_from_indexes .append (prop_name )
113162
114163 entity = Entity (key = key , exclude_from_indexes = exclude_from_indexes )
115164 entity .update (entity_props )
165+ entity ._meanings .update (entity_meanings )
116166 return entity
117167
118168
169+ def entity_to_protobuf (entity ):
170+ """Converts an entity into a protobuf.
171+
172+ :type entity: :class:`gcloud.datastore.entity.Entity`
173+ :param entity: The entity to be turned into a protobuf.
174+
175+ :rype: :class:`gcloud.datastore._datastore_v1_pb2.Entity`
176+ :returns: The Protobuf representing the entity.
177+ """
178+ entity_pb = datastore_pb .Entity ()
179+ if entity .key is not None :
180+ key_pb = entity .key .to_protobuf ()
181+ entity_pb .key .CopyFrom (key_pb )
182+
183+ for name , value in entity .items ():
184+ value_is_list = isinstance (value , list )
185+ if value_is_list and len (value ) == 0 :
186+ continue
187+
188+ prop = entity_pb .property .add ()
189+ # Set the name of the property.
190+ prop .name = name
191+
192+ # Set the appropriate value.
193+ _set_protobuf_value (prop .value , value )
194+
195+ # Add index information to protobuf.
196+ if name in entity .exclude_from_indexes :
197+ if not value_is_list :
198+ prop .value .indexed = False
199+
200+ for sub_value in prop .value .list_value :
201+ sub_value .indexed = False
202+
203+ # Add meaning information to protobuf.
204+ if name in entity ._meanings :
205+ meaning , orig_value = entity ._meanings [name ]
206+ # Only add the meaning back to the protobuf if the value is
207+ # unchanged from when it was originally read from the API.
208+ if orig_value is value :
209+ # For lists, we set meaning on each sub-element.
210+ if value_is_list :
211+ for sub_value_pb in prop .value .list_value :
212+ sub_value_pb .meaning = meaning
213+ else :
214+ prop .value .meaning = meaning
215+
216+ return entity_pb
217+
218+
119219def key_from_protobuf (pb ):
120220 """Factory method for creating a key based on a protobuf.
121221
@@ -248,29 +348,12 @@ def _get_value_from_value_pb(value_pb):
248348 result = entity_from_protobuf (value_pb .entity_value )
249349
250350 elif value_pb .list_value :
251- result = [_get_value_from_value_pb (x ) for x in value_pb .list_value ]
351+ result = [_get_value_from_value_pb (value )
352+ for value in value_pb .list_value ]
252353
253354 return result
254355
255356
256- def _get_value_from_property_pb (property_pb ):
257- """Given a protobuf for a Property, get the correct value.
258-
259- The Cloud Datastore Protobuf API returns a Property Protobuf which
260- has one value set and the rest blank. This function retrieves the
261- the one value provided.
262-
263- Some work is done to coerce the return value into a more useful type
264- (particularly in the case of a timestamp value, or a key value).
265-
266- :type property_pb: :class:`gcloud.datastore._datastore_v1_pb2.Property`
267- :param property_pb: The Property Protobuf.
268-
269- :returns: The value provided by the Protobuf.
270- """
271- return _get_value_from_value_pb (property_pb .value )
272-
273-
274357def _set_protobuf_value (value_pb , val ):
275358 """Assign 'val' to the correct subfield of 'value_pb'.
276359
@@ -285,7 +368,7 @@ def _set_protobuf_value(value_pb, val):
285368
286369 :type val: :class:`datetime.datetime`, boolean, float, integer, string,
287370 :class:`gcloud.datastore.key.Key`,
288- :class:`gcloud.datastore.entity.Entity`,
371+ :class:`gcloud.datastore.entity.Entity`
289372 :param val: The value to be assigned.
290373 """
291374 if val is None :
@@ -296,15 +379,8 @@ def _set_protobuf_value(value_pb, val):
296379 if attr == 'key_value' :
297380 value_pb .key_value .CopyFrom (val )
298381 elif attr == 'entity_value' :
299- e_pb = value_pb .entity_value
300- e_pb .Clear ()
301- key = val .key
302- if key is not None :
303- e_pb .key .CopyFrom (key .to_protobuf ())
304- for item_key , value in val .items ():
305- p_pb = e_pb .property .add ()
306- p_pb .name = item_key
307- _set_protobuf_value (p_pb .value , value )
382+ entity_pb = entity_to_protobuf (val )
383+ value_pb .entity_value .CopyFrom (entity_pb )
308384 elif attr == 'list_value' :
309385 l_pb = value_pb .list_value
310386 for item in val :
0 commit comments