sea/format.py
# -*- coding:utf-8 -*-
import json
from google.protobuf.descriptor import FieldDescriptor
from google.protobuf.json_format import ParseDict
EXTENSION_CONTAINER = '___X'
TYPE_CALLABLE_MAP = {
FieldDescriptor.TYPE_DOUBLE: float,
FieldDescriptor.TYPE_FLOAT: float,
FieldDescriptor.TYPE_INT32: int,
FieldDescriptor.TYPE_INT64: int,
FieldDescriptor.TYPE_UINT32: int,
FieldDescriptor.TYPE_UINT64: int,
FieldDescriptor.TYPE_SINT32: int,
FieldDescriptor.TYPE_SINT64: int,
FieldDescriptor.TYPE_FIXED32: int,
FieldDescriptor.TYPE_FIXED64: int,
FieldDescriptor.TYPE_SFIXED32: int,
FieldDescriptor.TYPE_SFIXED64: int,
FieldDescriptor.TYPE_BOOL: bool,
FieldDescriptor.TYPE_STRING: str,
FieldDescriptor.TYPE_BYTES: bytes,
FieldDescriptor.TYPE_ENUM: int,
}
def repeated(type_callable):
return lambda value_list: [type_callable(value) for value in value_list]
def enum_label_name(field, value):
return field.enum_type.values_by_number[int(value)].name
def _is_map_entry(field):
return (field.type == FieldDescriptor.TYPE_MESSAGE and
field.message_type.has_options and
field.message_type.GetOptions().map_entry)
def _is_repeat_label(field):
return field.label == FieldDescriptor.LABEL_REPEATED
def msg2dict(pb, keys=None, use_enum_labels=False,
including_default_value_fields=True):
if keys:
field_values = [(pb.DESCRIPTOR.fields_by_name[key],
getattr(pb, key)) for key in keys]
else:
field_values = pb.ListFields()
result_dict, extensions = _handle_field_values(
pb, field_values, use_enum_labels, including_default_value_fields)
# Serialize default value if including_default_value_fields is True.
if including_default_value_fields:
result_dict = _handle_default_value_fields(pb, keys, result_dict)
if extensions:
result_dict[EXTENSION_CONTAINER] = extensions
return result_dict
def msg2json(msg, keys=None, indent=2, sort_keys=False):
d = msg2dict(msg, keys=keys)
return json.dumps(d, indent=indent, sort_keys=sort_keys)
def dict2msg(d, message, ignore_unknown_fields=False):
return ParseDict(d, message, ignore_unknown_fields=ignore_unknown_fields)
def stream2dict(stream):
yield from map(msg2dict, stream)
def _handle_field_values(pb, field_values,
use_enum_labels, including_default_value_fields):
result_dict = {}
extensions = {}
for field, value in field_values:
if field.message_type and field.message_type.has_options and \
field.message_type.GetOptions().map_entry:
result_dict[field.name] = dict()
value_field = field.message_type.fields_by_name['value']
type_callable = _get_field_value_adaptor(
pb, value_field,
use_enum_labels, including_default_value_fields)
for k, v in value.items():
result_dict[field.name][k] = type_callable(v)
continue
type_callable = _get_field_value_adaptor(
pb, field, use_enum_labels, including_default_value_fields)
if field.label == FieldDescriptor.LABEL_REPEATED:
type_callable = repeated(type_callable)
if field.is_extension:
extensions[str(field.number)] = type_callable(value)
continue
result_dict[field.name] = type_callable(value)
return result_dict, extensions
def _handle_default_value_fields(pb, keys, result_dict):
for field in pb.DESCRIPTOR.fields:
if keys and field.name not in keys:
continue
# Singular message fields and oneof fields will not be affected.
if ((field.label != FieldDescriptor.LABEL_REPEATED and
field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE) or
field.containing_oneof):
continue
if field.name in result_dict:
# Skip the field which has been serailized already.
continue
if _is_map_entry(field):
result_dict[field.name] = {}
elif _is_repeat_label(field):
result_dict[field.name] = []
else:
result_dict[field.name] = field.default_value
return result_dict
def _get_field_value_adaptor(pb, field, use_enum_labels,
including_default_value_fields):
if field.type == FieldDescriptor.TYPE_MESSAGE:
# recursively encode protobuf sub-message
return lambda pb: msg2dict(
pb, use_enum_labels=use_enum_labels,
including_default_value_fields=including_default_value_fields)
if use_enum_labels and field.type == FieldDescriptor.TYPE_ENUM:
return lambda value: enum_label_name(field, value)
if field.type in TYPE_CALLABLE_MAP:
return TYPE_CALLABLE_MAP[field.type]
raise TypeError("Field %s.%s has unrecognised type id %d" % (
pb.__class__.__name__, field.name, field.type))