| #!/usr/bin/env python |
| """Common code for converting proto to other formats, such as JSON.""" |
| |
| import base64 |
| import collections |
| import datetime |
| import json |
| import logging |
| import os |
| import sys |
| |
| from protorpc import message_types |
| from protorpc import messages |
| from protorpc import protojson |
| import six |
| |
| from apitools.base.py import exceptions |
| |
| __all__ = [ |
| 'CopyProtoMessage', |
| 'JsonToMessage', |
| 'MessageToJson', |
| 'DictToMessage', |
| 'MessageToDict', |
| 'PyValueToMessage', |
| 'MessageToPyValue', |
| 'MessageToRepr', |
| 'GetCustomJsonFieldMapping', |
| 'AddCustomJsonFieldMapping', |
| 'GetCustomJsonEnumMapping', |
| 'AddCustomJsonEnumMapping', |
| ] |
| |
| |
| _Codec = collections.namedtuple('_Codec', ['encoder', 'decoder']) |
| CodecResult = collections.namedtuple('CodecResult', ['value', 'complete']) |
| |
| |
| # TODO(craigcitro): Make these non-global. |
| _UNRECOGNIZED_FIELD_MAPPINGS = {} |
| _CUSTOM_MESSAGE_CODECS = {} |
| _CUSTOM_FIELD_CODECS = {} |
| _FIELD_TYPE_CODECS = {} |
| |
| |
| def MapUnrecognizedFields(field_name): |
| """Register field_name as a container for unrecognized fields.""" |
| def Register(cls): |
| _UNRECOGNIZED_FIELD_MAPPINGS[cls] = field_name |
| return cls |
| return Register |
| |
| |
| def RegisterCustomMessageCodec(encoder, decoder): |
| """Register a custom encoder/decoder for this message class.""" |
| def Register(cls): |
| _CUSTOM_MESSAGE_CODECS[cls] = _Codec(encoder=encoder, decoder=decoder) |
| return cls |
| return Register |
| |
| |
| def RegisterCustomFieldCodec(encoder, decoder): |
| """Register a custom encoder/decoder for this field.""" |
| def Register(field): |
| _CUSTOM_FIELD_CODECS[field] = _Codec(encoder=encoder, decoder=decoder) |
| return field |
| return Register |
| |
| |
| def RegisterFieldTypeCodec(encoder, decoder): |
| """Register a custom encoder/decoder for all fields of this type.""" |
| def Register(field_type): |
| _FIELD_TYPE_CODECS[field_type] = _Codec( |
| encoder=encoder, decoder=decoder) |
| return field_type |
| return Register |
| |
| |
| # TODO(craigcitro): Delete this function with the switch to proto2. |
| def CopyProtoMessage(message): |
| codec = protojson.ProtoJson() |
| return codec.decode_message(type(message), codec.encode_message(message)) |
| |
| |
| def MessageToJson(message, include_fields=None): |
| """Convert the given message to JSON.""" |
| result = _ProtoJsonApiTools.Get().encode_message(message) |
| return _IncludeFields(result, message, include_fields) |
| |
| |
| def JsonToMessage(message_type, message): |
| """Convert the given JSON to a message of type message_type.""" |
| return _ProtoJsonApiTools.Get().decode_message(message_type, message) |
| |
| |
| # TODO(craigcitro): Do this directly, instead of via JSON. |
| def DictToMessage(d, message_type): |
| """Convert the given dictionary to a message of type message_type.""" |
| return JsonToMessage(message_type, json.dumps(d)) |
| |
| |
| def MessageToDict(message): |
| """Convert the given message to a dictionary.""" |
| return json.loads(MessageToJson(message)) |
| |
| |
| def PyValueToMessage(message_type, value): |
| """Convert the given python value to a message of type message_type.""" |
| return JsonToMessage(message_type, json.dumps(value)) |
| |
| |
| def MessageToPyValue(message): |
| """Convert the given message to a python value.""" |
| return json.loads(MessageToJson(message)) |
| |
| |
| def MessageToRepr(msg, multiline=False, **kwargs): |
| """Return a repr-style string for a protorpc message. |
| |
| protorpc.Message.__repr__ does not return anything that could be considered |
| python code. Adding this function lets us print a protorpc message in such |
| a way that it could be pasted into code later, and used to compare against |
| other things. |
| |
| Args: |
| msg: protorpc.Message, the message to be repr'd. |
| multiline: bool, True if the returned string should have each field |
| assignment on its own line. |
| **kwargs: {str:str}, Additional flags for how to format the string. |
| |
| Known **kwargs: |
| shortstrings: bool, True if all string values should be |
| truncated at 100 characters, since when mocking the contents |
| typically don't matter except for IDs, and IDs are usually |
| less than 100 characters. |
| no_modules: bool, True if the long module name should not be printed with |
| each type. |
| |
| Returns: |
| str, A string of valid python (assuming the right imports have been made) |
| that recreates the message passed into this function. |
| |
| """ |
| |
| # TODO(user): craigcitro suggests a pretty-printer from apitools/gen. |
| |
| indent = kwargs.get('indent', 0) |
| |
| def IndentKwargs(kwargs): |
| kwargs = dict(kwargs) |
| kwargs['indent'] = kwargs.get('indent', 0) + 4 |
| return kwargs |
| |
| if isinstance(msg, list): |
| s = '[' |
| for item in msg: |
| if multiline: |
| s += '\n' + ' ' * (indent + 4) |
| s += MessageToRepr( |
| item, multiline=multiline, **IndentKwargs(kwargs)) + ',' |
| if multiline: |
| s += '\n' + ' ' * indent |
| s += ']' |
| return s |
| |
| if isinstance(msg, messages.Message): |
| s = type(msg).__name__ + '(' |
| if not kwargs.get('no_modules'): |
| s = msg.__module__ + '.' + s |
| names = sorted([field.name for field in msg.all_fields()]) |
| for name in names: |
| field = msg.field_by_name(name) |
| if multiline: |
| s += '\n' + ' ' * (indent + 4) |
| value = getattr(msg, field.name) |
| s += field.name + '=' + MessageToRepr( |
| value, multiline=multiline, **IndentKwargs(kwargs)) + ',' |
| if multiline: |
| s += '\n' + ' ' * indent |
| s += ')' |
| return s |
| |
| if isinstance(msg, six.string_types): |
| if kwargs.get('shortstrings') and len(msg) > 100: |
| msg = msg[:100] |
| |
| if isinstance(msg, datetime.datetime): |
| |
| class SpecialTZInfo(datetime.tzinfo): |
| |
| def __init__(self, offset): |
| super(SpecialTZInfo, self).__init__() |
| self.offset = offset |
| |
| def __repr__(self): |
| s = 'TimeZoneOffset(' + repr(self.offset) + ')' |
| if not kwargs.get('no_modules'): |
| s = 'protorpc.util.' + s |
| return s |
| |
| msg = datetime.datetime( |
| msg.year, msg.month, msg.day, msg.hour, msg.minute, msg.second, |
| msg.microsecond, SpecialTZInfo(msg.tzinfo.utcoffset(0))) |
| |
| return repr(msg) |
| |
| |
| def _GetField(message, field_path): |
| for field in field_path: |
| if field not in dir(message): |
| raise KeyError('no field "%s"' % field) |
| message = getattr(message, field) |
| return message |
| |
| |
| def _SetField(dictblob, field_path, value): |
| for field in field_path[:-1]: |
| dictblob[field] = {} |
| dictblob = dictblob[field] |
| dictblob[field_path[-1]] = value |
| |
| |
| def _IncludeFields(encoded_message, message, include_fields): |
| """Add the requested fields to the encoded message.""" |
| if include_fields is None: |
| return encoded_message |
| result = json.loads(encoded_message) |
| for field_name in include_fields: |
| try: |
| value = _GetField(message, field_name.split('.')) |
| nullvalue = None |
| if isinstance(value, list): |
| nullvalue = [] |
| except KeyError: |
| raise exceptions.InvalidDataError( |
| 'No field named %s in message of type %s' % ( |
| field_name, type(message))) |
| _SetField(result, field_name.split('.'), nullvalue) |
| return json.dumps(result) |
| |
| |
| def _GetFieldCodecs(field, attr): |
| result = [ |
| getattr(_CUSTOM_FIELD_CODECS.get(field), attr, None), |
| getattr(_FIELD_TYPE_CODECS.get(type(field)), attr, None), |
| ] |
| return [x for x in result if x is not None] |
| |
| |
| class _ProtoJsonApiTools(protojson.ProtoJson): |
| |
| """JSON encoder used by apitools clients.""" |
| _INSTANCE = None |
| |
| @classmethod |
| def Get(cls): |
| if cls._INSTANCE is None: |
| cls._INSTANCE = cls() |
| return cls._INSTANCE |
| |
| def decode_message(self, message_type, encoded_message): |
| if message_type in _CUSTOM_MESSAGE_CODECS: |
| return _CUSTOM_MESSAGE_CODECS[ |
| message_type].decoder(encoded_message) |
| # We turn off the default logging in protorpc. We may want to |
| # remove this later. |
| old_level = logging.getLogger().level |
| logging.getLogger().setLevel(logging.ERROR) |
| try: |
| result = _DecodeCustomFieldNames(message_type, encoded_message) |
| result = super(_ProtoJsonApiTools, self).decode_message( |
| message_type, result) |
| finally: |
| logging.getLogger().setLevel(old_level) |
| result = _ProcessUnknownEnums(result, encoded_message) |
| result = _ProcessUnknownMessages(result, encoded_message) |
| return _DecodeUnknownFields(result, encoded_message) |
| |
| def decode_field(self, field, value): |
| """Decode the given JSON value. |
| |
| Args: |
| field: a messages.Field for the field we're decoding. |
| value: a python value we'd like to decode. |
| |
| Returns: |
| A value suitable for assignment to field. |
| """ |
| for decoder in _GetFieldCodecs(field, 'decoder'): |
| result = decoder(field, value) |
| value = result.value |
| if result.complete: |
| return value |
| if isinstance(field, messages.MessageField): |
| field_value = self.decode_message( |
| field.message_type, json.dumps(value)) |
| elif isinstance(field, messages.EnumField): |
| value = GetCustomJsonEnumMapping( |
| field.type, json_name=value) or value |
| try: |
| field_value = super( |
| _ProtoJsonApiTools, self).decode_field(field, value) |
| except messages.DecodeError: |
| if not isinstance(value, six.string_types): |
| raise |
| field_value = None |
| else: |
| field_value = super( |
| _ProtoJsonApiTools, self).decode_field(field, value) |
| return field_value |
| |
| def encode_message(self, message): |
| if isinstance(message, messages.FieldList): |
| return '[%s]' % (', '.join(self.encode_message(x) |
| for x in message)) |
| message_type = type(message) |
| if message_type in _CUSTOM_MESSAGE_CODECS: |
| return _CUSTOM_MESSAGE_CODECS[type(message)].encoder(message) |
| message = _EncodeUnknownFields(message) |
| result = super(_ProtoJsonApiTools, self).encode_message(message) |
| return _EncodeCustomFieldNames(message, result) |
| |
| def encode_field(self, field, value): |
| """Encode the given value as JSON. |
| |
| Args: |
| field: a messages.Field for the field we're encoding. |
| value: a value for field. |
| |
| Returns: |
| A python value suitable for json.dumps. |
| """ |
| for encoder in _GetFieldCodecs(field, 'encoder'): |
| result = encoder(field, value) |
| value = result.value |
| if result.complete: |
| return value |
| if isinstance(field, messages.EnumField): |
| if field.repeated: |
| remapped_value = [GetCustomJsonEnumMapping( |
| field.type, python_name=e.name) or e.name for e in value] |
| else: |
| remapped_value = GetCustomJsonEnumMapping( |
| field.type, python_name=value.name) |
| if remapped_value: |
| return remapped_value |
| if (isinstance(field, messages.MessageField) and |
| not isinstance(field, message_types.DateTimeField)): |
| value = json.loads(self.encode_message(value)) |
| return super(_ProtoJsonApiTools, self).encode_field(field, value) |
| |
| |
| # TODO(craigcitro): Fold this and _IncludeFields in as codecs. |
| def _DecodeUnknownFields(message, encoded_message): |
| """Rewrite unknown fields in message into message.destination.""" |
| destination = _UNRECOGNIZED_FIELD_MAPPINGS.get(type(message)) |
| if destination is None: |
| return message |
| pair_field = message.field_by_name(destination) |
| if not isinstance(pair_field, messages.MessageField): |
| raise exceptions.InvalidDataFromServerError( |
| 'Unrecognized fields must be mapped to a compound ' |
| 'message type.') |
| pair_type = pair_field.message_type |
| # TODO(craigcitro): Add more error checking around the pair |
| # type being exactly what we suspect (field names, etc). |
| if isinstance(pair_type.value, messages.MessageField): |
| new_values = _DecodeUnknownMessages( |
| message, json.loads(encoded_message), pair_type) |
| else: |
| new_values = _DecodeUnrecognizedFields(message, pair_type) |
| setattr(message, destination, new_values) |
| # We could probably get away with not setting this, but |
| # why not clear it? |
| setattr(message, '_Message__unrecognized_fields', {}) |
| return message |
| |
| |
| def _DecodeUnknownMessages(message, encoded_message, pair_type): |
| """Process unknown fields in encoded_message of a message type.""" |
| field_type = pair_type.value.type |
| new_values = [] |
| all_field_names = [x.name for x in message.all_fields()] |
| for name, value_dict in encoded_message.items(): |
| if name in all_field_names: |
| continue |
| value = PyValueToMessage(field_type, value_dict) |
| if pair_type.value.repeated: |
| value = _AsMessageList(value) |
| new_pair = pair_type(key=name, value=value) |
| new_values.append(new_pair) |
| return new_values |
| |
| |
| def _DecodeUnrecognizedFields(message, pair_type): |
| """Process unrecognized fields in message.""" |
| new_values = [] |
| for unknown_field in message.all_unrecognized_fields(): |
| # TODO(craigcitro): Consider validating the variant if |
| # the assignment below doesn't take care of it. It may |
| # also be necessary to check it in the case that the |
| # type has multiple encodings. |
| value, _ = message.get_unrecognized_field_info(unknown_field) |
| value_type = pair_type.field_by_name('value') |
| if isinstance(value_type, messages.MessageField): |
| decoded_value = DictToMessage(value, pair_type.value.message_type) |
| elif isinstance(value_type, messages.EnumField): |
| decoded_value = pair_type.value.type(value) |
| else: |
| decoded_value = value |
| new_pair = pair_type(key=str(unknown_field), value=decoded_value) |
| new_values.append(new_pair) |
| return new_values |
| |
| |
| def _EncodeUnknownFields(message): |
| """Remap unknown fields in message out of message.source.""" |
| source = _UNRECOGNIZED_FIELD_MAPPINGS.get(type(message)) |
| if source is None: |
| return message |
| result = CopyProtoMessage(message) |
| pairs_field = message.field_by_name(source) |
| if not isinstance(pairs_field, messages.MessageField): |
| raise exceptions.InvalidUserInputError( |
| 'Invalid pairs field %s' % pairs_field) |
| pairs_type = pairs_field.message_type |
| value_variant = pairs_type.field_by_name('value').variant |
| pairs = getattr(message, source) |
| for pair in pairs: |
| if value_variant == messages.Variant.MESSAGE: |
| encoded_value = MessageToDict(pair.value) |
| else: |
| encoded_value = pair.value |
| result.set_unrecognized_field(pair.key, encoded_value, value_variant) |
| setattr(result, source, []) |
| return result |
| |
| |
| def _SafeEncodeBytes(field, value): |
| """Encode the bytes in value as urlsafe base64.""" |
| try: |
| if field.repeated: |
| result = [base64.urlsafe_b64encode(byte) for byte in value] |
| else: |
| result = base64.urlsafe_b64encode(value) |
| complete = True |
| except TypeError: |
| result = value |
| complete = False |
| return CodecResult(value=result, complete=complete) |
| |
| |
| def _SafeDecodeBytes(unused_field, value): |
| """Decode the urlsafe base64 value into bytes.""" |
| try: |
| result = base64.urlsafe_b64decode(str(value)) |
| complete = True |
| except TypeError: |
| result = value |
| complete = False |
| return CodecResult(value=result, complete=complete) |
| |
| |
| def _ProcessUnknownEnums(message, encoded_message): |
| """Add unknown enum values from encoded_message as unknown fields. |
| |
| ProtoRPC diverges from the usual protocol buffer behavior here and |
| doesn't allow unknown fields. Throwing on unknown fields makes it |
| impossible to let servers add new enum values and stay compatible |
| with older clients, which isn't reasonable for us. We simply store |
| unrecognized enum values as unknown fields, and all is well. |
| |
| Args: |
| message: Proto message we've decoded thus far. |
| encoded_message: JSON string we're decoding. |
| |
| Returns: |
| message, with any unknown enums stored as unrecognized fields. |
| """ |
| if not encoded_message: |
| return message |
| decoded_message = json.loads(encoded_message) |
| for field in message.all_fields(): |
| if (isinstance(field, messages.EnumField) and |
| field.name in decoded_message and |
| message.get_assigned_value(field.name) is None): |
| message.set_unrecognized_field( |
| field.name, decoded_message[field.name], messages.Variant.ENUM) |
| return message |
| |
| |
| def _ProcessUnknownMessages(message, encoded_message): |
| """Store any remaining unknown fields as strings. |
| |
| ProtoRPC currently ignores unknown values for which no type can be |
| determined (and logs a "No variant found" message). For the purposes |
| of reserializing, this is quite harmful (since it throws away |
| information). Here we simply add those as unknown fields of type |
| string (so that they can easily be reserialized). |
| |
| Args: |
| message: Proto message we've decoded thus far. |
| encoded_message: JSON string we're decoding. |
| |
| Returns: |
| message, with any remaining unrecognized fields saved. |
| """ |
| if not encoded_message: |
| return message |
| decoded_message = json.loads(encoded_message) |
| message_fields = [x.name for x in message.all_fields()] + list( |
| message.all_unrecognized_fields()) |
| missing_fields = [x for x in decoded_message.keys() |
| if x not in message_fields] |
| for field_name in missing_fields: |
| message.set_unrecognized_field(field_name, decoded_message[field_name], |
| messages.Variant.STRING) |
| return message |
| |
| |
| RegisterFieldTypeCodec(_SafeEncodeBytes, _SafeDecodeBytes)(messages.BytesField) |
| |
| |
| # Note that these could share a dictionary, since they're keyed by |
| # distinct types, but it's not really worth it. |
| _JSON_ENUM_MAPPINGS = {} |
| _JSON_FIELD_MAPPINGS = {} |
| |
| |
| def _GetTypeKey(message_type, package): |
| """Get the prefix for this message type in mapping dicts.""" |
| key = message_type.definition_name() |
| if package and key.startswith(package + '.'): |
| module_name = message_type.__module__ |
| # We normalize '__main__' to something unique, if possible. |
| if module_name == '__main__': |
| try: |
| file_name = sys.modules[module_name].__file__ |
| except (AttributeError, KeyError): |
| pass |
| else: |
| base_name = os.path.basename(file_name) |
| split_name = os.path.splitext(base_name) |
| if len(split_name) == 1: |
| module_name = unicode(base_name) |
| else: |
| module_name = u'.'.join(split_name[:-1]) |
| key = module_name + '.' + key.partition('.')[2] |
| return key |
| |
| |
| def AddCustomJsonEnumMapping(enum_type, python_name, json_name, |
| package=''): |
| """Add a custom wire encoding for a given enum value. |
| |
| This is primarily used in generated code, to handle enum values |
| which happen to be Python keywords. |
| |
| Args: |
| enum_type: (messages.Enum) An enum type |
| python_name: (string) Python name for this value. |
| json_name: (string) JSON name to be used on the wire. |
| package: (basestring, optional) Package prefix for this enum, if |
| present. We strip this off the enum name in order to generate |
| unique keys. |
| """ |
| if not issubclass(enum_type, messages.Enum): |
| raise exceptions.TypecheckError( |
| 'Cannot set JSON enum mapping for non-enum "%s"' % enum_type) |
| enum_name = _GetTypeKey(enum_type, package) |
| if python_name not in enum_type.names(): |
| raise exceptions.InvalidDataError( |
| 'Enum value %s not a value for type %s' % (python_name, enum_type)) |
| field_mappings = _JSON_ENUM_MAPPINGS.setdefault(enum_name, {}) |
| _CheckForExistingMappings('enum', enum_type, python_name, json_name) |
| field_mappings[python_name] = json_name |
| |
| |
| def AddCustomJsonFieldMapping(message_type, python_name, json_name, |
| package=''): |
| """Add a custom wire encoding for a given message field. |
| |
| This is primarily used in generated code, to handle enum values |
| which happen to be Python keywords. |
| |
| Args: |
| message_type: (messages.Message) A message type |
| python_name: (string) Python name for this value. |
| json_name: (string) JSON name to be used on the wire. |
| package: (basestring, optional) Package prefix for this message, if |
| present. We strip this off the message name in order to generate |
| unique keys. |
| """ |
| if not issubclass(message_type, messages.Message): |
| raise exceptions.TypecheckError( |
| 'Cannot set JSON field mapping for ' |
| 'non-message "%s"' % message_type) |
| message_name = _GetTypeKey(message_type, package) |
| try: |
| _ = message_type.field_by_name(python_name) |
| except KeyError: |
| raise exceptions.InvalidDataError( |
| 'Field %s not recognized for type %s' % ( |
| python_name, message_type)) |
| field_mappings = _JSON_FIELD_MAPPINGS.setdefault(message_name, {}) |
| _CheckForExistingMappings('field', message_type, python_name, json_name) |
| field_mappings[python_name] = json_name |
| |
| |
| def GetCustomJsonEnumMapping(enum_type, python_name=None, json_name=None): |
| """Return the appropriate remapping for the given enum, or None.""" |
| return _FetchRemapping(enum_type.definition_name(), 'enum', |
| python_name=python_name, json_name=json_name, |
| mappings=_JSON_ENUM_MAPPINGS) |
| |
| |
| def GetCustomJsonFieldMapping(message_type, python_name=None, json_name=None): |
| """Return the appropriate remapping for the given field, or None.""" |
| return _FetchRemapping(message_type.definition_name(), 'field', |
| python_name=python_name, json_name=json_name, |
| mappings=_JSON_FIELD_MAPPINGS) |
| |
| |
| def _FetchRemapping(type_name, mapping_type, python_name=None, json_name=None, |
| mappings=None): |
| """Common code for fetching a key or value from a remapping dict.""" |
| if python_name and json_name: |
| raise exceptions.InvalidDataError( |
| 'Cannot specify both python_name and json_name ' |
| 'for %s remapping' % mapping_type) |
| if not (python_name or json_name): |
| raise exceptions.InvalidDataError( |
| 'Must specify either python_name or json_name for %s remapping' % ( |
| mapping_type,)) |
| field_remappings = mappings.get(type_name, {}) |
| if field_remappings: |
| if python_name: |
| return field_remappings.get(python_name) |
| elif json_name: |
| if json_name in list(field_remappings.values()): |
| return [k for k in field_remappings |
| if field_remappings[k] == json_name][0] |
| return None |
| |
| |
| def _CheckForExistingMappings(mapping_type, message_type, |
| python_name, json_name): |
| """Validate that no mappings exist for the given values.""" |
| if mapping_type == 'field': |
| getter = GetCustomJsonFieldMapping |
| elif mapping_type == 'enum': |
| getter = GetCustomJsonEnumMapping |
| remapping = getter(message_type, python_name=python_name) |
| if remapping is not None: |
| raise exceptions.InvalidDataError( |
| 'Cannot add mapping for %s "%s", already mapped to "%s"' % ( |
| mapping_type, python_name, remapping)) |
| remapping = getter(message_type, json_name=json_name) |
| if remapping is not None: |
| raise exceptions.InvalidDataError( |
| 'Cannot add mapping for %s "%s", already mapped to "%s"' % ( |
| mapping_type, json_name, remapping)) |
| |
| |
| def _EncodeCustomFieldNames(message, encoded_value): |
| message_name = type(message).definition_name() |
| field_remappings = list(_JSON_FIELD_MAPPINGS.get(message_name, {}).items()) |
| if field_remappings: |
| decoded_value = json.loads(encoded_value) |
| for python_name, json_name in field_remappings: |
| if python_name in encoded_value: |
| decoded_value[json_name] = decoded_value.pop(python_name) |
| encoded_value = json.dumps(decoded_value) |
| return encoded_value |
| |
| |
| def _DecodeCustomFieldNames(message_type, encoded_message): |
| message_name = message_type.definition_name() |
| field_remappings = _JSON_FIELD_MAPPINGS.get(message_name, {}) |
| if field_remappings: |
| decoded_message = json.loads(encoded_message) |
| for python_name, json_name in list(field_remappings.items()): |
| if json_name in decoded_message: |
| decoded_message[python_name] = decoded_message.pop(json_name) |
| encoded_message = json.dumps(decoded_message) |
| return encoded_message |
| |
| |
| def _AsMessageList(msg): |
| """Convert the provided list-as-JsonValue to a list.""" |
| # This really needs to live in extra_types, but extra_types needs |
| # to import this file to be able to register codecs. |
| # TODO(craigcitro): Split out a codecs module and fix this ugly |
| # import. |
| from apitools.base.py import extra_types |
| |
| def _IsRepeatedJsonValue(msg): |
| """Return True if msg is a repeated value as a JsonValue.""" |
| if isinstance(msg, extra_types.JsonArray): |
| return True |
| if isinstance(msg, extra_types.JsonValue) and msg.array_value: |
| return True |
| return False |
| |
| if not _IsRepeatedJsonValue(msg): |
| raise ValueError('invalid argument to _AsMessageList') |
| if isinstance(msg, extra_types.JsonValue): |
| msg = msg.array_value |
| if isinstance(msg, extra_types.JsonArray): |
| msg = msg.entries |
| return msg |