| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- #!/usr/bin/env python
- """A class for representing built-in EE API Function.
- Earth Engine can dynamically produce a JSON array listing the
- algorithms available to the user. Each item in the dictionary identifies
- the name and return type of the algorithm, the name and type of its
- arguments, whether they're required or optional, default values and docs
- for each argument and the algorithms as a whole.
- This class manages the algorithm dictionary and creates JavaScript functions
- to apply each EE algorithm.
- """
- # Using lowercase function naming to match the JavaScript names.
- # pylint: disable=g-bad-name
- import copy
- import keyword
- import re
- from . import computedobject
- from . import data
- from . import deprecation
- from . import ee_exception
- from . import ee_types
- from . import function
- class ApiFunction(function.Function):
- """An object representing an EE API Function."""
- # A dictionary of functions defined by the API server.
- _api = None
- # A set of algorithm names containing all algorithms that have been bound to
- # a function so far using importApi().
- _bound_signatures = set()
- def __init__(self, name, opt_signature=None):
- """Creates a function defined by the EE API.
- Args:
- name: The name of the function.
- opt_signature: The signature of the function. If unspecified,
- looked up dynamically.
- """
- if opt_signature is None:
- opt_signature = ApiFunction.lookup(name).getSignature()
- # The signature of this API function.
- self._signature = copy.deepcopy(opt_signature)
- self._signature['name'] = name
- def __eq__(self, other):
- return (isinstance(other, ApiFunction) and
- self.getSignature() == other.getSignature())
- # For Python 3, __hash__ is needed because __eq__ is defined.
- # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
- def __hash__(self):
- return hash(computedobject.ComputedObject.freeze(self.getSignature()))
- def __ne__(self, other):
- return not self.__eq__(other)
- @classmethod
- def call_(cls, name, *args, **kwargs):
- """Call a named API function with positional and keyword arguments.
- Args:
- name: The name of the API function to call.
- *args: Positional arguments to pass to the function.
- **kwargs: Keyword arguments to pass to the function.
- Returns:
- An object representing the called function. If the signature specifies
- a recognized return type, the returned value will be cast to that type.
- """
- return cls.lookup(name).call(*args, **kwargs)
- @classmethod
- def apply_(cls, name, named_args):
- """Call a named API function with a dictionary of named arguments.
- Args:
- name: The name of the API function to call.
- named_args: A dictionary of arguments to the function.
- Returns:
- An object representing the called function. If the signature specifies
- a recognized return type, the returned value will be cast to that type.
- """
- return cls.lookup(name).apply(named_args)
- def encode_invocation(self, unused_encoder):
- return self._signature['name']
- def encode_cloud_invocation(self, unused_encoder):
- return {'functionName': self._signature['name']}
- def getSignature(self):
- """Returns a description of the interface provided by this function."""
- return self._signature
- @classmethod
- def allSignatures(cls):
- """Returns a map from the name to signature for all API functions."""
- cls.initialize()
- return dict([(name, func.getSignature())
- for name, func in cls._api.items()])
- @classmethod
- def unboundFunctions(cls):
- """Returns the functions that have not been bound using importApi() yet."""
- cls.initialize()
- return dict([(name, func) for name, func in cls._api.items()
- if name not in cls._bound_signatures])
- @classmethod
- def lookup(cls, name):
- """Looks up an API function by name.
- Args:
- name: The name of the function to get.
- Returns:
- The requested ApiFunction.
- """
- result = cls.lookupInternal(name)
- if not name:
- raise ee_exception.EEException(
- 'Unknown built-in function name: %s' % name)
- return result
- @classmethod
- def lookupInternal(cls, name):
- """Looks up an API function by name.
- Args:
- name: The name of the function to get.
- Returns:
- The requested ApiFunction or None if not found.
- """
- cls.initialize()
- return cls._api.get(name, None)
- @classmethod
- def initialize(cls):
- """Initializes the list of signatures from the Earth Engine front-end."""
- if not cls._api:
- signatures = data.getAlgorithms()
- api = {}
- for name, sig in signatures.items():
- # Strip type parameters.
- sig['returns'] = re.sub('<.*>', '', sig['returns'])
- for arg in sig['args']:
- arg['type'] = re.sub('<.*>', '', arg['type'])
- api[name] = cls(name, sig)
- cls._api = api
- @classmethod
- def reset(cls):
- """Clears the API functions list so it will be reloaded from the server."""
- cls._api = None
- cls._bound_signatures = set()
- @classmethod
- def importApi(cls, target, prefix, type_name, opt_prepend=None):
- """Adds all API functions that begin with a given prefix to a target class.
- Args:
- target: The class to add to.
- prefix: The prefix to search for in the signatures.
- type_name: The name of the object's type. Functions whose
- first argument matches this type are bound as instance methods, and
- those whose first argument doesn't match are bound as static methods.
- opt_prepend: An optional string to prepend to the names of the
- added functions.
- """
- cls.initialize()
- prepend = opt_prepend or ''
- for name, api_func in cls._api.items():
- parts = name.split('.')
- if len(parts) == 2 and parts[0] == prefix:
- fname = prepend + parts[1]
- signature = api_func.getSignature()
- cls._bound_signatures.add(name)
- # Specifically handle the function names that are illegal in python.
- if keyword.iskeyword(fname):
- fname = fname.title()
- # Don't overwrite existing versions of this function.
- if (hasattr(target, fname) and
- not hasattr(getattr(target, fname), 'signature')):
- continue
- # Create a new function so we can attach properties to it.
- def MakeBoundFunction(func):
- # We need the lambda to capture "func" from the enclosing scope.
- return lambda *args, **kwargs: func.call(*args, **kwargs) # pylint: disable=unnecessary-lambda
- bound_function = MakeBoundFunction(api_func)
- # Add docs. If there are non-ASCII characters in the docs, and we're in
- # Python 2, use a hammer to force them into a str.
- try:
- setattr(bound_function, '__name__', str(name))
- except TypeError:
- setattr(bound_function, '__name__', name.encode('utf8'))
- try:
- bound_function.__doc__ = str(api_func)
- except UnicodeEncodeError:
- bound_function.__doc__ = api_func.__str__().encode('utf8')
- # Attach the signature object for documentation generators.
- bound_function.signature = signature
- # Mark as deprecated if needed.
- if signature.get('deprecated'):
- deprecated_decorator = deprecation.Deprecated(signature['deprecated'])
- bound_function = deprecated_decorator(bound_function)
- # Mark as preview if needed.
- if signature.get('preview'):
- bound_function.__doc__ += (
- '\nPREVIEW: This function is preview or internal only.')
- # Decide whether this is a static or an instance function.
- is_instance = (signature['args'] and
- ee_types.isSubtype(signature['args'][0]['type'],
- type_name))
- if not is_instance:
- bound_function = staticmethod(bound_function)
- # Attach the function as a method.
- setattr(target, fname, bound_function)
- @staticmethod
- def clearApi(target):
- """Removes all methods added by importApi() from a target class.
- Args:
- target: The class to remove from.
- """
- for attr_name in dir(target):
- attr_value = getattr(target, attr_name)
- if callable(attr_value) and hasattr(attr_value, 'signature'):
- delattr(target, attr_name)
|