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.

Files changed (115) hide show
  1. {earthengine_api-1.6.11/earthengine_api.egg-info → earthengine_api-1.6.12}/PKG-INFO +1 -1
  2. {earthengine_api-1.6.11 → earthengine_api-1.6.12/earthengine_api.egg-info}/PKG-INFO +1 -1
  3. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/SOURCES.txt +2 -0
  4. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/__init__.py +5 -5
  5. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_cloud_api_utils.py +33 -10
  6. earthengine_api-1.6.12/ee/_state.py +105 -0
  7. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/apifunction.py +1 -1
  8. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/apitestcase.py +15 -21
  9. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/batch.py +1 -1
  10. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/commands.py +153 -63
  11. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/eecli.py +1 -1
  12. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/utils.py +25 -15
  13. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/collection.py +27 -18
  14. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/computedobject.py +5 -5
  15. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/customfunction.py +3 -3
  16. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/data.py +104 -210
  17. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_array.py +4 -2
  18. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_number.py +1 -1
  19. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_string.py +18 -26
  20. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_types.py +2 -2
  21. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/element.py +1 -1
  22. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/featurecollection.py +10 -7
  23. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/filter.py +2 -2
  24. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/geometry.py +20 -21
  25. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/image.py +7 -12
  26. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/imagecollection.py +3 -3
  27. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/mapclient.py +9 -9
  28. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/oauth.py +13 -6
  29. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/_cloud_api_utils_test.py +16 -0
  30. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/_helpers_test.py +9 -9
  31. earthengine_api-1.6.12/ee/tests/_state_test.py +49 -0
  32. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/apifunction_test.py +5 -5
  33. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/batch_test.py +61 -50
  34. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/collection_test.py +13 -13
  35. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/data_test.py +65 -60
  36. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/dictionary_test.py +9 -9
  37. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_number_test.py +32 -26
  38. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_string_test.py +8 -0
  39. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_test.py +37 -19
  40. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/element_test.py +2 -2
  41. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/feature_test.py +6 -6
  42. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/function_test.py +5 -5
  43. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/geometry_test.py +73 -51
  44. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/oauth_test.py +21 -2
  45. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/serializer_test.py +8 -8
  46. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/pyproject.toml +1 -1
  47. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/LICENSE +0 -0
  48. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/MANIFEST.in +0 -0
  49. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/README.md +0 -0
  50. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/dependency_links.txt +0 -0
  51. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/entry_points.txt +0 -0
  52. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/requires.txt +0 -0
  53. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/earthengine_api.egg-info/top_level.txt +0 -0
  54. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_arg_types.py +0 -0
  55. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_helpers.py +0 -0
  56. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/_utils.py +0 -0
  57. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/blob.py +0 -0
  58. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/classifier.py +0 -0
  59. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/__init__.py +0 -0
  60. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/cli/eecli_wrapper.py +0 -0
  61. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/clusterer.py +0 -0
  62. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/confusionmatrix.py +0 -0
  63. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/daterange.py +0 -0
  64. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/deprecation.py +0 -0
  65. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/deserializer.py +0 -0
  66. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/dictionary.py +0 -0
  67. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_date.py +0 -0
  68. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_exception.py +0 -0
  69. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/ee_list.py +0 -0
  70. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/encodable.py +0 -0
  71. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/errormargin.py +0 -0
  72. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/feature.py +0 -0
  73. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/function.py +0 -0
  74. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/image_converter.py +0 -0
  75. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/join.py +0 -0
  76. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/kernel.py +0 -0
  77. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/model.py +0 -0
  78. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/pixeltype.py +0 -0
  79. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/projection.py +0 -0
  80. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/py.typed +0 -0
  81. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/reducer.py +0 -0
  82. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/serializer.py +0 -0
  83. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/table_converter.py +0 -0
  84. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/terrain.py +0 -0
  85. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/_utils_test.py +0 -0
  86. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/algorithms.json +0 -0
  87. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/blob_test.py +0 -0
  88. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/classifier_test.py +0 -0
  89. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/cloud_api_discovery_document.json +0 -0
  90. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/clusterer_test.py +0 -0
  91. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/computedobject_test.py +0 -0
  92. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/confusionmatrix_test.py +0 -0
  93. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/daterange_test.py +0 -0
  94. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/deprecation_test.py +0 -0
  95. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/deserializer_test.py +0 -0
  96. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_array_test.py +0 -0
  97. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_date_test.py +0 -0
  98. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_list_test.py +0 -0
  99. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/ee_types_test.py +0 -0
  100. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/errormargin_test.py +0 -0
  101. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/featurecollection_test.py +0 -0
  102. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/filter_test.py +0 -0
  103. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/geometry_point_test.py +0 -0
  104. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/image_converter_test.py +0 -0
  105. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/image_test.py +0 -0
  106. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/imagecollection_test.py +0 -0
  107. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/join_test.py +0 -0
  108. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/kernel_test.py +0 -0
  109. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/model_test.py +0 -0
  110. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/pixeltype_test.py +0 -0
  111. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/projection_test.py +0 -0
  112. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/reducer_test.py +0 -0
  113. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/table_converter_test.py +0 -0
  114. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/ee/tests/terrain_test.py +0 -0
  115. {earthengine_api-1.6.11 → earthengine_api-1.6.12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: earthengine-api
3
- Version: 1.6.11
3
+ Version: 1.6.12
4
4
  Summary: Earth Engine Python API
5
5
  Author-email: Google LLC <noreply@google.com>
6
6
  License: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: earthengine-api
3
- Version: 1.6.11
3
+ Version: 1.6.12
4
4
  Summary: Earth Engine Python API
5
5
  Author-email: Google LLC <noreply@google.com>
6
6
  License: Apache-2.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.11'
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. If this is called a second time with a different
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. 'persistent' (default) means use
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. Listed to make the linter hush.
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. If false, any keys
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 a state string from an Operation to the Task equivalent."""
866
+ """Converts an Operation state to a Task state."""
857
867
  return _convert_value(
858
- state, {
859
- 'PENDING': 'READY',
860
- 'RUNNING': 'RUNNING',
861
- 'CANCELLING': 'CANCEL_REQUESTED',
862
- 'SUCCEEDED': 'COMPLETED',
863
- 'CANCELLED': 'CANCELLED',
864
- 'FAILED': 'FAILED'
865
- }, 'UNKNOWN')
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. Each item in the dictionary identifies
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
- # pylint: disable=protected-access
74
- ee.data._install_cloud_api_resource = self.old_install_cloud_api_resource
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
- old_cloud_api_resource = ee.data._cloud_api_resource # pylint: disable=protected-access
157
- old_cloud_api_resource_raw = ee.data._cloud_api_resource_raw # pylint: disable=protected-access
158
- old_initialized = ee.data._initialized # pylint: disable=protected-access
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
- ee.data._cloud_api_resource = cloud_api_resource # pylint: disable=protected-access
165
- ee.data._cloud_api_resource_raw = cloud_api_resource_raw # pylint: disable=protected-access
166
- ee.data._initialized = True # pylint: disable=protected-access
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
- ee.data._cloud_api_resource = old_cloud_api_resource # pylint: disable=protected-access
170
- ee.data._cloud_api_resource_raw = old_cloud_api_resource_raw # pylint: disable=protected-access
171
- ee.data._initialized = old_initialized # pylint: disable=protected-access
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.getTaskStatus() may be appropriate.
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". The
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]]. The value 'null' is special: it evaluates
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. Use --force to refresh.')
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
- statuses = ee.data.getTaskList()
1168
+ operations = ee.data.listOperations()
1115
1169
  else:
1116
- statuses = ee.data.getTaskStatus(args.task_ids)
1117
- for status in statuses:
1118
- state = status['state']
1119
- task_id = status['id']
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 "%s"' % task_id)
1122
- elif state == 'READY' or state == 'RUNNING':
1123
- print('Canceling task "%s"' % task_id)
1124
- ee.data.cancelTask(task_id)
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 "{}".'.format(status['id'], 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, status in enumerate(ee.data.getTaskStatus(args.task_id)):
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('%s:' % status['id'])
1146
- print(' State: %s' % status['state'])
1147
- if status['state'] == 'UNKNOWN':
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: %s' % TASK_TYPES.get(status.get('task_type'), 'Unknown'))
1150
- print(' Description: %s' % status.get('description'))
1151
- print(' Created: %s' % _parse_millis(status['creation_timestamp_ms']))
1152
- if 'start_timestamp_ms' in status:
1153
- print(' Started: %s' % _parse_millis(status['start_timestamp_ms']))
1154
- if 'update_timestamp_ms' in status:
1155
- print(' Updated: %s' % _parse_millis(status['update_timestamp_ms']))
1156
- if 'error_message' in status:
1157
- print(' Error: %s' % status['error_message'])
1158
- if 'destination_uris' in status:
1159
- print(' Destination URIs: %s' % ', '.join(status['destination_uris']))
1160
- if 'priority' in status:
1161
- print(' Priority: %s' % status['priority'])
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', '-s', required=False, nargs='*',
1172
- choices=['READY', 'RUNNING', 'COMPLETED', 'FAILED',
1173
- 'CANCELLED', 'UNKNOWN'],
1174
- help=('List tasks only with a given status'))
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 = args.status
1189
- tasks = ee.data.getTaskList()
1190
- descs = [utils.truncate(task.get('description', ''), 40) for task in tasks]
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} {:%ds} {:10s} {:s}' % (desc_length + 1)
1193
- for task in tasks:
1194
- if status and task['state'] not in status:
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(task.get('description', ''), 40)
1197
- task_type = TASK_TYPES.get(task['task_type'], 'Unknown')
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
- show_date = lambda ms: _parse_millis(ms).strftime('%Y-%m-%d %H:%M:%S')
1201
- eecu = '{:.4f}'.format(
1202
- task['batch_eecu_usage_seconds']
1203
- ) if 'batch_eecu_usage_seconds' in task else '-'
1204
- trailing_extras = task.get('destination_uris', [])
1205
- trailing_extras.append(task.get('priority', '-'))
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
- show_date(task['creation_timestamp_ms']),
1208
- show_date(task['start_timestamp_ms']),
1209
- show_date(task['update_timestamp_ms']),
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(format_str.format(
1214
- task['id'], task_type, truncated_desc,
1215
- task['state'], task.get('error_message', '---')) + extra)
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
- tasks = ee.data.getTaskList()
1245
- for task in tasks:
1246
- if task['state'] not in utils.TASK_FINISHED_STATES:
1247
- task_ids.append(task['id'])
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
- statuses = ee.data.getTaskStatus(args.task_ids)
1250
- for status in statuses:
1251
- state = status['state']
1252
- task_id = status['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 "%s"' % 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. Overrides any ee_config if specified.')
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.',