Index: third_party/gsutil/third_party/protorpc/protorpc/protojson.py |
diff --git a/third_party/gsutil/third_party/protorpc/protorpc/protojson.py b/third_party/gsutil/third_party/protorpc/protorpc/protojson.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..a269fa25996c0fb093e4209a88b0d9a6e4963afb |
--- /dev/null |
+++ b/third_party/gsutil/third_party/protorpc/protorpc/protojson.py |
@@ -0,0 +1,366 @@ |
+#!/usr/bin/env python |
+# |
+# Copyright 2010 Google Inc. |
+# |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
+# you may not use this file except in compliance with the License. |
+# You may obtain a copy of the License at |
+# |
+# http://www.apache.org/licenses/LICENSE-2.0 |
+# |
+# Unless required by applicable law or agreed to in writing, software |
+# distributed under the License is distributed on an "AS IS" BASIS, |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+# See the License for the specific language governing permissions and |
+# limitations under the License. |
+# |
+ |
+"""JSON support for message types. |
+ |
+Public classes: |
+ MessageJSONEncoder: JSON encoder for message objects. |
+ |
+Public functions: |
+ encode_message: Encodes a message in to a JSON string. |
+ decode_message: Merge from a JSON string in to a message. |
+""" |
+import six |
+ |
+__author__ = 'rafek@google.com (Rafe Kaplan)' |
+ |
+import base64 |
+import binascii |
+import logging |
+ |
+from . import message_types |
+from . import messages |
+from . import util |
+ |
+__all__ = [ |
+ 'ALTERNATIVE_CONTENT_TYPES', |
+ 'CONTENT_TYPE', |
+ 'MessageJSONEncoder', |
+ 'encode_message', |
+ 'decode_message', |
+ 'ProtoJson', |
+] |
+ |
+ |
+def _load_json_module(): |
+ """Try to load a valid json module. |
+ |
+ There are more than one json modules that might be installed. They are |
+ mostly compatible with one another but some versions may be different. |
+ This function attempts to load various json modules in a preferred order. |
+ It does a basic check to guess if a loaded version of json is compatible. |
+ |
+ Returns: |
+ Compatible json module. |
+ |
+ Raises: |
+ ImportError if there are no json modules or the loaded json module is |
+ not compatible with ProtoRPC. |
+ """ |
+ first_import_error = None |
+ for module_name in ['json', |
+ 'simplejson']: |
+ try: |
+ module = __import__(module_name, {}, {}, 'json') |
+ if not hasattr(module, 'JSONEncoder'): |
+ message = ('json library "%s" is not compatible with ProtoRPC' % |
+ module_name) |
+ logging.warning(message) |
+ raise ImportError(message) |
+ else: |
+ return module |
+ except ImportError as err: |
+ if not first_import_error: |
+ first_import_error = err |
+ |
+ logging.error('Must use valid json library (Python 2.6 json or simplejson)') |
+ raise first_import_error |
+json = _load_json_module() |
+ |
+ |
+# TODO: Rename this to MessageJsonEncoder. |
+class MessageJSONEncoder(json.JSONEncoder): |
+ """Message JSON encoder class. |
+ |
+ Extension of JSONEncoder that can build JSON from a message object. |
+ """ |
+ |
+ def __init__(self, protojson_protocol=None, **kwargs): |
+ """Constructor. |
+ |
+ Args: |
+ protojson_protocol: ProtoJson instance. |
+ """ |
+ super(MessageJSONEncoder, self).__init__(**kwargs) |
+ self.__protojson_protocol = protojson_protocol or ProtoJson.get_default() |
+ |
+ def default(self, value): |
+ """Return dictionary instance from a message object. |
+ |
+ Args: |
+ value: Value to get dictionary for. If not encodable, will |
+ call superclasses default method. |
+ """ |
+ if isinstance(value, messages.Enum): |
+ return str(value) |
+ |
+ if six.PY3 and isinstance(value, bytes): |
+ return value.decode('utf8') |
+ |
+ if isinstance(value, messages.Message): |
+ result = {} |
+ for field in value.all_fields(): |
+ item = value.get_assigned_value(field.name) |
+ if item not in (None, [], ()): |
+ result[field.name] = self.__protojson_protocol.encode_field( |
+ field, item) |
+ # Handle unrecognized fields, so they're included when a message is |
+ # decoded then encoded. |
+ for unknown_key in value.all_unrecognized_fields(): |
+ unrecognized_field, _ = value.get_unrecognized_field_info(unknown_key) |
+ result[unknown_key] = unrecognized_field |
+ return result |
+ else: |
+ return super(MessageJSONEncoder, self).default(value) |
+ |
+ |
+class ProtoJson(object): |
+ """ProtoRPC JSON implementation class. |
+ |
+ Implementation of JSON based protocol used for serializing and deserializing |
+ message objects. Instances of remote.ProtocolConfig constructor or used with |
+ remote.Protocols.add_protocol. See the remote.py module for more details. |
+ """ |
+ |
+ CONTENT_TYPE = 'application/json' |
+ ALTERNATIVE_CONTENT_TYPES = [ |
+ 'application/x-javascript', |
+ 'text/javascript', |
+ 'text/x-javascript', |
+ 'text/x-json', |
+ 'text/json', |
+ ] |
+ |
+ def encode_field(self, field, value): |
+ """Encode a python field value to a JSON value. |
+ |
+ Args: |
+ field: A ProtoRPC field instance. |
+ value: A python value supported by field. |
+ |
+ Returns: |
+ A JSON serializable value appropriate for field. |
+ """ |
+ if isinstance(field, messages.BytesField): |
+ if field.repeated: |
+ value = [base64.b64encode(byte) for byte in value] |
+ else: |
+ value = base64.b64encode(value) |
+ elif isinstance(field, message_types.DateTimeField): |
+ # DateTimeField stores its data as a RFC 3339 compliant string. |
+ if field.repeated: |
+ value = [i.isoformat() for i in value] |
+ else: |
+ value = value.isoformat() |
+ return value |
+ |
+ def encode_message(self, message): |
+ """Encode Message instance to JSON string. |
+ |
+ Args: |
+ Message instance to encode in to JSON string. |
+ |
+ Returns: |
+ String encoding of Message instance in protocol JSON format. |
+ |
+ Raises: |
+ messages.ValidationError if message is not initialized. |
+ """ |
+ message.check_initialized() |
+ |
+ return json.dumps(message, cls=MessageJSONEncoder, protojson_protocol=self) |
+ |
+ def decode_message(self, message_type, encoded_message): |
+ """Merge JSON structure to Message instance. |
+ |
+ Args: |
+ message_type: Message to decode data to. |
+ encoded_message: JSON encoded version of message. |
+ |
+ Returns: |
+ Decoded instance of message_type. |
+ |
+ Raises: |
+ ValueError: If encoded_message is not valid JSON. |
+ messages.ValidationError if merged message is not initialized. |
+ """ |
+ if not encoded_message.strip(): |
+ return message_type() |
+ |
+ dictionary = json.loads(encoded_message) |
+ message = self.__decode_dictionary(message_type, dictionary) |
+ message.check_initialized() |
+ return message |
+ |
+ def __find_variant(self, value): |
+ """Find the messages.Variant type that describes this value. |
+ |
+ Args: |
+ value: The value whose variant type is being determined. |
+ |
+ Returns: |
+ The messages.Variant value that best describes value's type, or None if |
+ it's a type we don't know how to handle. |
+ """ |
+ if isinstance(value, bool): |
+ return messages.Variant.BOOL |
+ elif isinstance(value, six.integer_types): |
+ return messages.Variant.INT64 |
+ elif isinstance(value, float): |
+ return messages.Variant.DOUBLE |
+ elif isinstance(value, six.string_types): |
+ return messages.Variant.STRING |
+ elif isinstance(value, (list, tuple)): |
+ # Find the most specific variant that covers all elements. |
+ variant_priority = [None, messages.Variant.INT64, messages.Variant.DOUBLE, |
+ messages.Variant.STRING] |
+ chosen_priority = 0 |
+ for v in value: |
+ variant = self.__find_variant(v) |
+ try: |
+ priority = variant_priority.index(variant) |
+ except IndexError: |
+ priority = -1 |
+ if priority > chosen_priority: |
+ chosen_priority = priority |
+ return variant_priority[chosen_priority] |
+ # Unrecognized type. |
+ return None |
+ |
+ def __decode_dictionary(self, message_type, dictionary): |
+ """Merge dictionary in to message. |
+ |
+ Args: |
+ message: Message to merge dictionary in to. |
+ dictionary: Dictionary to extract information from. Dictionary |
+ is as parsed from JSON. Nested objects will also be dictionaries. |
+ """ |
+ message = message_type() |
+ for key, value in six.iteritems(dictionary): |
+ if value is None: |
+ try: |
+ message.reset(key) |
+ except AttributeError: |
+ pass # This is an unrecognized field, skip it. |
+ continue |
+ |
+ try: |
+ field = message.field_by_name(key) |
+ except KeyError: |
+ # Save unknown values. |
+ variant = self.__find_variant(value) |
+ if variant: |
+ if key.isdigit(): |
+ key = int(key) |
+ message.set_unrecognized_field(key, value, variant) |
+ else: |
+ logging.warning('No variant found for unrecognized field: %s', key) |
+ continue |
+ |
+ # Normalize values in to a list. |
+ if isinstance(value, list): |
+ if not value: |
+ continue |
+ else: |
+ value = [value] |
+ |
+ valid_value = [] |
+ for item in value: |
+ valid_value.append(self.decode_field(field, item)) |
+ |
+ if field.repeated: |
+ existing_value = getattr(message, field.name) |
+ setattr(message, field.name, valid_value) |
+ else: |
+ setattr(message, field.name, valid_value[-1]) |
+ return message |
+ |
+ def decode_field(self, field, value): |
+ """Decode a JSON value to a python value. |
+ |
+ Args: |
+ field: A ProtoRPC field instance. |
+ value: A serialized JSON value. |
+ |
+ Return: |
+ A Python value compatible with field. |
+ """ |
+ if isinstance(field, messages.EnumField): |
+ try: |
+ return field.type(value) |
+ except TypeError: |
+ raise messages.DecodeError('Invalid enum value "%s"' % (value or '')) |
+ |
+ elif isinstance(field, messages.BytesField): |
+ try: |
+ return base64.b64decode(value) |
+ except (binascii.Error, TypeError) as err: |
+ raise messages.DecodeError('Base64 decoding error: %s' % err) |
+ |
+ elif isinstance(field, message_types.DateTimeField): |
+ try: |
+ return util.decode_datetime(value) |
+ except ValueError as err: |
+ raise messages.DecodeError(err) |
+ |
+ elif (isinstance(field, messages.MessageField) and |
+ issubclass(field.type, messages.Message)): |
+ return self.__decode_dictionary(field.type, value) |
+ |
+ elif (isinstance(field, messages.FloatField) and |
+ isinstance(value, (six.integer_types, six.string_types))): |
+ try: |
+ return float(value) |
+ except: |
+ pass |
+ |
+ elif (isinstance(field, messages.IntegerField) and |
+ isinstance(value, six.string_types)): |
+ try: |
+ return int(value) |
+ except: |
+ pass |
+ |
+ return value |
+ |
+ @staticmethod |
+ def get_default(): |
+ """Get default instanceof ProtoJson.""" |
+ try: |
+ return ProtoJson.__default |
+ except AttributeError: |
+ ProtoJson.__default = ProtoJson() |
+ return ProtoJson.__default |
+ |
+ @staticmethod |
+ def set_default(protocol): |
+ """Set the default instance of ProtoJson. |
+ |
+ Args: |
+ protocol: A ProtoJson instance. |
+ """ |
+ if not isinstance(protocol, ProtoJson): |
+ raise TypeError('Expected protocol of type ProtoJson') |
+ ProtoJson.__default = protocol |
+ |
+CONTENT_TYPE = ProtoJson.CONTENT_TYPE |
+ |
+ALTERNATIVE_CONTENT_TYPES = ProtoJson.ALTERNATIVE_CONTENT_TYPES |
+ |
+encode_message = ProtoJson.get_default().encode_message |
+ |
+decode_message = ProtoJson.get_default().decode_message |