#!/usr/bin/env python """A deserializer that decodes EE object trees from JSON DAGs.""" # Using lowercase function naming to match the JavaScript names. # pylint: disable=g-bad-name # pylint: disable=g-bad-import-order import json import numbers import six from . import apifunction from . import computedobject from . import customfunction from . import ee_date from . import ee_exception from . import encodable from . import function from . import geometry def fromJSON(json_obj): """Deserialize an object from a JSON string appropriate for API calls. Args: json_obj: The JSON representation of the input. Returns: The deserialized object. """ return decode(json.loads(json_obj)) def decode(json_obj): """Decodes an object previously encoded using the EE API v2 (DAG) format. Args: json_obj: The serialied object to decode. Returns: The decoded object. """ if 'values' in json_obj and 'result' in json_obj: return decodeCloudApi(json_obj) named_values = {} # Incrementally decode scope entries if there are any. if isinstance(json_obj, dict) and json_obj['type'] == 'CompoundValue': for i, (key, value) in enumerate(json_obj['scope']): if key in named_values: raise ee_exception.EEException( 'Duplicate scope key "%s" in scope #%d.' % (key, i)) named_values[key] = _decodeValue(value, named_values) json_obj = json_obj['value'] # Decode the final value. return _decodeValue(json_obj, named_values) def _decodeValue(json_obj, named_values): """Decodes an object previously encoded using the EE API v2 (DAG) format. This uses a provided scope for ValueRef lookup and does not not allow the input to be a CompoundValue. Args: json_obj: The serialied object to decode. named_values: The objects that can be referenced by ValueRefs. Returns: The decoded object. """ # Check for primitive values. if (json_obj is None or isinstance(json_obj, (bool, numbers.Number, six.string_types))): return json_obj # Check for array values. if isinstance(json_obj, (list, tuple)): return [_decodeValue(element, named_values) for element in json_obj] # Ensure that we've got a proper object at this point. if not isinstance(json_obj, dict): raise ee_exception.EEException('Cannot decode object: ' + json_obj) # Check for explicitly typed values. type_name = json_obj['type'] if type_name == 'ValueRef': if json_obj['value'] in named_values: return named_values[json_obj['value']] else: raise ee_exception.EEException('Unknown ValueRef: ' + json_obj) elif type_name == 'ArgumentRef': var_name = json_obj['value'] if not isinstance(var_name, six.string_types): raise ee_exception.EEException('Invalid variable name: ' + var_name) return customfunction.CustomFunction.variable(None, var_name) # pylint: disable=protected-access elif type_name == 'Date': microseconds = json_obj['value'] if not isinstance(microseconds, numbers.Number): raise ee_exception.EEException('Invalid date value: ' + microseconds) return ee_date.Date(microseconds / 1e3) elif type_name == 'Bytes': result = encodable.Encodable() result.encode = lambda encoder: json_obj node = {'bytesValue': json_obj['value']} result.encode_cloud_value = lambda encoder: node return result elif type_name == 'Invocation': if 'functionName' in json_obj: func = apifunction.ApiFunction.lookup(json_obj['functionName']) else: func = _decodeValue(json_obj['function'], named_values) args = dict((key, _decodeValue(value, named_values)) for (key, value) in json_obj['arguments'].items()) return _invocation(func, args) elif type_name == 'Dictionary': return dict((key, _decodeValue(value, named_values)) for (key, value) in json_obj['value'].items()) elif type_name == 'Function': body = _decodeValue(json_obj['body'], named_values) signature = { 'name': '', 'args': [{'name': arg_name, 'type': 'Object', 'optional': False} for arg_name in json_obj['argumentNames']], 'returns': 'Object' } return customfunction.CustomFunction(signature, lambda *args: body) elif type_name in ('Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'LinearRing', 'GeometryCollection'): return geometry.Geometry(json_obj) elif type_name == 'CompoundValue': raise ee_exception.EEException('Nested CompoundValues are disallowed.') else: raise ee_exception.EEException('Unknown encoded object type: ' + type_name) def _invocation(func, args): """Creates an EE object representing the application of `func` to `args`.""" if isinstance(func, function.Function): return func.apply(args) elif isinstance(func, computedobject.ComputedObject): # We have to allow ComputedObjects for cases where invocations return a # function, e.g. Image.parseExpression(). These need to get turned back into # some kind of Function, for which we need a signature. Type information has # been lost at this point, so we just use ComputedObject. signature = { 'name': '', 'args': [{'name': name, 'type': 'ComputedObject', 'optional': False} for name in args], 'returns': 'ComputedObject' } return function.SecondOrderFunction(func, signature).apply(args) raise ee_exception.EEException('Invalid function value: %s' % func) def fromCloudApiJSON(json_obj): """Deserializes an object from the JSON string used in Cloud API calls. Args: json_obj: The JSON representation of the input. Returns: The deserialized object. """ return decodeCloudApi(json.loads(json_obj)) def decodeCloudApi(json_obj): """Decodes an object previously encoded using the EE Cloud API format. Args: json_obj: The serialized object to decode. Returns: The decoded object. """ decoded = {} def lookup(reference, kind): if reference not in decoded: if reference not in json_obj['values']: raise ee_exception.EEException('Cannot find %s %s' % (reference, kind)) decoded[reference] = decode_node(json_obj['values'][reference]) return decoded[reference] def decode_node(node): if 'constantValue' in node: return node['constantValue'] elif 'arrayValue' in node: return [decode_node(x) for x in node['arrayValue']['values']] elif 'dictionaryValue' in node: return {key: decode_node(x) for key, x in six.iteritems(node['dictionaryValue']['values'])} elif 'argumentReference' in node: return customfunction.CustomFunction.variable( None, node['argumentReference']) # pylint: disable=protected-access elif 'functionDefinitionValue' in node: return decode_function_definition(node['functionDefinitionValue']) elif 'functionInvocationValue' in node: return decode_function_invocation(node['functionInvocationValue']) elif 'bytesValue' in node: return _decodeValue({'type': 'Bytes', 'value': node['bytesValue']}, {}) elif 'integerValue' in node: return int(node['integerValue']) elif 'valueReference' in node: return lookup(node['valueReference'], 'reference') return None def decode_function_definition(defined): body = lookup(defined['body'], 'function body') signature_args = [{'name': name, 'type': 'Object', 'optional': False} for name in defined['argumentNames']] signature = {'args': signature_args, 'name': '', 'returns': 'Object'} return customfunction.CustomFunction(signature, lambda *args: body) def decode_function_invocation(invoked): if 'functionReference' in invoked: func = lookup(invoked['functionReference'], 'function') else: func = apifunction.ApiFunction.lookup(invoked['functionName']) args = { key: decode_node(x) for key, x in six.iteritems(invoked['arguments']) } return _invocation(func, args) return lookup(json_obj['result'], 'result value')