deserializer.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. #!/usr/bin/env python
  2. """A deserializer that decodes EE object trees from JSON DAGs."""
  3. # Using lowercase function naming to match the JavaScript names.
  4. # pylint: disable=g-bad-name
  5. # pylint: disable=g-bad-import-order
  6. import json
  7. import numbers
  8. from . import apifunction
  9. from . import computedobject
  10. from . import customfunction
  11. from . import ee_date
  12. from . import ee_exception
  13. from . import encodable
  14. from . import function
  15. from . import geometry
  16. def fromJSON(json_obj):
  17. """Deserialize an object from a JSON string appropriate for API calls.
  18. Args:
  19. json_obj: The JSON representation of the input.
  20. Returns:
  21. The deserialized object.
  22. """
  23. return decode(json.loads(json_obj))
  24. def decode(json_obj):
  25. """Decodes an object previously encoded using the EE API v2 (DAG) format.
  26. Args:
  27. json_obj: The serialied object to decode.
  28. Returns:
  29. The decoded object.
  30. """
  31. if 'values' in json_obj and 'result' in json_obj:
  32. return decodeCloudApi(json_obj)
  33. named_values = {}
  34. # Incrementally decode scope entries if there are any.
  35. if isinstance(json_obj, dict) and json_obj['type'] == 'CompoundValue':
  36. for i, (key, value) in enumerate(json_obj['scope']):
  37. if key in named_values:
  38. raise ee_exception.EEException(
  39. 'Duplicate scope key "%s" in scope #%d.' % (key, i))
  40. named_values[key] = _decodeValue(value, named_values)
  41. json_obj = json_obj['value']
  42. # Decode the final value.
  43. return _decodeValue(json_obj, named_values)
  44. def _decodeValue(json_obj, named_values):
  45. """Decodes an object previously encoded using the EE API v2 (DAG) format.
  46. This uses a provided scope for ValueRef lookup and does not not allow the
  47. input to be a CompoundValue.
  48. Args:
  49. json_obj: The serialied object to decode.
  50. named_values: The objects that can be referenced by ValueRefs.
  51. Returns:
  52. The decoded object.
  53. """
  54. # Check for primitive values.
  55. if (json_obj is None or isinstance(json_obj, (bool, numbers.Number, str))):
  56. return json_obj
  57. # Check for array values.
  58. if isinstance(json_obj, (list, tuple)):
  59. return [_decodeValue(element, named_values) for element in json_obj]
  60. # Ensure that we've got a proper object at this point.
  61. if not isinstance(json_obj, dict):
  62. raise ee_exception.EEException('Cannot decode object: ' + json_obj)
  63. # Check for explicitly typed values.
  64. type_name = json_obj['type']
  65. if type_name == 'ValueRef':
  66. if json_obj['value'] in named_values:
  67. return named_values[json_obj['value']]
  68. else:
  69. raise ee_exception.EEException('Unknown ValueRef: ' + json_obj)
  70. elif type_name == 'ArgumentRef':
  71. var_name = json_obj['value']
  72. if not isinstance(var_name, str):
  73. raise ee_exception.EEException('Invalid variable name: ' + var_name)
  74. return customfunction.CustomFunction.variable(None, var_name) # pylint: disable=protected-access
  75. elif type_name == 'Date':
  76. microseconds = json_obj['value']
  77. if not isinstance(microseconds, numbers.Number):
  78. raise ee_exception.EEException('Invalid date value: ' + microseconds)
  79. return ee_date.Date(microseconds / 1e3)
  80. elif type_name == 'Bytes':
  81. result = encodable.Encodable()
  82. result.encode = lambda encoder: json_obj
  83. node = {'bytesValue': json_obj['value']}
  84. result.encode_cloud_value = lambda encoder: node
  85. return result
  86. elif type_name == 'Invocation':
  87. if 'functionName' in json_obj:
  88. func = apifunction.ApiFunction.lookup(json_obj['functionName'])
  89. else:
  90. func = _decodeValue(json_obj['function'], named_values)
  91. args = dict((key, _decodeValue(value, named_values))
  92. for (key, value) in json_obj['arguments'].items())
  93. return _invocation(func, args)
  94. elif type_name == 'Dictionary':
  95. return dict((key, _decodeValue(value, named_values))
  96. for (key, value) in json_obj['value'].items())
  97. elif type_name == 'Function':
  98. body = _decodeValue(json_obj['body'], named_values)
  99. signature = {
  100. 'name': '',
  101. 'args': [{'name': arg_name, 'type': 'Object', 'optional': False}
  102. for arg_name in json_obj['argumentNames']],
  103. 'returns': 'Object'
  104. }
  105. return customfunction.CustomFunction(signature, lambda *args: body)
  106. elif type_name in ('Point', 'MultiPoint', 'LineString', 'MultiLineString',
  107. 'Polygon', 'MultiPolygon', 'LinearRing',
  108. 'GeometryCollection'):
  109. return geometry.Geometry(json_obj)
  110. elif type_name == 'CompoundValue':
  111. raise ee_exception.EEException('Nested CompoundValues are disallowed.')
  112. else:
  113. raise ee_exception.EEException('Unknown encoded object type: ' + type_name)
  114. def _invocation(func, args):
  115. """Creates an EE object representing the application of `func` to `args`."""
  116. if isinstance(func, function.Function):
  117. return func.apply(args)
  118. elif isinstance(func, computedobject.ComputedObject):
  119. # We have to allow ComputedObjects for cases where invocations return a
  120. # function, e.g. Image.parseExpression(). These need to get turned back into
  121. # some kind of Function, for which we need a signature. Type information has
  122. # been lost at this point, so we just use ComputedObject.
  123. signature = {
  124. 'name': '',
  125. 'args': [{'name': name, 'type': 'ComputedObject', 'optional': False}
  126. for name in args],
  127. 'returns': 'ComputedObject'
  128. }
  129. return function.SecondOrderFunction(func, signature).apply(args)
  130. raise ee_exception.EEException('Invalid function value: %s' % func)
  131. def fromCloudApiJSON(json_obj):
  132. """Deserializes an object from the JSON string used in Cloud API calls.
  133. Args:
  134. json_obj: The JSON representation of the input.
  135. Returns:
  136. The deserialized object.
  137. """
  138. return decodeCloudApi(json.loads(json_obj))
  139. def decodeCloudApi(json_obj):
  140. """Decodes an object previously encoded using the EE Cloud API format.
  141. Args:
  142. json_obj: The serialized object to decode.
  143. Returns:
  144. The decoded object.
  145. """
  146. decoded = {}
  147. def lookup(reference, kind):
  148. if reference not in decoded:
  149. if reference not in json_obj['values']:
  150. raise ee_exception.EEException('Cannot find %s %s' % (reference, kind))
  151. decoded[reference] = decode_node(json_obj['values'][reference])
  152. return decoded[reference]
  153. def decode_node(node):
  154. if 'constantValue' in node:
  155. return node['constantValue']
  156. elif 'arrayValue' in node:
  157. return [decode_node(x) for x in node['arrayValue']['values']]
  158. elif 'dictionaryValue' in node:
  159. return {
  160. key: decode_node(x)
  161. for key, x in node['dictionaryValue']['values'].items()
  162. }
  163. elif 'argumentReference' in node:
  164. return customfunction.CustomFunction.variable(
  165. None, node['argumentReference']) # pylint: disable=protected-access
  166. elif 'functionDefinitionValue' in node:
  167. return decode_function_definition(node['functionDefinitionValue'])
  168. elif 'functionInvocationValue' in node:
  169. return decode_function_invocation(node['functionInvocationValue'])
  170. elif 'bytesValue' in node:
  171. return _decodeValue({'type': 'Bytes', 'value': node['bytesValue']}, {})
  172. elif 'integerValue' in node:
  173. return int(node['integerValue'])
  174. elif 'valueReference' in node:
  175. return lookup(node['valueReference'], 'reference')
  176. return None
  177. def decode_function_definition(defined):
  178. body = lookup(defined['body'], 'function body')
  179. signature_args = [{'name': name, 'type': 'Object', 'optional': False}
  180. for name in defined['argumentNames']]
  181. signature = {'args': signature_args, 'name': '', 'returns': 'Object'}
  182. return customfunction.CustomFunction(signature, lambda *args: body)
  183. def decode_function_invocation(invoked):
  184. if 'functionReference' in invoked:
  185. func = lookup(invoked['functionReference'], 'function')
  186. else:
  187. func = apifunction.ApiFunction.lookup(invoked['functionName'])
  188. args = {key: decode_node(x) for key, x in invoked['arguments'].items()}
  189. return _invocation(func, args)
  190. return lookup(json_obj['result'], 'result value')