diff --git a/javascript/build/ee_api_js.js b/javascript/build/ee_api_js.js index 7bd2276f0..b5f5ff5b3 100644 --- a/javascript/build/ee_api_js.js +++ b/javascript/build/ee_api_js.js @@ -364,8 +364,8 @@ yf.prototype.i=function(){return{P:{ranges:of},keys:["gamma","opacity","paletteC k.Object.defineProperties(yf.prototype,{gamma:{configurable:!0,enumerable:!0,get:function(){return G(this,"gamma")?F(this,"gamma"):null},set:function(a){this.h.gamma=a}},opacity:{configurable:!0,enumerable:!0,get:function(){return G(this,"opacity")?F(this,"opacity"):null},set:function(a){this.h.opacity=a}},Xc:{configurable:!0,enumerable:!0,get:function(){return G(this,"paletteColors")?F(this,"paletteColors"):null},set:function(a){this.h.paletteColors=a}},Wf:{configurable:!0,enumerable:!0,get:function(){return G(this, "ranges")?F(this,"ranges"):null},set:function(a){this.h.ranges=a}}});var Qg=function(a){a=void 0===a?{}:a;this.h={};this.h.start=null==a.start?null:a.start;this.h.end=null==a.end?null:a.end};q(Qg,E);Qg.prototype.i=function(){return{keys:["end","start"]}}; k.Object.defineProperties(Qg.prototype,{end:{configurable:!0,enumerable:!0,get:function(){return G(this,"end")?F(this,"end"):null},set:function(a){this.h.end=a}},start:{configurable:!0,enumerable:!0,get:function(){return G(this,"start")?F(this,"start"):null},set:function(a){this.h.start=a}}}); -var Wd={$Xgafv:"$.xgafv",access_token:"access_token",alt:"alt",assetId:"assetId",billingAccount:"billingAccount",callback:"callback",fields:"fields",filter:"filter",key:"key",oauth_token:"oauth_token",overwrite:"overwrite",pageSize:"pageSize",pageToken:"pageToken",parent:"parent",prettyPrint:"prettyPrint",quotaUser:"quotaUser",region:"region",updateMask:"updateMask",uploadType:"uploadType",upload_protocol:"upload_protocol",view:"view",workloadTag:"workloadTag"},Ug=function(a){this.m="v1";this.j=new Zd(a, -null)};Ug.prototype.list=function(a,b,c){b=void 0===b?{}:b;c=void 0===c?{}:c;this.j.C(a,RegExp("^projects/[^/]+$"));return I(this.j,{body:null,B:"GET",D:"earthengine.projects.algorithms.list",path:"/"+this.m+"/"+a+"/algorithms",u:H(b,c),G:tg})};var Vg=function(a){this.m="v1";this.j=new Zd(a,null)}; +var Wd={$Xgafv:"$.xgafv",access_token:"access_token",alt:"alt",assetId:"assetId",callback:"callback",fields:"fields",filter:"filter",key:"key",oauth_token:"oauth_token",overwrite:"overwrite",pageSize:"pageSize",pageToken:"pageToken",parent:"parent",prettyPrint:"prettyPrint",quotaUser:"quotaUser",region:"region",updateMask:"updateMask",uploadType:"uploadType",upload_protocol:"upload_protocol",view:"view",workloadTag:"workloadTag"},Ug=function(a){this.m="v1";this.j=new Zd(a,null)}; +Ug.prototype.list=function(a,b,c){b=void 0===b?{}:b;c=void 0===c?{}:c;this.j.C(a,RegExp("^projects/[^/]+$"));return I(this.j,{body:null,B:"GET",D:"earthengine.projects.algorithms.list",path:"/"+this.m+"/"+a+"/algorithms",u:H(b,c),G:tg})};var Vg=function(a){this.m="v1";this.j=new Zd(a,null)}; Vg.prototype.getCapabilities=function(a,b,c){b=void 0===b?{}:b;c=void 0===c?{}:c;this.j.C(a,RegExp("^projects/[^/]+$"));return I(this.j,{body:null,B:"GET",D:"earthengine.projects.getCapabilities",path:"/"+this.m+"/"+a+"/capabilities",u:H(b,c),G:Ze})};Vg.prototype.Sd=function(a,b){b=void 0===b?{}:b;var c=void 0===c?{}:c;this.j.C(a,RegExp("^projects/[^/]+$"));return I(this.j,{body:null,B:"GET",D:"earthengine.projects.listAssets",path:"/"+this.m+"/"+a+":listAssets",u:H(b,c),G:ug})}; var Wg=function(a){this.m="v1";this.j=new Zd(a,null)};h=Wg.prototype;h.create=function(a,b,c,d){c=void 0===c?{}:c;d=void 0===d?{}:d;this.j.C(a,RegExp("^projects/[^/]+$"));return I(this.j,{body:b,B:"POST",D:"earthengine.projects.assets.create",path:"/"+this.m+"/"+a+"/assets",u:H(c,d),G:qf})}; h.delete=function(a,b,c){b=void 0===b?{}:b;c=void 0===c?{}:c;this.j.C(a,RegExp("^projects/[^/]+/assets/.*$"));return I(this.j,{body:null,B:"DELETE",D:"earthengine.projects.assets.delete",path:"/"+this.m+"/"+a,u:H(b,c),G:zf})};h.get=function(a,b,c){b=void 0===b?{}:b;c=void 0===c?{}:c;this.j.C(a,RegExp("^projects/[^/]+/assets/.*$"));return I(this.j,{body:null,B:"GET",D:"earthengine.projects.assets.get",path:"/"+this.m+"/"+a,u:H(b,c),G:qf})}; @@ -441,7 +441,7 @@ Uc(p,f[m]))});return b?b(l):l};return this.callback?(uj(d,null,function(f,l){ret vj.prototype.send=function(a,b){var c=[a.B+" "+a.path+" HTTP/1.1"];c.push("Content-Type: application/json; charset=utf-8");var d=yj();null!=d&&c.push("Authorization: "+d);a=a.body?JSON.stringify(a.body):"";return[c.join("\r\n")+"\r\n\r\n"+a,b]}; var zj=function(a,b,c){a=n(b.split("--"+a.split("; boundary=")[1]));for(b=a.next();!b.done;b=a.next())if(b=b.value.split("\r\n\r\n"),!(3>b.length)){var d=b[0].match(/\r\nContent-ID: ]*)>/)[1],e=Number(b[1].match(/^HTTP\S*\s(\d+)\s/)[1]);c(d,e,b.slice(2).join("\r\n\r\n"))}},sj=function(){var a=Aj.replace(/\/api$/,"");return"window"in r&&!a.match(/^https?:\/\/content-/)?a.replace(/^(https?:\/\/)(.*\.googleapis\.com)$/,"$1content-$2"):a},Cj=function(a,b,c){var d=[];a&&(d=d.concat(Bj)); b&&d.push("https://www.googleapis.com/auth/devstorage.read_write");a=d=d.concat(c);c=b=0;for(var e={};cg)break;y++}return Xj(C.status,function(Q){try{return C.getResponseHeader(Q)}catch(oa){return null}},C.responseText,l,void 0,e,d,f)},Vj=function(a,b,c,d,e,g,f){var l=0,m={url:a,method:c,content:d,headers:e},p=Pj,v=null!=g?g:10; m.callback=function(y){y=y.target;if(429==y.getStatus()&&la.y||a.y>=1<, string=)=} opt_callback * An optional callback. If not supplied, the call is made synchronously. - * @return {?Array} The list of writable folders. - * Null if a callback is specified. + * @return {?Array} The list of top-level assets and + * folders. Null if a callback is specified. * @export */ ee.data.getAssetRoots = function(opt_callback) { @@ -1683,13 +1689,14 @@ ee.data.getAssetRoots = function(opt_callback) { /** * Attempts to create a home root folder (e.g. "users/joe") for the current - * user. This results in an error if the user already has a home root folder or - * the requested ID is unavailable. + * user. This results in an error if the user already has a home root folder + * or the requested ID is unavailable. * * @param {string} requestedId The requested ID of the home folder * (e.g. "users/joe"). - * @param {function(?Array, string=)=} opt_callback - * An optional callback. If not supplied, the call is made synchronously. + * @param {function(?Array, string=)=} + * opt_callback An optional callback. If not supplied, the call is made + * synchronously. * @export */ ee.data.createAssetHome = function(requestedId, opt_callback) { diff --git a/python/ee/__init__.py b/python/ee/__init__.py index a3f97916f..4db575c69 100644 --- a/python/ee/__init__.py +++ b/python/ee/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """The EE Python library.""" -__version__ = '0.1.376' +__version__ = '0.1.377' # Using lowercase function naming to match the JavaScript names. # pylint: disable=g-bad-name @@ -13,6 +13,7 @@ import os from typing import Any, Hashable, List as ListType, Optional, Sequence, Type, Union +from ee import _cloud_api_utils from ee import batch from ee import data from ee import deserializer @@ -110,7 +111,7 @@ def Initialize( credentials: Optional[Any] = 'persistent', opt_url: Optional[str] = None, cloud_api_key: Optional[str] = None, - http_transport: Optional[Any] = None, + http_transport: Optional[_cloud_api_utils.HttpTransportable] = None, project: Optional[Union[str, int]] = None, ) -> None: """Initialize the EE library. diff --git a/python/ee/_cloud_api_utils.py b/python/ee/_cloud_api_utils.py index 1587c055b..b54aef1e9 100644 --- a/python/ee/_cloud_api_utils.py +++ b/python/ee/_cloud_api_utils.py @@ -12,9 +12,10 @@ import json import os import re -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Protocol, Sequence, Tuple, Type, Union import warnings +from google import auth import google_auth_httplib2 from googleapiclient import discovery from googleapiclient import http @@ -39,9 +40,24 @@ _cloud_api_user_project: Optional[str] = None +class HttpTransportable(Protocol): + """A protocol for HTTP transport objects.""" + + def request( # pylint: disable=invalid-name + self, + uri: str, + method: str, + body: Optional[str], + headers: Optional[Dict[str, str]], + redirections: Optional[int], + connection_type: Optional[Type[Any]], + ) -> Any: + """Make an HTTP request.""" + + class _Http: """A httplib2.Http-like object based on requests.""" - timeout: Optional[float] + _timeout: Optional[float] def __init__(self, timeout: Optional[float] = None): self._timeout = timeout @@ -128,13 +144,13 @@ def set_cloud_api_user_project(cloud_api_user_project: str) -> None: def build_cloud_resource( api_base_url: str, api_key: Optional[str] = None, - credentials: Optional[Any] = None, + credentials: Optional[auth.credentials.Credentials] = None, timeout: Optional[float] = None, headers_supplier: Optional[Callable[[], Dict[str, Any]]] = None, response_inspector: Optional[Callable[[Any], None]] = None, - http_transport: Optional[Any] = None, + http_transport: Optional[HttpTransportable] = None, raw: Optional[bool] = False, -) -> Any: +) -> discovery.Resource: """Builds an Earth Engine Cloud API resource. Args: diff --git a/python/ee/data.py b/python/ee/data.py index 12b4903ec..97da00879 100644 --- a/python/ee/data.py +++ b/python/ee/data.py @@ -59,7 +59,7 @@ _cloud_api_client_version: Optional[str] = None # The http_transport to use. -_http_transport = None +_http_transport: _cloud_api_utils.HttpTransportable = None # Whether the module has been initialized. _initialized: bool = False @@ -123,6 +123,7 @@ def __init__(self): # The default base URL for API calls. DEFAULT_API_BASE_URL = 'https://earthengine.googleapis.com/api' +HIGH_VOLUME_API_BASE_URL = 'https://earthengine-highvolume.googleapis.com' # The default base URL for media/tile calls. DEFAULT_TILE_BASE_URL = 'https://earthengine.googleapis.com' @@ -156,7 +157,7 @@ def initialize( cloud_api_base_url: Optional[str] = None, cloud_api_key: Optional[str] = None, project: Optional[str] = None, - http_transport: Any = None, + http_transport: Optional[_cloud_api_utils.HttpTransportable] = None, ) -> None: """Initializes the data module, setting credentials and base URLs. @@ -595,6 +596,19 @@ def listAssets(params: Dict[str, Any]) -> Dict[str, List[Any]]: def listBuckets(project: Optional[str] = None) -> Any: + """Returns top-level assets and folders for the Cloud Project or user. + + Args: + project: Project to query, e.g. "projects/my-project". Defaults to current + project. Use "projects/earthengine-legacy" for user home folders. + + Returns: + A dictionary with a list of top-level assets and folders like: + {"assets": [ + {"type": "FOLDER", "id": "projects/my-project/assets/my-folder", ...}, + {"type": "IMAGE", "id": "projects/my-project/assets/my-image", ...}, + ]} + """ if project is None: project = _get_projects_path() return _execute_cloud_call(_get_cloud_projects().listAssets(parent=project)) @@ -1887,16 +1901,18 @@ def startTableIngestion( def getAssetRoots() -> Any: - """Returns the list of the root folders the user owns. + """Returns a list of top-level assets and folders for the current project. - Note: The "id" values for roots are two levels deep, e.g. "users/johndoe" - not "users/johndoe/notaroot". + Note: The "id" values for Cloud Projects are + "projects/my-project/assets/my-asset", where legacy assets (if the + current project is set to "earthengine-legacy") are "users/my-username", + not "users/my-username/my-asset". Returns: - A list of folder descriptions formatted like: + The list of top-level assets and folders like: [ - {"type": "Folder", "id": "users/foo"}, - {"type": "Folder", "id": "projects/bar"}, + {"id": "users/foo", "type": "Folder", ...}, + {"id": "projects/bar", "type": "Folder", ...}, ] """ return _cloud_api_utils.convert_list_assets_result_to_get_list_result( diff --git a/python/ee/tests/algorithms.json b/python/ee/tests/algorithms.json index 64f7d99ff..f16508c90 100644 --- a/python/ee/tests/algorithms.json +++ b/python/ee/tests/algorithms.json @@ -3063,6 +3063,29 @@ "optional": true, "defaultValue": 0.0 }] + }, { + "name": "algorithms/Classifier.smileKNN", + "description": "Creates an empty kNN classifier.\nThe k-nearest neighbor algorithm (k-NN) is a method for classifying objects by a majority vote of its neighbors, with the object being assigned to the class most common amongst its k nearest neighbors (k is a positive integer, typically small, typically odd).", + "returnType": "Classifier", + "arguments": [{ + "argumentName": "k", + "type": "Integer", + "description": "The number of neighbors for classification.", + "optional": true, + "defaultValue": 1.0 + }, { + "argumentName": "searchMethod", + "type": "String", + "description": "Search method. The following are valid [AUTO, LINEAR_SEARCH, KD_TREE, COVER_TREE].\nAUTO Will choose between KD_TREE and COVER_TREE depending on the dimension count. Results may vary between the different search methods for distance ties and probability values. Since performance and results may vary consult with SMILE\u0027s documentation and other literature.", + "optional": true, + "defaultValue": "AUTO" + }, { + "argumentName": "metric", + "type": "String", + "description": "The distance metric to use. NOTE: KD_TREE (and AUTO for low dimensions) will not use the metric selected. Options are:\n \u0027EUCLIDEAN\u0027 - euclidean distance.\n \u0027MAHALANOBIS\u0027 - Mahalanobis distance.\n \u0027MANHATTAN\u0027 - Manhattan distance.", + "optional": true, + "defaultValue": "EUCLIDEAN" + }] }, { "name": "algorithms/Classifier.smileNaiveBayes", "description": "Creates an empty Naive Bayes classifier. This classifier assumes that the feature vector consists of positive integers, and negative inputs are discarded.",