mirror of
https://github.com/google/earthengine-api.git
synced 2025-12-08 19:26:12 +00:00
253 lines
8.2 KiB
Python
253 lines
8.2 KiB
Python
#!/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)
|