apifunction.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/env python
  2. """A class for representing built-in EE API Function.
  3. Earth Engine can dynamically produce a JSON array listing the
  4. algorithms available to the user. Each item in the dictionary identifies
  5. the name and return type of the algorithm, the name and type of its
  6. arguments, whether they're required or optional, default values and docs
  7. for each argument and the algorithms as a whole.
  8. This class manages the algorithm dictionary and creates JavaScript functions
  9. to apply each EE algorithm.
  10. """
  11. # Using lowercase function naming to match the JavaScript names.
  12. # pylint: disable=g-bad-name
  13. import copy
  14. import keyword
  15. import re
  16. from . import computedobject
  17. from . import data
  18. from . import deprecation
  19. from . import ee_exception
  20. from . import ee_types
  21. from . import function
  22. class ApiFunction(function.Function):
  23. """An object representing an EE API Function."""
  24. # A dictionary of functions defined by the API server.
  25. _api = None
  26. # A set of algorithm names containing all algorithms that have been bound to
  27. # a function so far using importApi().
  28. _bound_signatures = set()
  29. def __init__(self, name, opt_signature=None):
  30. """Creates a function defined by the EE API.
  31. Args:
  32. name: The name of the function.
  33. opt_signature: The signature of the function. If unspecified,
  34. looked up dynamically.
  35. """
  36. if opt_signature is None:
  37. opt_signature = ApiFunction.lookup(name).getSignature()
  38. # The signature of this API function.
  39. self._signature = copy.deepcopy(opt_signature)
  40. self._signature['name'] = name
  41. def __eq__(self, other):
  42. return (isinstance(other, ApiFunction) and
  43. self.getSignature() == other.getSignature())
  44. # For Python 3, __hash__ is needed because __eq__ is defined.
  45. # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
  46. def __hash__(self):
  47. return hash(computedobject.ComputedObject.freeze(self.getSignature()))
  48. def __ne__(self, other):
  49. return not self.__eq__(other)
  50. @classmethod
  51. def call_(cls, name, *args, **kwargs):
  52. """Call a named API function with positional and keyword arguments.
  53. Args:
  54. name: The name of the API function to call.
  55. *args: Positional arguments to pass to the function.
  56. **kwargs: Keyword arguments to pass to the function.
  57. Returns:
  58. An object representing the called function. If the signature specifies
  59. a recognized return type, the returned value will be cast to that type.
  60. """
  61. return cls.lookup(name).call(*args, **kwargs)
  62. @classmethod
  63. def apply_(cls, name, named_args):
  64. """Call a named API function with a dictionary of named arguments.
  65. Args:
  66. name: The name of the API function to call.
  67. named_args: A dictionary of arguments to the function.
  68. Returns:
  69. An object representing the called function. If the signature specifies
  70. a recognized return type, the returned value will be cast to that type.
  71. """
  72. return cls.lookup(name).apply(named_args)
  73. def encode_invocation(self, unused_encoder):
  74. return self._signature['name']
  75. def encode_cloud_invocation(self, unused_encoder):
  76. return {'functionName': self._signature['name']}
  77. def getSignature(self):
  78. """Returns a description of the interface provided by this function."""
  79. return self._signature
  80. @classmethod
  81. def allSignatures(cls):
  82. """Returns a map from the name to signature for all API functions."""
  83. cls.initialize()
  84. return dict([(name, func.getSignature())
  85. for name, func in cls._api.items()])
  86. @classmethod
  87. def unboundFunctions(cls):
  88. """Returns the functions that have not been bound using importApi() yet."""
  89. cls.initialize()
  90. return dict([(name, func) for name, func in cls._api.items()
  91. if name not in cls._bound_signatures])
  92. @classmethod
  93. def lookup(cls, name):
  94. """Looks up an API function by name.
  95. Args:
  96. name: The name of the function to get.
  97. Returns:
  98. The requested ApiFunction.
  99. """
  100. result = cls.lookupInternal(name)
  101. if not name:
  102. raise ee_exception.EEException(
  103. 'Unknown built-in function name: %s' % name)
  104. return result
  105. @classmethod
  106. def lookupInternal(cls, name):
  107. """Looks up an API function by name.
  108. Args:
  109. name: The name of the function to get.
  110. Returns:
  111. The requested ApiFunction or None if not found.
  112. """
  113. cls.initialize()
  114. return cls._api.get(name, None)
  115. @classmethod
  116. def initialize(cls):
  117. """Initializes the list of signatures from the Earth Engine front-end."""
  118. if not cls._api:
  119. signatures = data.getAlgorithms()
  120. api = {}
  121. for name, sig in signatures.items():
  122. # Strip type parameters.
  123. sig['returns'] = re.sub('<.*>', '', sig['returns'])
  124. for arg in sig['args']:
  125. arg['type'] = re.sub('<.*>', '', arg['type'])
  126. api[name] = cls(name, sig)
  127. cls._api = api
  128. @classmethod
  129. def reset(cls):
  130. """Clears the API functions list so it will be reloaded from the server."""
  131. cls._api = None
  132. cls._bound_signatures = set()
  133. @classmethod
  134. def importApi(cls, target, prefix, type_name, opt_prepend=None):
  135. """Adds all API functions that begin with a given prefix to a target class.
  136. Args:
  137. target: The class to add to.
  138. prefix: The prefix to search for in the signatures.
  139. type_name: The name of the object's type. Functions whose
  140. first argument matches this type are bound as instance methods, and
  141. those whose first argument doesn't match are bound as static methods.
  142. opt_prepend: An optional string to prepend to the names of the
  143. added functions.
  144. """
  145. cls.initialize()
  146. prepend = opt_prepend or ''
  147. for name, api_func in cls._api.items():
  148. parts = name.split('.')
  149. if len(parts) == 2 and parts[0] == prefix:
  150. fname = prepend + parts[1]
  151. signature = api_func.getSignature()
  152. cls._bound_signatures.add(name)
  153. # Specifically handle the function names that are illegal in python.
  154. if keyword.iskeyword(fname):
  155. fname = fname.title()
  156. # Don't overwrite existing versions of this function.
  157. if (hasattr(target, fname) and
  158. not hasattr(getattr(target, fname), 'signature')):
  159. continue
  160. # Create a new function so we can attach properties to it.
  161. def MakeBoundFunction(func):
  162. # We need the lambda to capture "func" from the enclosing scope.
  163. return lambda *args, **kwargs: func.call(*args, **kwargs) # pylint: disable=unnecessary-lambda
  164. bound_function = MakeBoundFunction(api_func)
  165. # Add docs. If there are non-ASCII characters in the docs, and we're in
  166. # Python 2, use a hammer to force them into a str.
  167. try:
  168. setattr(bound_function, '__name__', str(name))
  169. except TypeError:
  170. setattr(bound_function, '__name__', name.encode('utf8'))
  171. try:
  172. bound_function.__doc__ = str(api_func)
  173. except UnicodeEncodeError:
  174. bound_function.__doc__ = api_func.__str__().encode('utf8')
  175. # Attach the signature object for documentation generators.
  176. bound_function.signature = signature
  177. # Mark as deprecated if needed.
  178. if signature.get('deprecated'):
  179. deprecated_decorator = deprecation.Deprecated(signature['deprecated'])
  180. bound_function = deprecated_decorator(bound_function)
  181. # Mark as preview if needed.
  182. if signature.get('preview'):
  183. bound_function.__doc__ += (
  184. '\nPREVIEW: This function is preview or internal only.')
  185. # Decide whether this is a static or an instance function.
  186. is_instance = (signature['args'] and
  187. ee_types.isSubtype(signature['args'][0]['type'],
  188. type_name))
  189. if not is_instance:
  190. bound_function = staticmethod(bound_function)
  191. # Attach the function as a method.
  192. setattr(target, fname, bound_function)
  193. @staticmethod
  194. def clearApi(target):
  195. """Removes all methods added by importApi() from a target class.
  196. Args:
  197. target: The class to remove from.
  198. """
  199. for attr_name in dir(target):
  200. attr_value = getattr(target, attr_name)
  201. if callable(attr_value) and hasattr(attr_value, 'signature'):
  202. delattr(target, attr_name)