earthengine-api 1.6.11__tar.gz → 1.6.12__tar.gz
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.6.11/earthengine_api.egg-info → earthengine_api-1.6.12}/PKG-INFO +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12/earthengine_api.egg-info}/PKG-INFO +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/SOURCES.txt +2 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/__init__.py +5 -5
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_cloud_api_utils.py +33 -10
- earthengine_api-1.6.12/ee/_state.py +105 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/apifunction.py +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/apitestcase.py +15 -21
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/batch.py +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/commands.py +153 -63
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/eecli.py +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/utils.py +25 -15
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/collection.py +27 -18
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/computedobject.py +5 -5
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/customfunction.py +3 -3
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/data.py +104 -210
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_array.py +4 -2
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_number.py +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_string.py +18 -26
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_types.py +2 -2
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/element.py +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/featurecollection.py +10 -7
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/filter.py +2 -2
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/geometry.py +20 -21
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/image.py +7 -12
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/imagecollection.py +3 -3
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/mapclient.py +9 -9
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/oauth.py +13 -6
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/_cloud_api_utils_test.py +16 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/_helpers_test.py +9 -9
- earthengine_api-1.6.12/ee/tests/_state_test.py +49 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/apifunction_test.py +5 -5
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/batch_test.py +61 -50
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/collection_test.py +13 -13
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/data_test.py +65 -60
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/dictionary_test.py +9 -9
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_number_test.py +32 -26
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_string_test.py +8 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_test.py +37 -19
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/element_test.py +2 -2
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/feature_test.py +6 -6
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/function_test.py +5 -5
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/geometry_test.py +73 -51
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/oauth_test.py +21 -2
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/serializer_test.py +8 -8
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/pyproject.toml +1 -1
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/LICENSE +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/MANIFEST.in +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/README.md +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/dependency_links.txt +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/entry_points.txt +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/requires.txt +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/top_level.txt +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_arg_types.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_helpers.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_utils.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/blob.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/classifier.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/__init__.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/eecli_wrapper.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/clusterer.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/confusionmatrix.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/daterange.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/deprecation.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/deserializer.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/dictionary.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_date.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_exception.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_list.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/encodable.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/errormargin.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/feature.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/function.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/image_converter.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/join.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/kernel.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/model.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/pixeltype.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/projection.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/py.typed +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/reducer.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/serializer.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/table_converter.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/terrain.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/_utils_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/algorithms.json +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/blob_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/classifier_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/cloud_api_discovery_document.json +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/clusterer_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/computedobject_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/confusionmatrix_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/daterange_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/deprecation_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/deserializer_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_array_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_date_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_list_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_types_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/errormargin_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/featurecollection_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/filter_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/geometry_point_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/image_converter_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/image_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/imagecollection_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/join_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/kernel_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/model_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/pixeltype_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/projection_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/reducer_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/table_converter_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/terrain_test.py +0 -0
- {earthengine_api-1.6.11 → earthengine_api-1.6.12}/setup.cfg +0 -0
|
@@ -12,6 +12,7 @@ ee/__init__.py
|
|
|
12
12
|
ee/_arg_types.py
|
|
13
13
|
ee/_cloud_api_utils.py
|
|
14
14
|
ee/_helpers.py
|
|
15
|
+
ee/_state.py
|
|
15
16
|
ee/_utils.py
|
|
16
17
|
ee/apifunction.py
|
|
17
18
|
ee/apitestcase.py
|
|
@@ -65,6 +66,7 @@ ee/cli/eecli_wrapper.py
|
|
|
65
66
|
ee/cli/utils.py
|
|
66
67
|
ee/tests/_cloud_api_utils_test.py
|
|
67
68
|
ee/tests/_helpers_test.py
|
|
69
|
+
ee/tests/_state_test.py
|
|
68
70
|
ee/tests/_utils_test.py
|
|
69
71
|
ee/tests/algorithms.json
|
|
70
72
|
ee/tests/apifunction_test.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""The EE Python library."""
|
|
2
2
|
|
|
3
|
-
__version__ = '1.6.
|
|
3
|
+
__version__ = '1.6.12'
|
|
4
4
|
|
|
5
5
|
# Using lowercase function naming to match the JavaScript names.
|
|
6
6
|
# pylint: disable=g-bad-name
|
|
@@ -171,12 +171,12 @@ def Initialize(
|
|
|
171
171
|
"""Initialize the EE library.
|
|
172
172
|
|
|
173
173
|
If this hasn't been called by the time any object constructor is used,
|
|
174
|
-
it will be called then.
|
|
174
|
+
it will be called then. If this is called a second time with a different
|
|
175
175
|
URL, this doesn't do an un-initialization of, e.g., the previously loaded
|
|
176
176
|
Algorithms, but will overwrite them and let point at alternate servers.
|
|
177
177
|
|
|
178
178
|
Args:
|
|
179
|
-
credentials: OAuth2 credentials.
|
|
179
|
+
credentials: OAuth2 credentials. 'persistent' (default) means use
|
|
180
180
|
credentials already stored in the filesystem, or raise an explanatory
|
|
181
181
|
exception guiding the user to create those credentials.
|
|
182
182
|
url: The base url for the EarthEngine REST API to connect to.
|
|
@@ -443,7 +443,7 @@ def _MakeClass(name: str) -> type[Any]:
|
|
|
443
443
|
"""Initializer for dynamically created classes.
|
|
444
444
|
|
|
445
445
|
Args:
|
|
446
|
-
self: The instance of this class.
|
|
446
|
+
self: The instance of this class. Listed to make the linter hush.
|
|
447
447
|
*args: Either a ComputedObject to be promoted to this type, or
|
|
448
448
|
arguments to an algorithm with the same name as this class.
|
|
449
449
|
**kwargs: Any kwargs passed to this class constructor.
|
|
@@ -485,7 +485,7 @@ def _MakeClass(name: str) -> type[Any]:
|
|
|
485
485
|
elif firstArgIsPrimitive:
|
|
486
486
|
# Can't cast a primitive.
|
|
487
487
|
raise EEException(
|
|
488
|
-
'Invalid argument for ee.{}(): {}.
|
|
488
|
+
'Invalid argument for ee.{}(): {}. '
|
|
489
489
|
'Must be a ComputedObject.'.format(name, args))
|
|
490
490
|
|
|
491
491
|
result = args[0]
|
|
@@ -35,6 +35,16 @@ ASSET_NAME_PATTERN = (r'^projects/((?:\w+(?:[\w\-]+\.[\w\-]+)*?\.\w+\:)?'
|
|
|
35
35
|
ASSET_ROOT_PATTERN = (r'^projects/((?:\w+(?:[\w\-]+\.[\w\-]+)*?\.\w+\:)?'
|
|
36
36
|
r'[a-z][a-z0-9\-]{4,28}[a-z0-9])/assets/?$')
|
|
37
37
|
|
|
38
|
+
# Conversion from task state to operation state.
|
|
39
|
+
TASK_TO_OPERATION_STATE = {
|
|
40
|
+
'READY': 'PENDING',
|
|
41
|
+
'RUNNING': 'RUNNING',
|
|
42
|
+
'CANCEL_REQUESTED': 'CANCELLING',
|
|
43
|
+
'COMPLETED': 'SUCCEEDED',
|
|
44
|
+
'CANCELLED': 'CANCELLED',
|
|
45
|
+
'FAILED': 'FAILED',
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
|
|
39
49
|
class _Http:
|
|
40
50
|
"""A httplib2.Http-like object based on requests."""
|
|
@@ -280,7 +290,7 @@ def _convert_dict(
|
|
|
280
290
|
not contain these keys.
|
|
281
291
|
key_warnings: Whether to print warnings for input keys that are not mapped
|
|
282
292
|
to anything in the output.
|
|
283
|
-
retain_keys: Whether or not to retain the state of dict.
|
|
293
|
+
retain_keys: Whether or not to retain the state of dict. If false, any keys
|
|
284
294
|
that don't show up in the conversions dict will be dropped from result.
|
|
285
295
|
|
|
286
296
|
Returns:
|
|
@@ -853,16 +863,29 @@ def convert_operation_to_task(operation: dict[str, Any]) -> dict[str, Any]:
|
|
|
853
863
|
|
|
854
864
|
|
|
855
865
|
def _convert_operation_state_to_task_state(state: str) -> str:
|
|
856
|
-
"""Converts
|
|
866
|
+
"""Converts an Operation state to a Task state."""
|
|
857
867
|
return _convert_value(
|
|
858
|
-
state,
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
868
|
+
state,
|
|
869
|
+
{value: key for key, value in TASK_TO_OPERATION_STATE.items()},
|
|
870
|
+
'UNKNOWN',
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def _convert_task_state_to_operation_state(state: str) -> str:
|
|
875
|
+
"""Converts a Task state to an Operation state."""
|
|
876
|
+
return _convert_value(state, TASK_TO_OPERATION_STATE, 'UNKNOWN')
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def convert_to_operation_state(state: str) -> str:
|
|
880
|
+
"""Converts a Task state or an Operation state to an Operation state."""
|
|
881
|
+
# First, try converting the state assuming it's a task state.
|
|
882
|
+
operation_state = _convert_task_state_to_operation_state(state)
|
|
883
|
+
if operation_state != 'UNKNOWN':
|
|
884
|
+
return operation_state
|
|
885
|
+
|
|
886
|
+
# If it wasn't a task state, check if the input is a valid operation state.
|
|
887
|
+
valid_operation_states = set(TASK_TO_OPERATION_STATE.values())
|
|
888
|
+
return state if state in valid_operation_states else 'UNKNOWN'
|
|
866
889
|
|
|
867
890
|
|
|
868
891
|
def convert_iam_policy_to_acl(policy: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""A class for representing built-in EE API Function.
|
|
2
2
|
|
|
3
3
|
Earth Engine can dynamically produce a JSON array listing the
|
|
4
|
-
algorithms available to the user.
|
|
4
|
+
algorithms available to the user. Each item in the dictionary identifies
|
|
5
5
|
the name and return type of the algorithm, the name and type of its
|
|
6
6
|
arguments, whether they're required or optional, default values and docs
|
|
7
7
|
for each argument and the algorithms as a whole.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable
|
|
4
4
|
import contextlib
|
|
5
|
+
import copy
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
7
8
|
from typing import Any, Optional
|
|
@@ -11,6 +12,7 @@ from googleapiclient import discovery
|
|
|
11
12
|
import unittest
|
|
12
13
|
import ee
|
|
13
14
|
from ee import _cloud_api_utils
|
|
15
|
+
from ee import _state
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
# Cached algorithms list
|
|
@@ -53,13 +55,8 @@ class ApiTestCase(unittest.TestCase):
|
|
|
53
55
|
self.old_get_download_id = ee.data.getDownloadId
|
|
54
56
|
self.old_get_thumb_id = ee.data.getThumbId
|
|
55
57
|
self.old_get_table_download_id = ee.data.getTableDownloadId
|
|
56
|
-
# pylint: disable=protected-access
|
|
57
|
-
self.old_install_cloud_api_resource = ee.data._install_cloud_api_resource
|
|
58
|
-
self.old_cloud_api_resource = ee.data._cloud_api_resource
|
|
59
|
-
self.old_cloud_api_resource_raw = ee.data._cloud_api_resource_raw
|
|
60
|
-
self.old_initialized = ee.data._initialized
|
|
58
|
+
# pylint: disable-next=protected-access
|
|
61
59
|
self.old_fetch_data_catalog_stac = ee.deprecation._FetchDataCatalogStac
|
|
62
|
-
# pylint: enable=protected-access
|
|
63
60
|
self.InitializeApi()
|
|
64
61
|
|
|
65
62
|
def tearDown(self):
|
|
@@ -70,13 +67,9 @@ class ApiTestCase(unittest.TestCase):
|
|
|
70
67
|
ee.data.getDownloadId = self.old_get_download_id
|
|
71
68
|
ee.data.getThumbId = self.old_get_thumb_id
|
|
72
69
|
ee.data.getTableDownloadId = self.old_get_table_download_id
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
ee.data._cloud_api_resource = self.old_cloud_api_resource
|
|
76
|
-
ee.data._cloud_api_resource_raw = self.old_cloud_api_resource_raw
|
|
77
|
-
ee.data._initialized = self.old_initialized
|
|
70
|
+
_state.reset_state()
|
|
71
|
+
# pylint: disable-next=protected-access
|
|
78
72
|
ee.deprecation._FetchDataCatalogStac = self.old_fetch_data_catalog_stac
|
|
79
|
-
# pylint: enable=protected-access
|
|
80
73
|
|
|
81
74
|
def InitializeApi(self):
|
|
82
75
|
"""Initializes the library with standard API methods.
|
|
@@ -153,22 +146,23 @@ def UsingCloudApi(
|
|
|
153
146
|
mock_http: Optional[Any] = None,
|
|
154
147
|
) -> Iterable[Any]: # pytype: disable=wrong-arg-types
|
|
155
148
|
"""Returns a context manager under which the Cloud API is enabled."""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
# pylint: disable=protected-access
|
|
150
|
+
old_state = copy.copy(_state._state)
|
|
151
|
+
# pylint: enable=protected-access
|
|
159
152
|
try:
|
|
160
153
|
if cloud_api_resource is None:
|
|
161
154
|
cloud_api_resource = _GenerateCloudApiResource(mock_http, False)
|
|
162
155
|
if cloud_api_resource_raw is None:
|
|
163
156
|
cloud_api_resource_raw = _GenerateCloudApiResource(mock_http, True)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
157
|
+
state = _state.get_state()
|
|
158
|
+
state.cloud_api_resource = cloud_api_resource
|
|
159
|
+
state.cloud_api_resource_raw = cloud_api_resource_raw
|
|
160
|
+
state.initialized = True
|
|
167
161
|
yield
|
|
168
162
|
finally:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
# pylint: disable=protected-access
|
|
164
|
+
_state._state = old_state
|
|
165
|
+
# pylint: enable=protected-access
|
|
172
166
|
|
|
173
167
|
|
|
174
168
|
# A sample of encoded EE API JSON, used by SerializerTest and DeserializerTest.
|
|
@@ -100,7 +100,7 @@ class Task:
|
|
|
100
100
|
- Unpickling a previously pickled Task object.
|
|
101
101
|
|
|
102
102
|
If you're looking for a task's status but don't need a full task object,
|
|
103
|
-
ee.data.
|
|
103
|
+
ee.data.getOperation() may be appropriate.
|
|
104
104
|
|
|
105
105
|
Args:
|
|
106
106
|
task_id: The task ID, originally obtained through ee.data.newTaskId().
|
|
@@ -69,7 +69,7 @@ TYPE_STRING = 'string'
|
|
|
69
69
|
SYSTEM_TIME_START = 'system:time_start'
|
|
70
70
|
SYSTEM_TIME_END = 'system:time_end'
|
|
71
71
|
|
|
72
|
-
# A regex that parses properties of the form "[(type)]name=value".
|
|
72
|
+
# A regex that parses properties of the form "[(type)]name=value". The
|
|
73
73
|
# second, third, and fourth group are type, name, and number, respectively.
|
|
74
74
|
PROPERTY_RE = re.compile(r'(\(([^\)]*)\))?([^=]+)=(.*)')
|
|
75
75
|
|
|
@@ -193,6 +193,39 @@ def _cloud_timestamp_for_timestamp_ms(timestamp_ms: float) -> str:
|
|
|
193
193
|
return timestamp.replace(tzinfo=None).isoformat() + 'Z'
|
|
194
194
|
|
|
195
195
|
|
|
196
|
+
def _datetime_from_cloud_timestamp(
|
|
197
|
+
cloud_timestamp: str | None,
|
|
198
|
+
) -> datetime.datetime:
|
|
199
|
+
"""Returns a datetime object for the given Cloud-formatted timestamp.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
cloud_timestamp: A timestamp string in the format 'YYYY-MM-DDTHH:MM:SS.mmmZ'
|
|
203
|
+
or 'YYYY-MM-DDTHH:MM:SSZ'. If None, returns the epoch.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
A datetime object for the given Cloud-formatted timestamp. If the timestamp
|
|
207
|
+
is None, returns the epoch.
|
|
208
|
+
"""
|
|
209
|
+
if not cloud_timestamp:
|
|
210
|
+
return datetime.datetime.fromtimestamp(0)
|
|
211
|
+
# Replace 'Z' with the UTC offset +00:00 for fromisoformat compatibility.
|
|
212
|
+
return datetime.datetime.fromisoformat(cloud_timestamp.replace('Z', '+00:00'))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _format_cloud_timestamp(timestamp: str | None) -> str:
|
|
216
|
+
"""Returns a formatted datetime for the given Cloud-formatted timestamp.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
timestamp: A timestamp string in the format 'YYYY-MM-DDTHH:MM:SS.mmmZ'
|
|
220
|
+
or 'YYYY-MM-DDTHH:MM:SSZ'.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
A formatted datetime string in the format 'YYYY-MM-DD HH:MM:SS'. If the
|
|
224
|
+
timestamp is None, returns the formatted epoch.
|
|
225
|
+
"""
|
|
226
|
+
return _datetime_from_cloud_timestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
|
227
|
+
|
|
228
|
+
|
|
196
229
|
def _parse_millis(millis: float) -> datetime.datetime:
|
|
197
230
|
return datetime.datetime.fromtimestamp(millis / 1000)
|
|
198
231
|
|
|
@@ -230,13 +263,34 @@ def _decode_date(string: str) -> Union[float, str]:
|
|
|
230
263
|
'Invalid value for property of type "date": "%s".' % string)
|
|
231
264
|
|
|
232
265
|
|
|
266
|
+
def _task_id_to_operation_name(task_id: str) -> str:
|
|
267
|
+
"""Converts a task ID to an operation name."""
|
|
268
|
+
# pylint: disable=protected-access
|
|
269
|
+
return ee._cloud_api_utils.convert_task_id_to_operation_name(
|
|
270
|
+
ee.data._get_state().cloud_api_user_project, task_id
|
|
271
|
+
)
|
|
272
|
+
# pylint: enable=protected-access
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _operation_name_to_task_id(operation_name: str) -> str:
|
|
276
|
+
"""Converts an operation name to a task ID."""
|
|
277
|
+
# pylint: disable-next=protected-access
|
|
278
|
+
return ee._cloud_api_utils.convert_operation_name_to_task_id(operation_name)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _convert_to_operation_state(state: str) -> str:
|
|
282
|
+
"""Converts a task or operation state to an operation state."""
|
|
283
|
+
# pylint: disable-next=protected-access
|
|
284
|
+
return ee._cloud_api_utils.convert_to_operation_state(state)
|
|
285
|
+
|
|
286
|
+
|
|
233
287
|
def _decode_property(string: str) -> tuple[str, Any]:
|
|
234
288
|
"""Decodes a general key-value property from a command-line argument.
|
|
235
289
|
|
|
236
290
|
Args:
|
|
237
291
|
string: The string must have the form name=value or (type)name=value, where
|
|
238
292
|
type is one of 'number', 'string', or 'date'. The value format for dates
|
|
239
|
-
is YYYY-MM-DD[THH:MM:SS[.MS]].
|
|
293
|
+
is YYYY-MM-DD[THH:MM:SS[.MS]]. The value 'null' is special: it evaluates
|
|
240
294
|
to None unless it is cast to a string of 'null'.
|
|
241
295
|
|
|
242
296
|
Returns:
|
|
@@ -400,7 +454,7 @@ class AuthenticateCommand:
|
|
|
400
454
|
args_auth['auth_mode'] = 'notebook'
|
|
401
455
|
|
|
402
456
|
if ee.Authenticate(**args_auth):
|
|
403
|
-
print('Authenticate: Credentials already exist.
|
|
457
|
+
print('Authenticate: Credentials already exist. Use --force to refresh.')
|
|
404
458
|
|
|
405
459
|
|
|
406
460
|
class SetProjectCommand:
|
|
@@ -1111,19 +1165,21 @@ class TaskCancelCommand:
|
|
|
1111
1165
|
config.ee_init()
|
|
1112
1166
|
cancel_all = args.task_ids == ['all']
|
|
1113
1167
|
if cancel_all:
|
|
1114
|
-
|
|
1168
|
+
operations = ee.data.listOperations()
|
|
1115
1169
|
else:
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1170
|
+
operation_names = map(_task_id_to_operation_name, args.task_ids)
|
|
1171
|
+
operations = map(ee.data.getOperation, operation_names)
|
|
1172
|
+
for operation in operations:
|
|
1173
|
+
name = operation['name']
|
|
1174
|
+
state = operation['metadata']['state']
|
|
1175
|
+
task_id = _operation_name_to_task_id(name)
|
|
1120
1176
|
if state == 'UNKNOWN':
|
|
1121
|
-
raise ee.EEException('Unknown task id "
|
|
1122
|
-
elif state == '
|
|
1123
|
-
print('Canceling task "
|
|
1124
|
-
ee.data.
|
|
1177
|
+
raise ee.EEException(f'Unknown task id "{task_id}"')
|
|
1178
|
+
elif state == 'PENDING' or state == 'RUNNING':
|
|
1179
|
+
print(f'Canceling task "{task_id}"')
|
|
1180
|
+
ee.data.cancelOperation(name)
|
|
1125
1181
|
elif not cancel_all:
|
|
1126
|
-
print('Task "{}" already in state "{}".'
|
|
1182
|
+
print(f'Task "{task_id}" already in state "{state}".')
|
|
1127
1183
|
|
|
1128
1184
|
|
|
1129
1185
|
class TaskInfoCommand:
|
|
@@ -1139,26 +1195,30 @@ class TaskInfoCommand:
|
|
|
1139
1195
|
) -> None:
|
|
1140
1196
|
"""Runs the TaskInfo command."""
|
|
1141
1197
|
config.ee_init()
|
|
1142
|
-
for i,
|
|
1198
|
+
for i, task_id in enumerate(args.task_id):
|
|
1199
|
+
operation = ee.data.getOperation(_task_id_to_operation_name(task_id))
|
|
1143
1200
|
if i:
|
|
1144
1201
|
print()
|
|
1145
|
-
print('
|
|
1146
|
-
|
|
1147
|
-
|
|
1202
|
+
print(f'{task_id}:')
|
|
1203
|
+
metadata = operation['metadata']
|
|
1204
|
+
state = metadata['state']
|
|
1205
|
+
print(f' State: {state}')
|
|
1206
|
+
if state == 'UNKNOWN':
|
|
1148
1207
|
continue
|
|
1149
|
-
print(' Type:
|
|
1150
|
-
print(' Description:
|
|
1151
|
-
print(' Created:
|
|
1152
|
-
if '
|
|
1153
|
-
print(' Started:
|
|
1154
|
-
if '
|
|
1155
|
-
print(' Updated:
|
|
1156
|
-
if '
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1208
|
+
print(f' Type: {TASK_TYPES.get(metadata.get("type"), "Unknown")}')
|
|
1209
|
+
print(f' Description: {metadata.get("description")}')
|
|
1210
|
+
print(f' Created: {_format_cloud_timestamp(metadata["createTime"])}')
|
|
1211
|
+
if start_time := metadata.get('startTime'):
|
|
1212
|
+
print(f' Started: {_format_cloud_timestamp(start_time)}')
|
|
1213
|
+
if update_time := metadata.get('updateTime'):
|
|
1214
|
+
print(f' Updated: {_format_cloud_timestamp(update_time)}')
|
|
1215
|
+
if error := operation.get('error'):
|
|
1216
|
+
if error_message := error.get('message'):
|
|
1217
|
+
print(f' Error: {error_message}')
|
|
1218
|
+
if destination_uris := metadata.get('destinationUris'):
|
|
1219
|
+
print(f' Destination URIs: {destination_uris}')
|
|
1220
|
+
if priority := metadata.get('priority'):
|
|
1221
|
+
print(f' Priority: {priority}')
|
|
1162
1222
|
|
|
1163
1223
|
|
|
1164
1224
|
class TaskListCommand:
|
|
@@ -1168,10 +1228,27 @@ class TaskListCommand:
|
|
|
1168
1228
|
|
|
1169
1229
|
def __init__(self, parser: argparse.ArgumentParser):
|
|
1170
1230
|
parser.add_argument(
|
|
1171
|
-
'--status',
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1231
|
+
'--status',
|
|
1232
|
+
'-s',
|
|
1233
|
+
required=False,
|
|
1234
|
+
nargs='*',
|
|
1235
|
+
choices=[
|
|
1236
|
+
'CANCELLED',
|
|
1237
|
+
'CANCELLING',
|
|
1238
|
+
'COMPLETED', # Kept for backward compatibility.
|
|
1239
|
+
'FAILED',
|
|
1240
|
+
'PENDING',
|
|
1241
|
+
'READY', # Kept for backward compatibility.
|
|
1242
|
+
'RUNNING',
|
|
1243
|
+
'SUCCEEDED',
|
|
1244
|
+
'UNKNOWN',
|
|
1245
|
+
],
|
|
1246
|
+
help=(
|
|
1247
|
+
'List tasks only with a given status. Note: for backward'
|
|
1248
|
+
' compatibility, "READY" is an alias for "PENDING" and "COMPLETED"'
|
|
1249
|
+
' is an alias for "SUCCEEDED".'
|
|
1250
|
+
),
|
|
1251
|
+
)
|
|
1175
1252
|
parser.add_argument(
|
|
1176
1253
|
'--long_format',
|
|
1177
1254
|
'-l',
|
|
@@ -1185,34 +1262,47 @@ class TaskListCommand:
|
|
|
1185
1262
|
) -> None:
|
|
1186
1263
|
"""Lists tasks present for a user, maybe filtering by state."""
|
|
1187
1264
|
config.ee_init()
|
|
1188
|
-
status =
|
|
1189
|
-
|
|
1190
|
-
|
|
1265
|
+
status = list(
|
|
1266
|
+
map(_convert_to_operation_state, args.status) if args.status else []
|
|
1267
|
+
)
|
|
1268
|
+
operations = ee.data.listOperations()
|
|
1269
|
+
descs = [
|
|
1270
|
+
utils.truncate(op.get('metadata', {}).get('description', ''), 40)
|
|
1271
|
+
for op in operations
|
|
1272
|
+
]
|
|
1191
1273
|
desc_length = max((len(word) for word in descs), default=0)
|
|
1192
|
-
format_str = '{:25s} {:13s} {
|
|
1193
|
-
for
|
|
1194
|
-
|
|
1274
|
+
format_str = f'{{:25s}} {{:13s}} {{:{desc_length + 1}s}} {{:10s}} {{:s}}'
|
|
1275
|
+
for operation in operations:
|
|
1276
|
+
metadata = operation['metadata']
|
|
1277
|
+
if status and metadata['state'] not in status:
|
|
1195
1278
|
continue
|
|
1196
|
-
truncated_desc = utils.truncate(
|
|
1197
|
-
task_type = TASK_TYPES.get(
|
|
1279
|
+
truncated_desc = utils.truncate(metadata.get('description', ''), 40)
|
|
1280
|
+
task_type = TASK_TYPES.get(metadata['type'], 'Unknown')
|
|
1198
1281
|
extra = ''
|
|
1199
1282
|
if args.long_format:
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
trailing_extras =
|
|
1205
|
-
trailing_extras.append(
|
|
1283
|
+
if eecu := metadata.get('batchEecuUsageSeconds'):
|
|
1284
|
+
eecu = f'{eecu:.4f}'
|
|
1285
|
+
else:
|
|
1286
|
+
eecu = '-'
|
|
1287
|
+
trailing_extras = metadata.get('destination_uris', [])
|
|
1288
|
+
trailing_extras.append(metadata.get('priority', '-'))
|
|
1206
1289
|
extra = ' {:20s} {:20s} {:20s} {:11s} {}'.format(
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1290
|
+
_format_cloud_timestamp(operation.get('createTime')),
|
|
1291
|
+
_format_cloud_timestamp(operation.get('startTime')),
|
|
1292
|
+
_format_cloud_timestamp(operation.get('updateTime')),
|
|
1210
1293
|
eecu,
|
|
1211
1294
|
' '.join(map(str, trailing_extras)),
|
|
1212
1295
|
)
|
|
1213
|
-
print(
|
|
1214
|
-
|
|
1215
|
-
|
|
1296
|
+
print(
|
|
1297
|
+
format_str.format(
|
|
1298
|
+
_operation_name_to_task_id(operation['name']),
|
|
1299
|
+
task_type,
|
|
1300
|
+
truncated_desc,
|
|
1301
|
+
metadata['state'],
|
|
1302
|
+
operation.get('error', {}).get('message', '---'),
|
|
1303
|
+
)
|
|
1304
|
+
+ extra
|
|
1305
|
+
)
|
|
1216
1306
|
|
|
1217
1307
|
|
|
1218
1308
|
class TaskWaitCommand:
|
|
@@ -1241,17 +1331,17 @@ class TaskWaitCommand:
|
|
|
1241
1331
|
config.ee_init()
|
|
1242
1332
|
task_ids = []
|
|
1243
1333
|
if args.task_ids == ['all']:
|
|
1244
|
-
|
|
1245
|
-
for
|
|
1246
|
-
if
|
|
1247
|
-
task_ids.append(
|
|
1334
|
+
operations = ee.data.listOperations()
|
|
1335
|
+
for operation in operations:
|
|
1336
|
+
if not operation.get('done', False):
|
|
1337
|
+
task_ids.append(_operation_name_to_task_id(operation['name']))
|
|
1248
1338
|
else:
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
state =
|
|
1252
|
-
task_id =
|
|
1339
|
+
for task_id in args.task_ids:
|
|
1340
|
+
operation = ee.data.getOperation(_task_id_to_operation_name(task_id))
|
|
1341
|
+
state = operation['metadata']['state']
|
|
1342
|
+
task_id = _operation_name_to_task_id(operation['name'])
|
|
1253
1343
|
if state == 'UNKNOWN':
|
|
1254
|
-
raise ee.EEException('Unknown task id "
|
|
1344
|
+
raise ee.EEException(f'Unknown task id "{task_id}"')
|
|
1255
1345
|
else:
|
|
1256
1346
|
task_ids.append(task_id)
|
|
1257
1347
|
|
|
@@ -35,7 +35,7 @@ def _run_command(*argv):
|
|
|
35
35
|
'Defaults to "~/%s".' % utils.DEFAULT_EE_CONFIG_FILE_RELATIVE)
|
|
36
36
|
parser.add_argument(
|
|
37
37
|
'--service_account_file', help='Path to a service account credentials'
|
|
38
|
-
'file.
|
|
38
|
+
'file. Overrides any ee_config if specified.')
|
|
39
39
|
parser.add_argument(
|
|
40
40
|
'--project',
|
|
41
41
|
help='Specifies a Google Cloud Platform Project id to override the call.',
|