@@ -266,66 +266,69 @@ def _parse_value_pb(value_pb, field_type, field_name, column_info=None):
266266 :returns: value extracted from value_pb
267267 :raises ValueError: if unknown type is passed
268268 """
269+ decoder = _get_type_decoder (field_type , field_name , column_info )
270+ return _parse_nullable (value_pb , decoder )
271+
272+
273+ def _get_type_decoder (field_type , field_name , column_info = None ):
274+ """Returns a function that converts a Value protobuf to cell data.
275+
276+ :type field_type: :class:`~google.cloud.spanner_v1.types.Type`
277+ :param field_type: type code for the value
278+
279+ :type field_name: str
280+ :param field_name: column name
281+
282+ :type column_info: dict
283+ :param column_info: (Optional) dict of column name and column information.
284+ An object where column names as keys and custom objects as corresponding
285+ values for deserialization. It's specifically useful for data types like
286+ protobuf where deserialization logic is on user-specific code. When provided,
287+ the custom object enables deserialization of backend-received column data.
288+ If not provided, data remains serialized as bytes for Proto Messages and
289+ integer for Proto Enums.
290+
291+ :rtype: a function that takes a single protobuf value as an input argument
292+ :returns: a function that can be used to extract a value from a protobuf value
293+ :raises ValueError: if unknown type is passed
294+ """
295+
269296 type_code = field_type .code
270- if value_pb .HasField ("null_value" ):
271- return None
272297 if type_code == TypeCode .STRING :
273- return value_pb . string_value
298+ return _parse_string
274299 elif type_code == TypeCode .BYTES :
275- return value_pb . string_value . encode ( "utf8" )
300+ return _parse_bytes
276301 elif type_code == TypeCode .BOOL :
277- return value_pb . bool_value
302+ return _parse_bool
278303 elif type_code == TypeCode .INT64 :
279- return int ( value_pb . string_value )
304+ return _parse_int64
280305 elif type_code == TypeCode .FLOAT64 :
281- if value_pb .HasField ("string_value" ):
282- return float (value_pb .string_value )
283- else :
284- return value_pb .number_value
306+ return _parse_float
285307 elif type_code == TypeCode .FLOAT32 :
286- if value_pb .HasField ("string_value" ):
287- return float (value_pb .string_value )
288- else :
289- return value_pb .number_value
308+ return _parse_float
290309 elif type_code == TypeCode .DATE :
291- return _date_from_iso8601_date ( value_pb . string_value )
310+ return _parse_date
292311 elif type_code == TypeCode .TIMESTAMP :
293- DatetimeWithNanoseconds = datetime_helpers .DatetimeWithNanoseconds
294- return DatetimeWithNanoseconds .from_rfc3339 (value_pb .string_value )
295- elif type_code == TypeCode .ARRAY :
296- return [
297- _parse_value_pb (
298- item_pb , field_type .array_element_type , field_name , column_info
299- )
300- for item_pb in value_pb .list_value .values
301- ]
302- elif type_code == TypeCode .STRUCT :
303- return [
304- _parse_value_pb (
305- item_pb , field_type .struct_type .fields [i ].type_ , field_name , column_info
306- )
307- for (i , item_pb ) in enumerate (value_pb .list_value .values )
308- ]
312+ return _parse_timestamp
309313 elif type_code == TypeCode .NUMERIC :
310- return decimal . Decimal ( value_pb . string_value )
314+ return _parse_numeric
311315 elif type_code == TypeCode .JSON :
312- return JsonObject . from_str ( value_pb . string_value )
316+ return _parse_json
313317 elif type_code == TypeCode .PROTO :
314- bytes_value = base64 .b64decode (value_pb .string_value )
315- if column_info is not None and column_info .get (field_name ) is not None :
316- default_proto_message = column_info .get (field_name )
317- if isinstance (default_proto_message , Message ):
318- proto_message = type (default_proto_message )()
319- proto_message .ParseFromString (bytes_value )
320- return proto_message
321- return bytes_value
318+ return lambda value_pb : _parse_proto (value_pb , column_info , field_name )
322319 elif type_code == TypeCode .ENUM :
323- int_value = int (value_pb .string_value )
324- if column_info is not None and column_info .get (field_name ) is not None :
325- proto_enum = column_info .get (field_name )
326- if isinstance (proto_enum , EnumTypeWrapper ):
327- return proto_enum .Name (int_value )
328- return int_value
320+ return lambda value_pb : _parse_proto_enum (value_pb , column_info , field_name )
321+ elif type_code == TypeCode .ARRAY :
322+ element_decoder = _get_type_decoder (
323+ field_type .array_element_type , field_name , column_info
324+ )
325+ return lambda value_pb : _parse_array (value_pb , element_decoder )
326+ elif type_code == TypeCode .STRUCT :
327+ element_decoders = [
328+ _get_type_decoder (item_field .type_ , field_name , column_info )
329+ for item_field in field_type .struct_type .fields
330+ ]
331+ return lambda value_pb : _parse_struct (value_pb , element_decoders )
329332 else :
330333 raise ValueError ("Unknown type: %s" % (field_type ,))
331334
@@ -351,6 +354,87 @@ def _parse_list_value_pbs(rows, row_type):
351354 return result
352355
353356
357+ def _parse_string (value_pb ) -> str :
358+ return value_pb .string_value
359+
360+
361+ def _parse_bytes (value_pb ):
362+ return value_pb .string_value .encode ("utf8" )
363+
364+
365+ def _parse_bool (value_pb ) -> bool :
366+ return value_pb .bool_value
367+
368+
369+ def _parse_int64 (value_pb ) -> int :
370+ return int (value_pb .string_value )
371+
372+
373+ def _parse_float (value_pb ) -> float :
374+ if value_pb .HasField ("string_value" ):
375+ return float (value_pb .string_value )
376+ else :
377+ return value_pb .number_value
378+
379+
380+ def _parse_date (value_pb ):
381+ return _date_from_iso8601_date (value_pb .string_value )
382+
383+
384+ def _parse_timestamp (value_pb ):
385+ DatetimeWithNanoseconds = datetime_helpers .DatetimeWithNanoseconds
386+ return DatetimeWithNanoseconds .from_rfc3339 (value_pb .string_value )
387+
388+
389+ def _parse_numeric (value_pb ):
390+ return decimal .Decimal (value_pb .string_value )
391+
392+
393+ def _parse_json (value_pb ):
394+ return JsonObject .from_str (value_pb .string_value )
395+
396+
397+ def _parse_proto (value_pb , column_info , field_name ):
398+ bytes_value = base64 .b64decode (value_pb .string_value )
399+ if column_info is not None and column_info .get (field_name ) is not None :
400+ default_proto_message = column_info .get (field_name )
401+ if isinstance (default_proto_message , Message ):
402+ proto_message = type (default_proto_message )()
403+ proto_message .ParseFromString (bytes_value )
404+ return proto_message
405+ return bytes_value
406+
407+
408+ def _parse_proto_enum (value_pb , column_info , field_name ):
409+ int_value = int (value_pb .string_value )
410+ if column_info is not None and column_info .get (field_name ) is not None :
411+ proto_enum = column_info .get (field_name )
412+ if isinstance (proto_enum , EnumTypeWrapper ):
413+ return proto_enum .Name (int_value )
414+ return int_value
415+
416+
417+ def _parse_array (value_pb , element_decoder ) -> []:
418+ return [
419+ _parse_nullable (item_pb , element_decoder )
420+ for item_pb in value_pb .list_value .values
421+ ]
422+
423+
424+ def _parse_struct (value_pb , element_decoders ):
425+ return [
426+ _parse_nullable (item_pb , element_decoders [i ])
427+ for (i , item_pb ) in enumerate (value_pb .list_value .values )
428+ ]
429+
430+
431+ def _parse_nullable (value_pb , decoder ):
432+ if value_pb .HasField ("null_value" ):
433+ return None
434+ else :
435+ return decoder (value_pb )
436+
437+
354438class _SessionWrapper (object ):
355439 """Base class for objects wrapping a session.
356440
0 commit comments