earthengine-api 1.5.13rc0__py3-none-any.whl → 1.7.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of earthengine-api might be problematic. Click here for more details.
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/METADATA +3 -3
- earthengine_api-1.7.4.dist-info/RECORD +109 -0
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/WHEEL +1 -1
- ee/__init__.py +29 -28
- ee/_arg_types.py +7 -6
- ee/_cloud_api_utils.py +95 -78
- ee/_helpers.py +17 -13
- ee/_state.py +105 -0
- ee/_utils.py +2 -1
- ee/apifunction.py +21 -19
- ee/apitestcase.py +33 -38
- ee/batch.py +87 -77
- ee/blob.py +10 -12
- ee/classifier.py +57 -59
- ee/cli/commands.py +178 -114
- ee/cli/eecli.py +1 -1
- ee/cli/utils.py +61 -42
- ee/clusterer.py +39 -41
- ee/collection.py +64 -54
- ee/computedobject.py +19 -16
- ee/confusionmatrix.py +9 -9
- ee/customfunction.py +13 -12
- ee/data.py +220 -322
- ee/daterange.py +10 -10
- ee/deprecation.py +21 -13
- ee/deserializer.py +25 -20
- ee/dictionary.py +11 -11
- ee/ee_array.py +22 -20
- ee/ee_date.py +23 -23
- ee/ee_list.py +15 -16
- ee/ee_number.py +11 -21
- ee/ee_string.py +24 -32
- ee/ee_types.py +4 -4
- ee/element.py +15 -15
- ee/encodable.py +7 -4
- ee/errormargin.py +4 -4
- ee/feature.py +68 -71
- ee/featurecollection.py +41 -40
- ee/filter.py +90 -92
- ee/function.py +8 -8
- ee/geometry.py +95 -93
- ee/image.py +238 -236
- ee/image_converter.py +4 -4
- ee/imagecollection.py +30 -27
- ee/join.py +13 -15
- ee/kernel.py +55 -57
- ee/mapclient.py +9 -9
- ee/model.py +29 -31
- ee/oauth.py +76 -63
- ee/pixeltype.py +6 -6
- ee/projection.py +5 -4
- ee/reducer.py +41 -41
- ee/serializer.py +14 -14
- ee/table_converter.py +7 -6
- ee/terrain.py +7 -9
- ee/tests/_cloud_api_utils_test.py +21 -6
- ee/tests/_helpers_test.py +57 -4
- ee/tests/_state_test.py +49 -0
- ee/tests/algorithms.json +85 -2
- ee/tests/apifunction_test.py +5 -5
- ee/tests/batch_test.py +135 -57
- ee/tests/blob_test.py +5 -5
- ee/tests/classifier_test.py +3 -3
- ee/tests/clusterer_test.py +3 -3
- ee/tests/collection_test.py +48 -13
- ee/tests/confusionmatrix_test.py +3 -3
- ee/tests/data_test.py +484 -55
- ee/tests/daterange_test.py +4 -4
- ee/tests/deprecation_test.py +6 -4
- ee/tests/deserializer_test.py +64 -5
- ee/tests/dictionary_test.py +12 -12
- ee/tests/ee_array_test.py +3 -3
- ee/tests/ee_date_test.py +4 -4
- ee/tests/ee_list_test.py +3 -3
- ee/tests/ee_number_test.py +75 -30
- ee/tests/ee_string_test.py +11 -3
- ee/tests/ee_test.py +40 -22
- ee/tests/element_test.py +2 -2
- ee/tests/errormargin_test.py +1 -1
- ee/tests/feature_test.py +10 -10
- ee/tests/featurecollection_test.py +3 -3
- ee/tests/filter_test.py +4 -4
- ee/tests/function_test.py +5 -5
- ee/tests/geometry_point_test.py +3 -3
- ee/tests/geometry_test.py +93 -52
- ee/tests/image_converter_test.py +1 -3
- ee/tests/image_test.py +3 -3
- ee/tests/imagecollection_test.py +3 -3
- ee/tests/join_test.py +3 -3
- ee/tests/kernel_test.py +7 -3
- ee/tests/model_test.py +17 -5
- ee/tests/oauth_test.py +189 -7
- ee/tests/pixeltype_test.py +6 -7
- ee/tests/projection_test.py +5 -6
- ee/tests/reducer_test.py +16 -3
- ee/tests/serializer_test.py +39 -12
- ee/tests/table_converter_test.py +51 -7
- ee/tests/terrain_test.py +11 -3
- earthengine_api-1.5.13rc0.dist-info/RECORD +0 -107
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/entry_points.txt +0 -0
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/licenses/LICENSE +0 -0
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/top_level.txt +0 -0
ee/_cloud_api_utils.py
CHANGED
|
@@ -6,12 +6,13 @@ parameters and result values.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import calendar
|
|
9
|
+
from collections.abc import Callable, Sequence
|
|
9
10
|
import copy
|
|
10
11
|
import datetime
|
|
11
12
|
import json
|
|
12
13
|
import os
|
|
13
14
|
import re
|
|
14
|
-
from typing import Any
|
|
15
|
+
from typing import Any
|
|
15
16
|
import warnings
|
|
16
17
|
|
|
17
18
|
import google_auth_httplib2
|
|
@@ -23,6 +24,7 @@ import requests
|
|
|
23
24
|
|
|
24
25
|
from ee import ee_exception
|
|
25
26
|
|
|
27
|
+
|
|
26
28
|
# The Cloud API version.
|
|
27
29
|
VERSION = os.environ.get('EE_CLOUD_API_VERSION', 'v1')
|
|
28
30
|
|
|
@@ -34,17 +36,24 @@ ASSET_NAME_PATTERN = (r'^projects/((?:\w+(?:[\w\-]+\.[\w\-]+)*?\.\w+\:)?'
|
|
|
34
36
|
ASSET_ROOT_PATTERN = (r'^projects/((?:\w+(?:[\w\-]+\.[\w\-]+)*?\.\w+\:)?'
|
|
35
37
|
r'[a-z][a-z0-9\-]{4,28}[a-z0-9])/assets/?$')
|
|
36
38
|
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
+
# Conversion from task state to operation state.
|
|
40
|
+
TASK_TO_OPERATION_STATE = {
|
|
41
|
+
'READY': 'PENDING',
|
|
42
|
+
'RUNNING': 'RUNNING',
|
|
43
|
+
'CANCEL_REQUESTED': 'CANCELLING',
|
|
44
|
+
'COMPLETED': 'SUCCEEDED',
|
|
45
|
+
'CANCELLED': 'CANCELLED',
|
|
46
|
+
'FAILED': 'FAILED',
|
|
47
|
+
}
|
|
39
48
|
|
|
40
49
|
|
|
41
50
|
class _Http:
|
|
42
51
|
"""A httplib2.Http-like object based on requests."""
|
|
43
52
|
_session: requests.Session
|
|
44
|
-
_timeout:
|
|
53
|
+
_timeout: float | None
|
|
45
54
|
|
|
46
55
|
def __init__(
|
|
47
|
-
self, session: requests.Session, timeout:
|
|
56
|
+
self, session: requests.Session, timeout: float | None = None
|
|
48
57
|
):
|
|
49
58
|
self._timeout = timeout
|
|
50
59
|
self._session = session
|
|
@@ -53,11 +62,11 @@ class _Http:
|
|
|
53
62
|
self,
|
|
54
63
|
uri: str,
|
|
55
64
|
method: str = 'GET',
|
|
56
|
-
body:
|
|
57
|
-
headers:
|
|
58
|
-
redirections:
|
|
59
|
-
connection_type:
|
|
60
|
-
) ->
|
|
65
|
+
body: str | None = None,
|
|
66
|
+
headers: dict[str, str] | None = None,
|
|
67
|
+
redirections: int | None = None,
|
|
68
|
+
connection_type: type[Any] | None = None,
|
|
69
|
+
) -> tuple[httplib2.Response, Any]:
|
|
61
70
|
"""Makes an HTTP request using httplib2 semantics."""
|
|
62
71
|
del connection_type # Ignored
|
|
63
72
|
del redirections # Ignored
|
|
@@ -84,7 +93,7 @@ class _Http:
|
|
|
84
93
|
|
|
85
94
|
|
|
86
95
|
def _wrap_request(
|
|
87
|
-
headers_supplier: Callable[[],
|
|
96
|
+
headers_supplier: Callable[[], dict[str, Any]],
|
|
88
97
|
response_inspector: Callable[[Any], None],
|
|
89
98
|
) -> Callable[..., http.HttpRequest]:
|
|
90
99
|
"""Builds a callable that wraps an API request.
|
|
@@ -109,10 +118,10 @@ def _wrap_request(
|
|
|
109
118
|
postproc: Callable[..., Any],
|
|
110
119
|
uri: str,
|
|
111
120
|
method: str = 'GET',
|
|
112
|
-
body:
|
|
113
|
-
headers:
|
|
114
|
-
methodId:
|
|
115
|
-
resumable:
|
|
121
|
+
body: Any | None = None,
|
|
122
|
+
headers: Any | None = None,
|
|
123
|
+
methodId: Any | None = None,
|
|
124
|
+
resumable: Any | None = None,
|
|
116
125
|
) -> http.HttpRequest:
|
|
117
126
|
"""Builds an HttpRequest, adding headers and response inspection."""
|
|
118
127
|
additional_headers = headers_supplier()
|
|
@@ -135,22 +144,17 @@ def _wrap_request(
|
|
|
135
144
|
return builder
|
|
136
145
|
|
|
137
146
|
|
|
138
|
-
def set_cloud_api_user_project(cloud_api_user_project: str) -> None:
|
|
139
|
-
global _cloud_api_user_project
|
|
140
|
-
_cloud_api_user_project = cloud_api_user_project
|
|
141
|
-
|
|
142
|
-
|
|
143
147
|
def build_cloud_resource(
|
|
144
148
|
api_base_url: str,
|
|
145
149
|
session: requests.Session,
|
|
146
|
-
api_key:
|
|
147
|
-
credentials:
|
|
148
|
-
timeout:
|
|
150
|
+
api_key: str | None = None,
|
|
151
|
+
credentials: Any | None = None,
|
|
152
|
+
timeout: float | None = None,
|
|
149
153
|
num_retries: int = 1,
|
|
150
|
-
headers_supplier:
|
|
151
|
-
response_inspector:
|
|
152
|
-
http_transport:
|
|
153
|
-
raw:
|
|
154
|
+
headers_supplier: Callable[[], dict[str, Any]] | None = None,
|
|
155
|
+
response_inspector: Callable[[Any], None] | None = None,
|
|
156
|
+
http_transport: Any | None = None,
|
|
157
|
+
raw: bool | None = False,
|
|
154
158
|
) -> Any:
|
|
155
159
|
"""Builds an Earth Engine Cloud API resource.
|
|
156
160
|
|
|
@@ -222,9 +226,9 @@ def build_cloud_resource(
|
|
|
222
226
|
|
|
223
227
|
def build_cloud_resource_from_document(
|
|
224
228
|
discovery_document: Any,
|
|
225
|
-
http_transport:
|
|
226
|
-
headers_supplier:
|
|
227
|
-
response_inspector:
|
|
229
|
+
http_transport: httplib2.Http | None = None,
|
|
230
|
+
headers_supplier: Callable[..., Any] | None = None,
|
|
231
|
+
response_inspector: Callable[..., Any] | None = None,
|
|
228
232
|
raw: bool = False,
|
|
229
233
|
) -> discovery.Resource:
|
|
230
234
|
"""Builds an Earth Engine Cloud API resource from a description of the API.
|
|
@@ -256,12 +260,12 @@ def build_cloud_resource_from_document(
|
|
|
256
260
|
|
|
257
261
|
|
|
258
262
|
def _convert_dict(
|
|
259
|
-
to_convert:
|
|
260
|
-
conversions:
|
|
261
|
-
defaults:
|
|
263
|
+
to_convert: dict[str, Any],
|
|
264
|
+
conversions: dict[str, Any],
|
|
265
|
+
defaults: dict[str, Any] | None = None,
|
|
262
266
|
key_warnings: bool = False,
|
|
263
267
|
retain_keys: bool = False,
|
|
264
|
-
) ->
|
|
268
|
+
) -> dict[str, Any]:
|
|
265
269
|
"""Applies a set of conversion rules to a dict.
|
|
266
270
|
|
|
267
271
|
Args:
|
|
@@ -287,14 +291,14 @@ def _convert_dict(
|
|
|
287
291
|
not contain these keys.
|
|
288
292
|
key_warnings: Whether to print warnings for input keys that are not mapped
|
|
289
293
|
to anything in the output.
|
|
290
|
-
retain_keys: Whether or not to retain the state of dict.
|
|
294
|
+
retain_keys: Whether or not to retain the state of dict. If false, any keys
|
|
291
295
|
that don't show up in the conversions dict will be dropped from result.
|
|
292
296
|
|
|
293
297
|
Returns:
|
|
294
298
|
The "to_convert" dict with keys renamed, values converted, and defaults
|
|
295
299
|
added.
|
|
296
300
|
"""
|
|
297
|
-
result:
|
|
301
|
+
result: dict[str, Any] = {}
|
|
298
302
|
for key, value in to_convert.items():
|
|
299
303
|
if key in conversions:
|
|
300
304
|
conversion = conversions[key]
|
|
@@ -319,7 +323,7 @@ def _convert_dict(
|
|
|
319
323
|
|
|
320
324
|
|
|
321
325
|
def _convert_value(
|
|
322
|
-
value: str, conversions:
|
|
326
|
+
value: str, conversions: dict[str, Any], default: Any) -> Any:
|
|
323
327
|
"""Converts a value using a set of value mappings.
|
|
324
328
|
|
|
325
329
|
Args:
|
|
@@ -382,7 +386,7 @@ def _convert_bounding_box_to_geo_json(bbox: Sequence[float]) -> str:
|
|
|
382
386
|
lng_min, lat_min, lng_max, lat_max))
|
|
383
387
|
|
|
384
388
|
|
|
385
|
-
def convert_get_list_params_to_list_assets_params(params) ->
|
|
389
|
+
def convert_get_list_params_to_list_assets_params(params) -> dict[str, Any]:
|
|
386
390
|
"""Converts a getList params dict to something usable with listAssets."""
|
|
387
391
|
params = _convert_dict(
|
|
388
392
|
params, {
|
|
@@ -401,7 +405,7 @@ def convert_get_list_params_to_list_assets_params(params) -> Dict[str, Any]:
|
|
|
401
405
|
return convert_list_images_params_to_list_assets_params(params)
|
|
402
406
|
|
|
403
407
|
|
|
404
|
-
def convert_list_assets_result_to_get_list_result(result) ->
|
|
408
|
+
def convert_list_assets_result_to_get_list_result(result) -> list[Any]:
|
|
405
409
|
"""Converts a listAssets result to something getList can return."""
|
|
406
410
|
if 'assets' not in result:
|
|
407
411
|
return []
|
|
@@ -434,7 +438,7 @@ def _convert_list_images_filter_params_to_list_assets_params(params) -> str:
|
|
|
434
438
|
# query in a set of double quotes. We trivially avoid doubly-escaping the
|
|
435
439
|
# quotes by replacing double quotes with single quotes.
|
|
436
440
|
region = region.replace('"', "'")
|
|
437
|
-
query_strings.append('intersects("{}")'
|
|
441
|
+
query_strings.append(f'intersects("{region}")')
|
|
438
442
|
del params['region']
|
|
439
443
|
if 'properties' in params:
|
|
440
444
|
if isinstance(params['properties'], list) and any(
|
|
@@ -452,8 +456,8 @@ def _convert_list_images_filter_params_to_list_assets_params(params) -> str:
|
|
|
452
456
|
|
|
453
457
|
|
|
454
458
|
def convert_list_images_params_to_list_assets_params(
|
|
455
|
-
params:
|
|
456
|
-
) ->
|
|
459
|
+
params: dict[str, Any]
|
|
460
|
+
) -> dict[str, Any]:
|
|
457
461
|
"""Converts a listImages params dict to something usable with listAssets."""
|
|
458
462
|
params = params.copy()
|
|
459
463
|
extra_filters = _convert_list_images_filter_params_to_list_assets_params(
|
|
@@ -470,14 +474,14 @@ def is_asset_root(asset_name: str) -> bool:
|
|
|
470
474
|
return bool(re.match(ASSET_ROOT_PATTERN, asset_name))
|
|
471
475
|
|
|
472
476
|
|
|
473
|
-
def convert_list_images_result_to_get_list_result(result) ->
|
|
477
|
+
def convert_list_images_result_to_get_list_result(result) -> list[Any]:
|
|
474
478
|
"""Converts a listImages result to something getList can return."""
|
|
475
479
|
if 'images' not in result:
|
|
476
480
|
return []
|
|
477
481
|
return [_convert_image_for_get_list_result(i) for i in result['images']]
|
|
478
482
|
|
|
479
483
|
|
|
480
|
-
def _convert_asset_for_get_list_result(asset) ->
|
|
484
|
+
def _convert_asset_for_get_list_result(asset) -> dict[str, Any]:
|
|
481
485
|
"""Converts an EarthEngineAsset to the format returned by getList."""
|
|
482
486
|
result = _convert_dict(
|
|
483
487
|
asset, {
|
|
@@ -488,7 +492,7 @@ def _convert_asset_for_get_list_result(asset) -> Dict[str, Any]:
|
|
|
488
492
|
return result
|
|
489
493
|
|
|
490
494
|
|
|
491
|
-
def _convert_image_for_get_list_result(asset) ->
|
|
495
|
+
def _convert_image_for_get_list_result(asset) -> dict[str, Any]:
|
|
492
496
|
"""Converts an Image to the format returned by getList."""
|
|
493
497
|
result = _convert_dict(
|
|
494
498
|
asset, {
|
|
@@ -534,12 +538,12 @@ def convert_asset_id_to_asset_name(asset_id: str) -> str:
|
|
|
534
538
|
if re.match(ASSET_NAME_PATTERN, asset_id) or is_asset_root(asset_id):
|
|
535
539
|
return asset_id
|
|
536
540
|
elif asset_id.split('/')[0] in ['users', 'projects']:
|
|
537
|
-
return 'projects/earthengine-legacy/assets/{}'
|
|
541
|
+
return f'projects/earthengine-legacy/assets/{asset_id}'
|
|
538
542
|
else:
|
|
539
|
-
return 'projects/earthengine-public/assets/{}'
|
|
543
|
+
return f'projects/earthengine-public/assets/{asset_id}'
|
|
540
544
|
|
|
541
545
|
|
|
542
|
-
def split_asset_name(asset_name: str) ->
|
|
546
|
+
def split_asset_name(asset_name: str) -> tuple[str, str]:
|
|
543
547
|
"""Splits an asset name into the parent and ID parts.
|
|
544
548
|
|
|
545
549
|
Args:
|
|
@@ -559,12 +563,12 @@ def convert_operation_name_to_task_id(operation_name: str) -> str:
|
|
|
559
563
|
return found.group(1) if found else operation_name
|
|
560
564
|
|
|
561
565
|
|
|
562
|
-
def convert_task_id_to_operation_name(task_id: str) -> str:
|
|
566
|
+
def convert_task_id_to_operation_name(project: str, task_id: str) -> str:
|
|
563
567
|
"""Converts a task ID to an Operation name."""
|
|
564
|
-
return 'projects/{}/operations/{}'
|
|
568
|
+
return f'projects/{project}/operations/{task_id}'
|
|
565
569
|
|
|
566
570
|
|
|
567
|
-
def convert_params_to_image_manifest(params:
|
|
571
|
+
def convert_params_to_image_manifest(params: dict[str, Any]) -> dict[str, Any]:
|
|
568
572
|
"""Converts params to an ImageManifest for ingestion."""
|
|
569
573
|
return _convert_dict(
|
|
570
574
|
params, {
|
|
@@ -574,7 +578,7 @@ def convert_params_to_image_manifest(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
574
578
|
retain_keys=True)
|
|
575
579
|
|
|
576
580
|
|
|
577
|
-
def convert_params_to_table_manifest(params:
|
|
581
|
+
def convert_params_to_table_manifest(params: dict[str, Any]) -> dict[str, Any]:
|
|
578
582
|
"""Converts params to a TableManifest for ingestion."""
|
|
579
583
|
return _convert_dict(
|
|
580
584
|
params, {
|
|
@@ -584,7 +588,7 @@ def convert_params_to_table_manifest(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
584
588
|
retain_keys=True)
|
|
585
589
|
|
|
586
590
|
|
|
587
|
-
def convert_tilesets_to_one_platform_tilesets(tilesets:
|
|
591
|
+
def convert_tilesets_to_one_platform_tilesets(tilesets: list[Any]) -> list[Any]:
|
|
588
592
|
"""Converts a tileset to a one platform representation of a tileset."""
|
|
589
593
|
converted_tilesets = []
|
|
590
594
|
for tileset in tilesets:
|
|
@@ -596,7 +600,7 @@ def convert_tilesets_to_one_platform_tilesets(tilesets: List[Any]) -> List[Any]:
|
|
|
596
600
|
return converted_tilesets
|
|
597
601
|
|
|
598
602
|
|
|
599
|
-
def convert_sources_to_one_platform_sources(sources:
|
|
603
|
+
def convert_sources_to_one_platform_sources(sources: list[Any]) -> list[Any]:
|
|
600
604
|
"""Converts the sources to one platform representation of sources."""
|
|
601
605
|
converted_sources = []
|
|
602
606
|
for source in sources:
|
|
@@ -615,7 +619,7 @@ def convert_sources_to_one_platform_sources(sources: List[Any]) -> List[Any]:
|
|
|
615
619
|
return converted_sources
|
|
616
620
|
|
|
617
621
|
|
|
618
|
-
def encode_number_as_cloud_value(number: float) ->
|
|
622
|
+
def encode_number_as_cloud_value(number: float) -> dict[str, float | str]:
|
|
619
623
|
# Numeric values in constantValue-style nodes end up stored in doubles. If the
|
|
620
624
|
# input is an integer that loses precision as a double, use the int64 slot
|
|
621
625
|
# ("integerValue") in ValueNode.
|
|
@@ -625,7 +629,7 @@ def encode_number_as_cloud_value(number: float) -> Dict[str, Union[float, str]]:
|
|
|
625
629
|
return {'constantValue': number}
|
|
626
630
|
|
|
627
631
|
|
|
628
|
-
def convert_algorithms(algorithms) ->
|
|
632
|
+
def convert_algorithms(algorithms) -> dict[str, Any]:
|
|
629
633
|
"""Converts a ListAlgorithmsResult to the internal format.
|
|
630
634
|
|
|
631
635
|
The internal code expects a dict mapping each algorithm's name to a dict
|
|
@@ -653,7 +657,7 @@ def convert_algorithms(algorithms) -> Dict[str, Any]:
|
|
|
653
657
|
return dict(_convert_algorithm(algorithm) for algorithm in algs)
|
|
654
658
|
|
|
655
659
|
|
|
656
|
-
def _convert_algorithm(algorithm:
|
|
660
|
+
def _convert_algorithm(algorithm: dict[str, Any]) -> tuple[str, dict[str, Any]]:
|
|
657
661
|
"""Converts an Algorithm to the internal format."""
|
|
658
662
|
# Strip leading 'algorithms/' from the name.
|
|
659
663
|
algorithm_name = algorithm['name'][11:]
|
|
@@ -677,11 +681,11 @@ def _convert_algorithm(algorithm: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
|
|
|
677
681
|
|
|
678
682
|
|
|
679
683
|
def _convert_algorithm_arguments(
|
|
680
|
-
args:
|
|
684
|
+
args: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
681
685
|
return [_convert_algorithm_argument(arg) for arg in args]
|
|
682
686
|
|
|
683
687
|
|
|
684
|
-
def _convert_algorithm_argument(arg:
|
|
688
|
+
def _convert_algorithm_argument(arg: dict[str, Any]) -> dict[str, Any]:
|
|
685
689
|
return _convert_dict(
|
|
686
690
|
arg, {
|
|
687
691
|
'argumentName': 'name',
|
|
@@ -696,7 +700,7 @@ def _convert_algorithm_argument(arg: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
696
700
|
})
|
|
697
701
|
|
|
698
702
|
|
|
699
|
-
def convert_to_image_file_format(format_str:
|
|
703
|
+
def convert_to_image_file_format(format_str: str | None) -> str:
|
|
700
704
|
"""Converts a legacy file format string to an ImageFileFormat enum value.
|
|
701
705
|
|
|
702
706
|
Args:
|
|
@@ -723,7 +727,7 @@ def convert_to_image_file_format(format_str: Optional[str]) -> str:
|
|
|
723
727
|
return format_str
|
|
724
728
|
|
|
725
729
|
|
|
726
|
-
def convert_to_table_file_format(format_str:
|
|
730
|
+
def convert_to_table_file_format(format_str: str | None) -> str:
|
|
727
731
|
"""Converts a legacy file format string to a TableFileFormat enum value.
|
|
728
732
|
|
|
729
733
|
Args:
|
|
@@ -746,7 +750,7 @@ def convert_to_table_file_format(format_str: Optional[str]) -> str:
|
|
|
746
750
|
return format_str
|
|
747
751
|
|
|
748
752
|
|
|
749
|
-
def convert_to_band_list(bands:
|
|
753
|
+
def convert_to_band_list(bands: list[str] | None | str) -> list[str]:
|
|
750
754
|
"""Converts a band list, possibly as CSV, to a real list of bands.
|
|
751
755
|
|
|
752
756
|
Args:
|
|
@@ -766,7 +770,7 @@ def convert_to_band_list(bands: Union[List[str], None, str]) -> List[str]:
|
|
|
766
770
|
raise ee_exception.EEException('Invalid band list ' + bands)
|
|
767
771
|
|
|
768
772
|
|
|
769
|
-
def convert_to_visualization_options(params:
|
|
773
|
+
def convert_to_visualization_options(params: dict[str, Any]) -> dict[str, Any]:
|
|
770
774
|
"""Extracts a VisualizationOptions from a param dict.
|
|
771
775
|
|
|
772
776
|
Args:
|
|
@@ -829,14 +833,14 @@ def convert_to_visualization_options(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
829
833
|
return result
|
|
830
834
|
|
|
831
835
|
|
|
832
|
-
def _convert_csv_numbers_to_list(value: str) ->
|
|
836
|
+
def _convert_csv_numbers_to_list(value: str) -> list[float]:
|
|
833
837
|
"""Converts a string containing CSV numbers to a list."""
|
|
834
838
|
if not value:
|
|
835
839
|
return []
|
|
836
840
|
return [float(x) for x in value.split(',')]
|
|
837
841
|
|
|
838
842
|
|
|
839
|
-
def convert_operation_to_task(operation:
|
|
843
|
+
def convert_operation_to_task(operation: dict[str, Any]) -> dict[str, Any]:
|
|
840
844
|
"""Converts an Operation to a legacy Task."""
|
|
841
845
|
result = _convert_dict(
|
|
842
846
|
operation['metadata'], {
|
|
@@ -860,19 +864,32 @@ def convert_operation_to_task(operation: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
860
864
|
|
|
861
865
|
|
|
862
866
|
def _convert_operation_state_to_task_state(state: str) -> str:
|
|
863
|
-
"""Converts
|
|
867
|
+
"""Converts an Operation state to a Task state."""
|
|
864
868
|
return _convert_value(
|
|
865
|
-
state,
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
869
|
+
state,
|
|
870
|
+
{value: key for key, value in TASK_TO_OPERATION_STATE.items()},
|
|
871
|
+
'UNKNOWN',
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def _convert_task_state_to_operation_state(state: str) -> str:
|
|
876
|
+
"""Converts a Task state to an Operation state."""
|
|
877
|
+
return _convert_value(state, TASK_TO_OPERATION_STATE, 'UNKNOWN')
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def convert_to_operation_state(state: str) -> str:
|
|
881
|
+
"""Converts a Task state or an Operation state to an Operation state."""
|
|
882
|
+
# First, try converting the state assuming it's a task state.
|
|
883
|
+
operation_state = _convert_task_state_to_operation_state(state)
|
|
884
|
+
if operation_state != 'UNKNOWN':
|
|
885
|
+
return operation_state
|
|
886
|
+
|
|
887
|
+
# If it wasn't a task state, check if the input is a valid operation state.
|
|
888
|
+
valid_operation_states = set(TASK_TO_OPERATION_STATE.values())
|
|
889
|
+
return state if state in valid_operation_states else 'UNKNOWN'
|
|
873
890
|
|
|
874
891
|
|
|
875
|
-
def convert_iam_policy_to_acl(policy:
|
|
892
|
+
def convert_iam_policy_to_acl(policy: dict[str, Any]) -> dict[str, Any]:
|
|
876
893
|
"""Converts an IAM Policy proto to the legacy ACL format."""
|
|
877
894
|
bindings = {
|
|
878
895
|
binding['role']: binding.get('members', [])
|
|
@@ -892,7 +909,7 @@ def convert_iam_policy_to_acl(policy: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
892
909
|
return result
|
|
893
910
|
|
|
894
911
|
|
|
895
|
-
def convert_acl_to_iam_policy(acl:
|
|
912
|
+
def convert_acl_to_iam_policy(acl: dict[str, Any]) -> dict[str, Any]:
|
|
896
913
|
"""Converts the legacy ACL format to an IAM Policy proto."""
|
|
897
914
|
owners = acl.get('owners', [])
|
|
898
915
|
readers = acl.get('readers', [])
|
|
@@ -910,8 +927,8 @@ def convert_acl_to_iam_policy(acl: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
910
927
|
|
|
911
928
|
|
|
912
929
|
def convert_to_grid_dimensions(
|
|
913
|
-
dimensions:
|
|
914
|
-
) ->
|
|
930
|
+
dimensions: float | Sequence[float]
|
|
931
|
+
) -> dict[str, float]:
|
|
915
932
|
"""Converts an input value to GridDimensions.
|
|
916
933
|
|
|
917
934
|
Args:
|
ee/_helpers.py
CHANGED
|
@@ -7,10 +7,11 @@ referenced from there (e.g., "ee.profilePrinting").
|
|
|
7
7
|
# Using lowercase function naming to match the JavaScript names.
|
|
8
8
|
# pylint: disable=g-bad-name
|
|
9
9
|
|
|
10
|
+
from collections.abc import Iterator
|
|
10
11
|
import contextlib
|
|
11
12
|
import json
|
|
12
13
|
import sys
|
|
13
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, TextIO
|
|
14
15
|
|
|
15
16
|
from google.auth import crypt
|
|
16
17
|
from google.oauth2 import service_account
|
|
@@ -21,13 +22,14 @@ from ee import data
|
|
|
21
22
|
from ee import ee_exception
|
|
22
23
|
from ee import oauth
|
|
23
24
|
|
|
24
|
-
|
|
25
25
|
# Number of times to retry fetching profile data.
|
|
26
26
|
_PROFILE_RETRIES = 5
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def ServiceAccountCredentials(
|
|
30
|
-
email: str
|
|
30
|
+
email: str | None = None,
|
|
31
|
+
key_file: str | None = None,
|
|
32
|
+
key_data: str | None = None,
|
|
31
33
|
) -> service_account.Credentials:
|
|
32
34
|
"""Configure OAuth2 credentials for a Google Service Account.
|
|
33
35
|
|
|
@@ -41,6 +43,10 @@ def ServiceAccountCredentials(
|
|
|
41
43
|
Returns:
|
|
42
44
|
An OAuth2 credentials object.
|
|
43
45
|
"""
|
|
46
|
+
if not email and not key_file and not key_data:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
'At least one of email, key_file, or key_data must be specified.'
|
|
49
|
+
)
|
|
44
50
|
|
|
45
51
|
# Assume anything that doesn't end in '.pem' is a JSON key.
|
|
46
52
|
if key_file and not key_file.endswith('.pem'):
|
|
@@ -59,7 +65,7 @@ def ServiceAccountCredentials(
|
|
|
59
65
|
|
|
60
66
|
# Probably a PEM key - just read the file into 'key_data'.
|
|
61
67
|
if key_file:
|
|
62
|
-
with open(key_file
|
|
68
|
+
with open(key_file) as file_:
|
|
63
69
|
key_data = file_.read()
|
|
64
70
|
|
|
65
71
|
# Raw PEM key.
|
|
@@ -69,7 +75,7 @@ def ServiceAccountCredentials(
|
|
|
69
75
|
|
|
70
76
|
|
|
71
77
|
def call(
|
|
72
|
-
func:
|
|
78
|
+
func: str | apifunction.ApiFunction, *args, **kwargs
|
|
73
79
|
) -> computedobject.ComputedObject:
|
|
74
80
|
"""Invoke the given algorithm with the specified args.
|
|
75
81
|
|
|
@@ -91,7 +97,7 @@ def call(
|
|
|
91
97
|
|
|
92
98
|
# pylint: disable-next=redefined-builtin
|
|
93
99
|
def apply(
|
|
94
|
-
func:
|
|
100
|
+
func: str | apifunction.ApiFunction, named_args: dict[str, Any]
|
|
95
101
|
) -> computedobject.ComputedObject:
|
|
96
102
|
"""Call a function with a dictionary of named arguments.
|
|
97
103
|
|
|
@@ -118,14 +124,13 @@ def profilePrinting(destination: TextIO = sys.stderr) -> Iterator[None]:
|
|
|
118
124
|
The profile will be printed when the context ends, whether or not any error
|
|
119
125
|
occurred within the context.
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
Example:
|
|
128
|
+
with ee.profilePrinting():
|
|
129
|
+
print(ee.Number(1).add(1).getInfo())
|
|
124
130
|
|
|
125
131
|
Args:
|
|
126
132
|
destination: A file-like object to which the profile text is written.
|
|
127
133
|
Defaults to sys.stderr.
|
|
128
|
-
|
|
129
134
|
"""
|
|
130
135
|
# Profile.getProfiles is `hidden`, so call it explicitly.
|
|
131
136
|
get_profiles = apifunction.ApiFunction.lookup('Profile.getProfiles').call
|
|
@@ -135,12 +140,11 @@ def profilePrinting(destination: TextIO = sys.stderr) -> Iterator[None]:
|
|
|
135
140
|
yield
|
|
136
141
|
finally:
|
|
137
142
|
# Make several attempts in case of transient errors.
|
|
138
|
-
|
|
139
|
-
for i in range(_PROFILE_RETRIES):
|
|
143
|
+
for attempt in range(_PROFILE_RETRIES):
|
|
140
144
|
try:
|
|
141
145
|
profile_text = get_profiles(ids=profile_ids).getInfo()
|
|
142
146
|
destination.write(profile_text)
|
|
143
147
|
break
|
|
144
148
|
except ee_exception.EEException as exception:
|
|
145
|
-
if
|
|
149
|
+
if attempt == _PROFILE_RETRIES - 1:
|
|
146
150
|
raise exception
|
ee/_state.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Stores the current state of the EE library."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
# Rename to avoid redefined-outer-name warning.
|
|
10
|
+
from google.oauth2 import credentials as credentials_lib
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# The default project to use for Cloud API calls.
|
|
15
|
+
DEFAULT_CLOUD_API_USER_PROJECT = 'earthengine-legacy'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclasses.dataclass()
|
|
19
|
+
class _WorkloadTag:
|
|
20
|
+
"""A helper class to manage the workload tag."""
|
|
21
|
+
|
|
22
|
+
_tag: str
|
|
23
|
+
_default: str
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._tag = ''
|
|
27
|
+
self._default = ''
|
|
28
|
+
|
|
29
|
+
def get(self) -> str:
|
|
30
|
+
return self._tag
|
|
31
|
+
|
|
32
|
+
def set(self, tag: int | str | None) -> None:
|
|
33
|
+
self._tag = self.validate(tag)
|
|
34
|
+
|
|
35
|
+
def set_default(self, new_default: int | str | None) -> None:
|
|
36
|
+
self._default = self.validate(new_default)
|
|
37
|
+
|
|
38
|
+
def reset(self) -> None:
|
|
39
|
+
self._tag = self._default
|
|
40
|
+
|
|
41
|
+
def validate(self, tag: int | str | None) -> str:
|
|
42
|
+
"""Returns the validated tag or raises a ValueError.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
tag: the tag to validate.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError if the tag does not match the expected format.
|
|
49
|
+
"""
|
|
50
|
+
if not tag and tag != 0:
|
|
51
|
+
return ''
|
|
52
|
+
tag = str(tag)
|
|
53
|
+
if not re.fullmatch(r'([a-z0-9]|[a-z0-9][-_a-z0-9]{0,61}[a-z0-9])', tag):
|
|
54
|
+
validation_message = (
|
|
55
|
+
'Tags must be 1-63 characters, '
|
|
56
|
+
'beginning and ending with a lowercase alphanumeric character '
|
|
57
|
+
'([a-z0-9]) with dashes (-), underscores (_), '
|
|
58
|
+
'and lowercase alphanumerics between.'
|
|
59
|
+
)
|
|
60
|
+
raise ValueError(f'Invalid tag, "{tag}". {validation_message}')
|
|
61
|
+
return tag
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclasses.dataclass()
|
|
65
|
+
class EEState:
|
|
66
|
+
"""Holds the configuration and initialized resources for an EE session."""
|
|
67
|
+
|
|
68
|
+
initialized: bool = False
|
|
69
|
+
credentials: credentials_lib.Credentials | None = None
|
|
70
|
+
api_base_url: str | None = None
|
|
71
|
+
tile_base_url: str | None = None
|
|
72
|
+
cloud_api_base_url: str | None = None
|
|
73
|
+
cloud_api_key: str | None = None
|
|
74
|
+
requests_session: requests.Session | None = None
|
|
75
|
+
cloud_api_resource: Any | None = None
|
|
76
|
+
cloud_api_resource_raw: Any | None = None
|
|
77
|
+
cloud_api_user_project: str | None = DEFAULT_CLOUD_API_USER_PROJECT
|
|
78
|
+
cloud_api_client_version: str | None = None
|
|
79
|
+
http_transport: Any | None = None
|
|
80
|
+
deadline_ms: int = 0
|
|
81
|
+
max_retries: int = 5
|
|
82
|
+
user_agent: str | None = None
|
|
83
|
+
workload_tag: _WorkloadTag = dataclasses.field(default_factory=_WorkloadTag)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
_state: EEState | None = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_state() -> EEState:
|
|
90
|
+
"""Retrieves the EEState for the current execution context.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The EEState for the current execution context.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
global _state
|
|
97
|
+
if not _state:
|
|
98
|
+
_state = EEState()
|
|
99
|
+
return _state
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def reset_state() -> None:
|
|
103
|
+
"""Resets the EEState to the default state."""
|
|
104
|
+
global _state
|
|
105
|
+
_state = None
|
ee/_utils.py
CHANGED