earthengine-api 1.6.10rc0__py3-none-any.whl → 1.6.11rc0__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: earthengine-api
3
- Version: 1.6.10rc0
3
+ Version: 1.6.11rc0
4
4
  Summary: Earth Engine Python API
5
5
  Author-email: Google LLC <noreply@google.com>
6
6
  License: Apache-2.0
@@ -1,20 +1,20 @@
1
- earthengine_api-1.6.10rc0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
2
- ee/__init__.py,sha256=ZgO1o8f_-nlj8Ja9MeXDIwKLSOIok7DqN49XMRhM6xw,16811
1
+ earthengine_api-1.6.11rc0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
2
+ ee/__init__.py,sha256=XAO2w927IcX3ubm3IW7Rw04qW0fEzd-6J43CGU_b5tU,16811
3
3
  ee/_arg_types.py,sha256=nrJrnPFnAS8fzMxAGmG3TbUOi_yFVrnSGW5IP8ATzDQ,2662
4
- ee/_cloud_api_utils.py,sha256=W3y3PBjWWCD5PYIxYjsFmU8eq-umN_cEs-u6lv5_9sQ,32459
5
- ee/_helpers.py,sha256=d2gHX7akpY8pnB9guC10Yy53W8sDfriaH21d-mEol18,4700
4
+ ee/_cloud_api_utils.py,sha256=sG1WtO9zy_xNSSGWiG1Y9ltrvkl5dExqIoWMw5O6llE,32192
5
+ ee/_helpers.py,sha256=71xYDbCO_1usY3SnedoA9zi7j6dnPBijru1eUh4o25g,4859
6
6
  ee/_utils.py,sha256=SAXQ_ZefZUaOtyV6Lp3pdYqEFqblMEA6Bvxz0ltLjzA,1329
7
- ee/apifunction.py,sha256=tzk_emkgi_OxFSEh33zXBHGQd-ja5VHaRTNBCXcrOXo,8747
7
+ ee/apifunction.py,sha256=5oatxvJH1KAFYz5Ae8T58wi40fK33-8y9ajEQz0wpW4,8733
8
8
  ee/apitestcase.py,sha256=K3zHLYFCJ_QhwdhoImIeD-DJjSgd_nAGQpftKYvjF-Q,14959
9
- ee/batch.py,sha256=vGIhExr37KJIU7-ZJJYckDSa7HK8rUHwEMLRONzuhhs,82481
10
- ee/blob.py,sha256=TttcEI5eQMMKvvlw5OgGk26-T0LPcHEYmse7K6wOTZo,3192
9
+ ee/batch.py,sha256=opkPNZ8V1tGQiMnVKVtSn1XA19AJhxrlTW5u77o8EmY,82401
10
+ ee/blob.py,sha256=cnnqmrHl998U2zEmfKcxQglaGk14Ip6pjObk_Ody6Uk,3221
11
11
  ee/classifier.py,sha256=3STLrLT6Ddwzjs2324E02wRWM8VEpeifgOO81geTHJQ,23410
12
12
  ee/clusterer.py,sha256=986D68b2eJ4xiiOiIOTNZ1psF8Ro-MFNQFKed058FS4,12256
13
13
  ee/collection.py,sha256=20g_Y_UnkhABGFOSybyE_IGmKvod5kR9xm5eKLjzNcE,32277
14
14
  ee/computedobject.py,sha256=o5F4y00PK-fye2pduA5QmMd6XIERA1lhC3yLs88zRpY,9082
15
15
  ee/confusionmatrix.py,sha256=e6vz-FcT6acWxk5pDEK0vLIN1pUjYMh5Gyn1QSGJT3I,4233
16
16
  ee/customfunction.py,sha256=k57sl03_vhOuEwAmgFpg0z3Ze9J3gy_W6n4u3uMaryA,7384
17
- ee/data.py,sha256=cBPDlbdaAYONZ3_dwZDM_v8rvEoWBDisvFoFE0ZF6Nw,88616
17
+ ee/data.py,sha256=-vgLRmxLmCunjxbK7D0riSNYreD5jqPjv3WLUbuZmGs,88549
18
18
  ee/daterange.py,sha256=nrRYkR2M2aVU0ZJyG7yiZStFt-W2TvYVuazoZK_WZqM,4948
19
19
  ee/deprecation.py,sha256=dkTRDZcdT90_j41z_mMlhtX8u5eD5N2BuKL90Q0oWYc,6085
20
20
  ee/deserializer.py,sha256=Xz9Dis85JgZeLvpF-4yw3ZRv39rjvCTBYHjio8DE1kM,8424
@@ -54,20 +54,20 @@ ee/cli/commands.py,sha256=P2KPuSx0wKcp1lfo7hwkTqwnjxORnxTKqJ0RwrX3O8c,70534
54
54
  ee/cli/eecli.py,sha256=iws34w24UJI6H7Wl9WsEUwy-uN57Pw-bgHtr2f92olY,3044
55
55
  ee/cli/eecli_wrapper.py,sha256=Z7R3IJcht2uG1h57oY7BSrkPiwQzNmmYlyCSX1_7L7c,1089
56
56
  ee/cli/utils.py,sha256=YWrgN_5k8GRWndST20KkGljn4Ee8F8935ID91qiLRtY,13796
57
- ee/tests/_cloud_api_utils_test.py,sha256=sbts2xDKmf1dCedVv1w3KBp-IvOfihxbM6g_H0nI1sg,18814
58
- ee/tests/_helpers_test.py,sha256=3nd6pTAykjYRxXDxtYJjkg48EgJSogPRdepi_nwGJAA,1935
57
+ ee/tests/_cloud_api_utils_test.py,sha256=YpTG4wBTuSIyb7XolAre41VuQCiH9di_7olCE_71S1U,18754
58
+ ee/tests/_helpers_test.py,sha256=p_GJaXr9xcQbBFkMp7f9H_h2OBqI1YPH1pRHGNE8cA4,3964
59
59
  ee/tests/_utils_test.py,sha256=bOarVj3U-VFo9Prog8WQN_hAOMwJOiWKJxevUbdFPBQ,2753
60
60
  ee/tests/algorithms.json,sha256=Vx1Kx_MhHv0z0B3WTeVAvchM8xVd3zYE7L-qT3gDGzA,729368
61
61
  ee/tests/apifunction_test.py,sha256=62El-6jcgQmD7qt9eEDdM7IeIQmpV8M5xQ439g-zfN4,3767
62
- ee/tests/batch_test.py,sha256=QN2F6lUgHYws16pfz9LSPnkEteWwkxU3YOfNvfRl-lo,62210
63
- ee/tests/blob_test.py,sha256=2Y-btrQ8FQqmf1ccYR8hLzdCaM5uoHfSoB7a5OziTxA,3575
62
+ ee/tests/batch_test.py,sha256=Vo-0_LoH2Ccutta6SAfgbk4VcptfeW2DA5yXJPWJj_Y,62399
63
+ ee/tests/blob_test.py,sha256=uCrM-ubRfAmNgHwhmUhWn7MiXqbNQutybcXIKdVsD_Q,3587
64
64
  ee/tests/classifier_test.py,sha256=K6-wNZ2uh9oPYo7BV0vtfU73SBFpeNcFRMRmvEFc6Pg,19087
65
65
  ee/tests/cloud_api_discovery_document.json,sha256=SnOeL8One57YdeHa7XxGZM-ptDPCeDSymBc7-Bo_hkA,41154
66
66
  ee/tests/clusterer_test.py,sha256=B4m06wAtBeqvnIhRD2lnNy1UHDB_caleK_CqcrAU8dk,11620
67
67
  ee/tests/collection_test.py,sha256=dnZwFADWQ8oShJHRTcYSzuP-waAxetTdM_CtVckWbMM,7563
68
68
  ee/tests/computedobject_test.py,sha256=B27rDq9Urpvy0WqpdbKRYbt6AcT1i93HX-es7hrhWVY,4840
69
69
  ee/tests/confusionmatrix_test.py,sha256=46JJh1-91AiYISXWZ6-2lvY5_Njvc8ompO9kmwqlFdg,7437
70
- ee/tests/data_test.py,sha256=WxNDhGA-wLNvyX45_KfS49l28QmLs8CKXL6zWeVQKBs,35294
70
+ ee/tests/data_test.py,sha256=9zDGICUCeJFi4Q6lX2mB0Qca9C-eUMEn4vzpTZI5Imk,40222
71
71
  ee/tests/daterange_test.py,sha256=a5fpg2lko3kCJzxQPCoAc_vjXkKy2zYcXbeSZKAFovI,8583
72
72
  ee/tests/deprecation_test.py,sha256=_sCs59l6c-ijeyt5yPO-IRJsh8GGPp7ArSg3Y12u4mQ,8352
73
73
  ee/tests/deserializer_test.py,sha256=-tbrL0cjrXdSLF7M3wl-QQuj6TjXJdkjp7RZvVErUy4,3427
@@ -77,7 +77,7 @@ ee/tests/ee_date_test.py,sha256=8rLUXfjyiW3LiBOCSneA7ZGgmoFgN5oZr58x5THtKGY,1110
77
77
  ee/tests/ee_list_test.py,sha256=yd2EWZGdg7pLJhsHSR5AbK58ZhT31GY-n2o1dDT3p9A,21797
78
78
  ee/tests/ee_number_test.py,sha256=3MxX8Awie_L3FfwyltrL2UXNdqTyvtYJB9qIi73wlPM,33442
79
79
  ee/tests/ee_string_test.py,sha256=9QuseILwZtbcczCyNyJ751kva96_9gPKXLRdgkgWMPs,9389
80
- ee/tests/ee_test.py,sha256=wTg7mS5dxWtZr5QDpLjsI8LBOQmw5INT6gxo1MBjAGI,17061
80
+ ee/tests/ee_test.py,sha256=_HELrvUb7Wpmeji4Jd_rlZfCcXWQ4mPxRTG7d88u0zI,17082
81
81
  ee/tests/ee_types_test.py,sha256=oRnqplaTWg47zuYfAYTTVwembCnw8XT20HPNMdAvgNE,921
82
82
  ee/tests/element_test.py,sha256=Kqu_Z65FQcYHX4LebKm3LD0mWkRTRZccs-qAGz3bLsE,1964
83
83
  ee/tests/errormargin_test.py,sha256=YEBzvBFsD756nicZBcjnPFAXy06jZNKiSSAa2hAzN-M,5061
@@ -100,8 +100,8 @@ ee/tests/reducer_test.py,sha256=NYJTmX6AmBZyXGjOkgUh4t3tB6E8_vGlPYQIxJQbV9Q,3151
100
100
  ee/tests/serializer_test.py,sha256=xZQBQMDK8dlGYLS-6s1JHhR1L5FKoaIwEkxdKlcpQTE,8802
101
101
  ee/tests/table_converter_test.py,sha256=2F7DyEj-iHVbt9-W1iwAHDl-K1GA_2QHalTXzac2ot8,3373
102
102
  ee/tests/terrain_test.py,sha256=8TmvGconOR-GkGrll4joXhpU9zi0b_8GFxvX_JlcCos,4328
103
- earthengine_api-1.6.10rc0.dist-info/METADATA,sha256=bOW5BfnHD5D6sqEBojtAm-PRYYCLh-Kq5NgTMrjeKSI,2197
104
- earthengine_api-1.6.10rc0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
105
- earthengine_api-1.6.10rc0.dist-info/entry_points.txt,sha256=-Ax4SCU-S474r8OD2LIxata6PRmkZoDrppQ4fP_exNc,50
106
- earthengine_api-1.6.10rc0.dist-info/top_level.txt,sha256=go5zOwCgm5lIS3yTR-Vsxp1gNI4qdS-MP5eY-7zMxVY,3
107
- earthengine_api-1.6.10rc0.dist-info/RECORD,,
103
+ earthengine_api-1.6.11rc0.dist-info/METADATA,sha256=_Kq57-3jjgcxF4o8peqU7Mf8SFWzQpDUL71Z9CpgIPI,2197
104
+ earthengine_api-1.6.11rc0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
105
+ earthengine_api-1.6.11rc0.dist-info/entry_points.txt,sha256=-Ax4SCU-S474r8OD2LIxata6PRmkZoDrppQ4fP_exNc,50
106
+ earthengine_api-1.6.11rc0.dist-info/top_level.txt,sha256=go5zOwCgm5lIS3yTR-Vsxp1gNI4qdS-MP5eY-7zMxVY,3
107
+ earthengine_api-1.6.11rc0.dist-info/RECORD,,
ee/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """The EE Python library."""
2
2
 
3
- __version__ = '1.6.10rc0'
3
+ __version__ = '1.6.11rc0'
4
4
 
5
5
  # Using lowercase function naming to match the JavaScript names.
6
6
  # pylint: disable=g-bad-name
ee/_cloud_api_utils.py CHANGED
@@ -35,9 +35,6 @@ 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
- # The default user project to use when making Cloud API calls.
39
- _cloud_api_user_project: Optional[str] = None
40
-
41
38
 
42
39
  class _Http:
43
40
  """A httplib2.Http-like object based on requests."""
@@ -136,11 +133,6 @@ def _wrap_request(
136
133
  return builder
137
134
 
138
135
 
139
- def set_cloud_api_user_project(cloud_api_user_project: str) -> None:
140
- global _cloud_api_user_project
141
- _cloud_api_user_project = cloud_api_user_project
142
-
143
-
144
136
  def build_cloud_resource(
145
137
  api_base_url: str,
146
138
  session: requests.Session,
@@ -560,9 +552,9 @@ def convert_operation_name_to_task_id(operation_name: str) -> str:
560
552
  return found.group(1) if found else operation_name
561
553
 
562
554
 
563
- def convert_task_id_to_operation_name(task_id: str) -> str:
555
+ def convert_task_id_to_operation_name(project: str, task_id: str) -> str:
564
556
  """Converts a task ID to an Operation name."""
565
- return f'projects/{_cloud_api_user_project}/operations/{task_id}'
557
+ return f'projects/{project}/operations/{task_id}'
566
558
 
567
559
 
568
560
  def convert_params_to_image_manifest(params: dict[str, Any]) -> dict[str, Any]:
ee/_helpers.py CHANGED
@@ -22,13 +22,14 @@ from ee import data
22
22
  from ee import ee_exception
23
23
  from ee import oauth
24
24
 
25
-
26
25
  # Number of times to retry fetching profile data.
27
26
  _PROFILE_RETRIES = 5
28
27
 
29
28
 
30
29
  def ServiceAccountCredentials(
31
- email: str, key_file: Optional[str] = None, key_data: Optional[str] = None
30
+ email: Optional[str] = None,
31
+ key_file: Optional[str] = None,
32
+ key_data: Optional[str] = None,
32
33
  ) -> service_account.Credentials:
33
34
  """Configure OAuth2 credentials for a Google Service Account.
34
35
 
@@ -42,6 +43,10 @@ def ServiceAccountCredentials(
42
43
  Returns:
43
44
  An OAuth2 credentials object.
44
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
+ )
45
50
 
46
51
  # Assume anything that doesn't end in '.pem' is a JSON key.
47
52
  if key_file and not key_file.endswith('.pem'):
@@ -119,14 +124,13 @@ def profilePrinting(destination: TextIO = sys.stderr) -> Iterator[None]:
119
124
  The profile will be printed when the context ends, whether or not any error
120
125
  occurred within the context.
121
126
 
122
- # Simple example:
123
- with ee.profilePrinting():
124
- print ee.Number(1).add(1).getInfo()
127
+ Example:
128
+ with ee.profilePrinting():
129
+ print(ee.Number(1).add(1).getInfo())
125
130
 
126
131
  Args:
127
132
  destination: A file-like object to which the profile text is written.
128
133
  Defaults to sys.stderr.
129
-
130
134
  """
131
135
  # Profile.getProfiles is `hidden`, so call it explicitly.
132
136
  get_profiles = apifunction.ApiFunction.lookup('Profile.getProfiles').call
@@ -136,12 +140,11 @@ def profilePrinting(destination: TextIO = sys.stderr) -> Iterator[None]:
136
140
  yield
137
141
  finally:
138
142
  # Make several attempts in case of transient errors.
139
- attempts = _PROFILE_RETRIES
140
- for i in range(_PROFILE_RETRIES):
143
+ for attempt in range(_PROFILE_RETRIES):
141
144
  try:
142
145
  profile_text = get_profiles(ids=profile_ids).getInfo()
143
146
  destination.write(profile_text)
144
147
  break
145
148
  except ee_exception.EEException as exception:
146
- if i == attempts - 1:
149
+ if attempt == _PROFILE_RETRIES - 1:
147
150
  raise exception
ee/apifunction.py CHANGED
@@ -60,7 +60,7 @@ class ApiFunction(function.Function):
60
60
  return (isinstance(other, ApiFunction) and
61
61
  self.getSignature() == other.getSignature())
62
62
 
63
- # For Python 3, __hash__ is needed because __eq__ is defined.
63
+ # __hash__ is needed because __eq__ is defined.
64
64
  # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
65
65
  def __hash__(self) -> int:
66
66
  return hash(computedobject.ComputedObject.freeze(self.getSignature()))
ee/batch.py CHANGED
@@ -112,7 +112,7 @@ class Task:
112
112
  - description: The name of the task, a freeform string.
113
113
  - sourceUrl: An optional URL for the script that generated the task.
114
114
  Specific task types have other custom config fields.
115
- name: The name of the operation. Only relevant when using the cloud api.
115
+ name: The name of the operation.
116
116
  """
117
117
  self.id = self._request_id = task_id
118
118
  self.config = config and config.copy()
@@ -123,10 +123,9 @@ class Task:
123
123
 
124
124
  @property
125
125
  def operation_name(self) -> str | None:
126
+ """The server-assigned name for this task."""
126
127
  if self.name:
127
128
  return self.name
128
- if self.id:
129
- return _cloud_api_utils.convert_task_id_to_operation_name(self.id)
130
129
  return None
131
130
 
132
131
  def start(self) -> None:
ee/blob.py CHANGED
@@ -46,24 +46,22 @@ class Blob(computedobject.ComputedObject):
46
46
  """
47
47
  self.initialize()
48
48
 
49
- args: dict[str, Any] = {'url': url}
50
- func = apifunction.ApiFunction(self.name())
51
-
52
- if isinstance(url, str):
53
- if not url.startswith('gs://'):
54
- raise ValueError(f'{self.name()} url must start with "gs://": "{url}"')
55
-
56
- elif isinstance(url, computedobject.ComputedObject):
49
+ if isinstance(url, computedobject.ComputedObject):
57
50
  if self.is_func_returning_same(url):
58
51
  # If it is a call that is already returning a Blob, just cast.
59
52
  super().__init__(url.func, url.args, url.varName)
60
53
  return
61
-
54
+ elif isinstance(url, str):
55
+ if not url.startswith('gs://'):
56
+ raise ValueError(f'{self.name()} url must start with "gs://": "{url}"')
62
57
  else:
63
58
  raise ValueError(
64
- f'{self.name()} url must be a string: {type(url)} -> "{url}"'
59
+ f'{self.name()} url must be a string or ComputedObject: '
60
+ f'{type(url)} -> "{url}"'
65
61
  )
66
62
 
63
+ func = apifunction.ApiFunction(self.name())
64
+ args: dict[str, Any] = {'url': url}
67
65
  super().__init__(func, func.promoteArgs(args))
68
66
 
69
67
  @classmethod
ee/data.py CHANGED
@@ -35,6 +35,64 @@ from ee import table_converter
35
35
 
36
36
  from ee import __version__
37
37
 
38
+ # The HTTP header through which profile results are returned.
39
+ # Lowercase because that's how httplib2 does things.
40
+ _PROFILE_RESPONSE_HEADER_LOWERCASE = 'x-earth-engine-computation-profile'
41
+
42
+ # The HTTP header through which profiling is requested when using the Cloud API.
43
+ _PROFILE_REQUEST_HEADER = 'X-Earth-Engine-Computation-Profiling'
44
+
45
+ # The HTTP header through which a user project override is provided.
46
+ _USER_PROJECT_OVERRIDE_HEADER = 'X-Goog-User-Project'
47
+
48
+ # The HTTP header used to indicate the version of the client library used.
49
+ _API_CLIENT_VERSION_HEADER = 'X-Goog-Api-Client'
50
+
51
+ # The HTTP header used to indicate the user agent.
52
+ _USER_AGENT_HEADER = 'user-agent'
53
+
54
+ # Optional HTTP header returned to display initialization-time messages.
55
+ _INIT_MESSAGE_HEADER = 'x-earth-engine-init-message' # lowercase for httplib2
56
+
57
+ # Maximum time to wait before retrying a rate-limited request (in milliseconds).
58
+ MAX_RETRY_WAIT = 120000
59
+
60
+ # Base time (in ms) to wait when performing exponential backoff in request
61
+ # retries.
62
+ BASE_RETRY_WAIT = 1000
63
+
64
+ # The default base URL for API calls.
65
+ DEFAULT_API_BASE_URL = 'https://earthengine.googleapis.com/api'
66
+ HIGH_VOLUME_API_BASE_URL = 'https://earthengine-highvolume.googleapis.com'
67
+
68
+ # The default base URL for media/tile calls.
69
+ DEFAULT_TILE_BASE_URL = 'https://earthengine.googleapis.com'
70
+
71
+ # The default base URL for Cloud API calls.
72
+ DEFAULT_CLOUD_API_BASE_URL = 'https://earthengine.googleapis.com'
73
+
74
+ # The default project to use for Cloud API calls.
75
+ DEFAULT_CLOUD_API_USER_PROJECT = 'earthengine-legacy'
76
+
77
+ # Asset types recognized by create_assets().
78
+ ASSET_TYPE_FOLDER = 'Folder'
79
+ ASSET_TYPE_IMAGE_COLL = 'ImageCollection'
80
+ # Cloud API versions of the asset types.
81
+ ASSET_TYPE_FOLDER_CLOUD = 'FOLDER'
82
+ ASSET_TYPE_IMAGE_COLL_CLOUD = 'IMAGE_COLLECTION'
83
+ # Max length of the above type names
84
+ MAX_TYPE_LENGTH = len(ASSET_TYPE_IMAGE_COLL_CLOUD)
85
+
86
+ # The maximum number of tasks to retrieve in each request to "/tasklist".
87
+ _TASKLIST_PAGE_SIZE = 500
88
+
89
+ # Next page token key for list endpoints.
90
+ _NEXT_PAGE_TOKEN_KEY = 'nextPageToken'
91
+
92
+ _NOT_INITIALIZED_MESSAGE = (
93
+ 'Earth Engine client library not initialized. See http://goo.gle/ee-auth.'
94
+ )
95
+
38
96
  # OAuth2 credentials object. This may be set by ee.Initialize().
39
97
  _credentials: Optional[credentials_lib.Credentials] = None
40
98
 
@@ -60,7 +118,7 @@ _cloud_api_resource = None
60
118
  _cloud_api_resource_raw = None
61
119
 
62
120
  # The default user project to use when making Cloud API calls.
63
- _cloud_api_user_project: Optional[str] = None
121
+ _cloud_api_user_project: str = DEFAULT_CLOUD_API_USER_PROJECT
64
122
 
65
123
  # The API client version number to send when making requests.
66
124
  _cloud_api_client_version: Optional[str] = None
@@ -102,64 +160,6 @@ class _ThreadLocals(threading.local):
102
160
 
103
161
  _thread_locals = _ThreadLocals()
104
162
 
105
- # The HTTP header through which profile results are returned.
106
- # Lowercase because that's how httplib2 does things.
107
- _PROFILE_RESPONSE_HEADER_LOWERCASE = 'x-earth-engine-computation-profile'
108
-
109
- # The HTTP header through which profiling is requested when using the Cloud API.
110
- _PROFILE_REQUEST_HEADER = 'X-Earth-Engine-Computation-Profiling'
111
-
112
- # The HTTP header through which a user project override is provided.
113
- _USER_PROJECT_OVERRIDE_HEADER = 'X-Goog-User-Project'
114
-
115
- # The HTTP header used to indicate the version of the client library used.
116
- _API_CLIENT_VERSION_HEADER = 'X-Goog-Api-Client'
117
-
118
- # The HTTP header used to indicate the user agent.
119
- _USER_AGENT_HEADER = 'user-agent'
120
-
121
- # Optional HTTP header returned to display initialization-time messages.
122
- _INIT_MESSAGE_HEADER = 'x-earth-engine-init-message' # lowercase for httplib2
123
-
124
- # Maximum time to wait before retrying a rate-limited request (in milliseconds).
125
- MAX_RETRY_WAIT = 120000
126
-
127
- # Base time (in ms) to wait when performing exponential backoff in request
128
- # retries.
129
- BASE_RETRY_WAIT = 1000
130
-
131
- # The default base URL for API calls.
132
- DEFAULT_API_BASE_URL = 'https://earthengine.googleapis.com/api'
133
- HIGH_VOLUME_API_BASE_URL = 'https://earthengine-highvolume.googleapis.com'
134
-
135
- # The default base URL for media/tile calls.
136
- DEFAULT_TILE_BASE_URL = 'https://earthengine.googleapis.com'
137
-
138
- # The default base URL for Cloud API calls.
139
- DEFAULT_CLOUD_API_BASE_URL = 'https://earthengine.googleapis.com'
140
-
141
- # The default project to use for Cloud API calls.
142
- DEFAULT_CLOUD_API_USER_PROJECT = 'earthengine-legacy'
143
-
144
- # Asset types recognized by create_assets().
145
- ASSET_TYPE_FOLDER = 'Folder'
146
- ASSET_TYPE_IMAGE_COLL = 'ImageCollection'
147
- # Cloud API versions of the asset types.
148
- ASSET_TYPE_FOLDER_CLOUD = 'FOLDER'
149
- ASSET_TYPE_IMAGE_COLL_CLOUD = 'IMAGE_COLLECTION'
150
- # Max length of the above type names
151
- MAX_TYPE_LENGTH = len(ASSET_TYPE_IMAGE_COLL_CLOUD)
152
-
153
- # The maximum number of tasks to retrieve in each request to "/tasklist".
154
- _TASKLIST_PAGE_SIZE = 500
155
-
156
- # Next page token key for list endpoints.
157
- _NEXT_PAGE_TOKEN_KEY = 'nextPageToken'
158
-
159
- _NOT_INITIALIZED_MESSAGE = (
160
- 'Earth Engine client library not initialized. See http://goo.gle/ee-auth.'
161
- )
162
-
163
163
 
164
164
  def initialize(
165
165
  credentials: Any = None,
@@ -231,9 +231,8 @@ def initialize(
231
231
 
232
232
  if project is not None:
233
233
  _cloud_api_user_project = project
234
- _cloud_api_utils.set_cloud_api_user_project(project)
235
234
  else:
236
- _cloud_api_utils.set_cloud_api_user_project(DEFAULT_CLOUD_API_USER_PROJECT)
235
+ _cloud_api_user_project = DEFAULT_CLOUD_API_USER_PROJECT
237
236
 
238
237
  _initialized = True
239
238
 
@@ -302,18 +301,14 @@ def reset() -> None:
302
301
  _cloud_api_key = None
303
302
  _cloud_api_resource = None
304
303
  _cloud_api_resource_raw = None
305
- _cloud_api_user_project = None
306
- _cloud_api_utils.set_cloud_api_user_project(DEFAULT_CLOUD_API_USER_PROJECT)
304
+ _cloud_api_user_project = DEFAULT_CLOUD_API_USER_PROJECT
307
305
  _http_transport = None
308
306
  _initialized = False
309
307
 
310
308
 
311
309
  def _get_projects_path() -> str:
312
310
  """Returns the projects path to use for constructing a request."""
313
- if _cloud_api_user_project is not None:
314
- return 'projects/' + _cloud_api_user_project
315
- else:
316
- return 'projects/' + DEFAULT_CLOUD_API_USER_PROJECT
311
+ return f'projects/{_cloud_api_user_project}'
317
312
 
318
313
 
319
314
  def _install_cloud_api_resource() -> None:
@@ -372,7 +367,7 @@ def _make_request_headers() -> Optional[dict[str, Any]]:
372
367
  headers[_API_CLIENT_VERSION_HEADER] = ' '.join(client_version_header_values)
373
368
  if _thread_locals.profile_hook:
374
369
  headers[_PROFILE_REQUEST_HEADER] = '1'
375
- if _cloud_api_user_project is not None:
370
+ if _cloud_api_user_project is not DEFAULT_CLOUD_API_USER_PROJECT:
376
371
  headers[_USER_PROJECT_OVERRIDE_HEADER] = _cloud_api_user_project
377
372
  if headers:
378
373
  return headers
@@ -455,7 +450,6 @@ def setCloudApiKey(cloud_api_key: str) -> None:
455
450
  def setCloudApiUserProject(cloud_api_user_project: str) -> None:
456
451
  global _cloud_api_user_project
457
452
  _cloud_api_user_project = cloud_api_user_project
458
- _cloud_api_utils.set_cloud_api_user_project(_cloud_api_user_project)
459
453
 
460
454
 
461
455
  def setUserAgent(user_agent: str) -> None:
@@ -1683,7 +1677,8 @@ def getTaskStatus(taskId: Union[list[str], str]) -> list[Any]:
1683
1677
  """Retrieve status of one or more long-running tasks.
1684
1678
 
1685
1679
  Args:
1686
- taskId: ID of the task or a list of multiple IDs.
1680
+ taskId: ID of the task or a list of multiple IDs. These will be assumed to
1681
+ be running in the currently initialized project.
1687
1682
 
1688
1683
  Returns:
1689
1684
  List containing one object for each queried task, in the same order as
@@ -1698,19 +1693,22 @@ def getTaskStatus(taskId: Union[list[str], str]) -> list[Any]:
1698
1693
  taskId = [taskId]
1699
1694
  result = []
1700
1695
  for one_id in taskId:
1696
+ # Don't use getOperation as it will translate the exception, and we need
1697
+ # to handle 404s specially.
1698
+ name = _cloud_api_utils.convert_task_id_to_operation_name(
1699
+ _cloud_api_user_project, one_id
1700
+ )
1701
1701
  try:
1702
- # Don't use getOperation as it will translate the exception, and we need
1703
- # to handle 404s specially.
1704
1702
  operation = (
1705
1703
  _get_cloud_projects()
1706
1704
  .operations()
1707
- .get(name=_cloud_api_utils.convert_task_id_to_operation_name(one_id))
1705
+ .get(name=name)
1708
1706
  .execute(num_retries=_max_retries)
1709
1707
  )
1710
1708
  result.append(_cloud_api_utils.convert_operation_to_task(operation))
1711
1709
  except googleapiclient.errors.HttpError as e:
1712
1710
  if e.resp.status == 404:
1713
- result.append({'id': one_id, 'state': 'UNKNOWN'})
1711
+ result.append({'id': one_id, 'state': 'UNKNOWN', 'name': name})
1714
1712
  else:
1715
1713
  raise _translate_cloud_exception(e) # pylint: disable=raise-missing-from
1716
1714
  return result
@@ -1734,7 +1732,11 @@ def getOperation(operation_name: str) -> Any:
1734
1732
  @deprecation.Deprecated('Use cancelOperation')
1735
1733
  def cancelTask(taskId: str) -> None:
1736
1734
  """Cancels a batch task."""
1737
- cancelOperation(_cloud_api_utils.convert_task_id_to_operation_name(taskId))
1735
+ cancelOperation(
1736
+ _cloud_api_utils.convert_task_id_to_operation_name(
1737
+ _cloud_api_user_project, taskId
1738
+ )
1739
+ )
1738
1740
 
1739
1741
 
1740
1742
  def cancelOperation(operation_name: str) -> None:
@@ -16,10 +16,6 @@ from ee import ee_exception
16
16
 
17
17
  class CloudApiUtilsTest(unittest.TestCase):
18
18
 
19
- def setUp(self):
20
- super().setUp()
21
- _cloud_api_utils.set_cloud_api_user_project('earthengine-legacy')
22
-
23
19
  def test_build_cloud_resource(self):
24
20
  base = 'https://earthengine.basetest'
25
21
  path = '$discovery/rest?version=v1&prettyPrint=false'
@@ -171,7 +167,10 @@ class CloudApiUtilsTest(unittest.TestCase):
171
167
  def test_convert_task_id_to_operation_name(self):
172
168
  self.assertEqual(
173
169
  'projects/earthengine-legacy/operations/taskId',
174
- _cloud_api_utils.convert_task_id_to_operation_name('taskId'))
170
+ _cloud_api_utils.convert_task_id_to_operation_name(
171
+ 'earthengine-legacy', 'taskId'
172
+ ),
173
+ )
175
174
 
176
175
  def test_encode_number_as_cloud_value(self):
177
176
  self.assertEqual({
ee/tests/_helpers_test.py CHANGED
@@ -7,14 +7,67 @@ name since that is the name we want to ensure works.
7
7
  """
8
8
 
9
9
  import io
10
+ import json
10
11
  import unittest
12
+ from unittest import mock
11
13
 
12
14
  import unittest
13
15
  import ee
16
+ from ee import _helpers
14
17
  from ee import apifunction
15
18
  from ee import apitestcase
16
19
  from ee import computedobject
17
20
  from ee import ee_exception
21
+ from ee import oauth
22
+
23
+
24
+ class ServiceAccountCredentialsTest(unittest.TestCase):
25
+
26
+ def testNoArgs(self):
27
+ with self.assertRaisesRegex(ValueError, 'At least one of'):
28
+ ee.ServiceAccountCredentials()
29
+
30
+ @mock.patch('google.oauth2.service_account.Credentials')
31
+ def testJsonFile(self, mock_credentials):
32
+ ee.ServiceAccountCredentials(key_file='foo.json')
33
+ mock_credentials.from_service_account_file.assert_called_with(
34
+ 'foo.json', scopes=oauth.SCOPES
35
+ )
36
+
37
+ @mock.patch('google.oauth2.service_account.Credentials')
38
+ def testJsonKeyData(self, mock_credentials):
39
+ key_data = {'client_email': 'foo@bar.com'}
40
+ ee.ServiceAccountCredentials(key_data=json.dumps(key_data))
41
+ mock_credentials.from_service_account_info.assert_called_with(
42
+ key_data, scopes=oauth.SCOPES
43
+ )
44
+
45
+ @mock.patch('google.auth.crypt.RSASigner')
46
+ @mock.patch('google.oauth2.service_account.Credentials')
47
+ def testPemKeyData(self, mock_credentials, mock_signer):
48
+ ee.ServiceAccountCredentials(email='foo@bar.com', key_data='pem_key_data')
49
+ mock_signer.from_string.assert_called_with('pem_key_data')
50
+ self.assertEqual(
51
+ mock_credentials.call_args[0][1], 'foo@bar.com'
52
+ )
53
+
54
+ @mock.patch('google.auth.crypt.RSASigner')
55
+ @mock.patch('google.oauth2.service_account.Credentials')
56
+ def testPemFile(self, mock_credentials, mock_signer):
57
+ with mock.patch.object(
58
+ _helpers, 'open', mock.mock_open(read_data='pem_key_data')
59
+ ):
60
+ ee.ServiceAccountCredentials(email='foo@bar.com', key_file='foo.pem')
61
+ mock_signer.from_string.assert_called_with('pem_key_data')
62
+ self.assertEqual(
63
+ mock_credentials.call_args[0][1], 'foo@bar.com'
64
+ )
65
+
66
+ def testBadJsonKeyData(self):
67
+ # This causes a different failure based on where the test is run.
68
+ message = r'Could not deserialize key data|No key could be detected'
69
+ with self.assertRaisesRegex(ValueError, message):
70
+ ee.ServiceAccountCredentials(key_data='not json')
18
71
 
19
72
 
20
73
  class ProfilingTest(apitestcase.ApiTestCase):
ee/tests/batch_test.py CHANGED
@@ -72,7 +72,8 @@ class TaskTest(unittest.TestCase):
72
72
  task.start()
73
73
 
74
74
  def testStatusWithId(self):
75
- task = batch.Task('test_1', 'a task type', 'a state')
75
+ name = 'projects/test-project/operations/test_1'
76
+ task = batch.Task('an id', 'a task type', 'a state', name=name)
76
77
  with mock.patch.object(
77
78
  data, 'getOperation', return_value=RUNNING_OPERATION
78
79
  ) as m:
@@ -97,7 +98,8 @@ class TaskTest(unittest.TestCase):
97
98
  )
98
99
 
99
100
  def testStatusWithIdStateUnknown(self):
100
- task = batch.Task('an id', 'a task type', 'a state')
101
+ name = 'projects/test-project/operations/an id'
102
+ task = batch.Task('an id', 'a task type', 'a state', name=name)
101
103
  with mock.patch.object(
102
104
  data, 'getOperation', return_value=UNKNOWN_OPERATION
103
105
  ) as m:
@@ -111,7 +113,8 @@ class TaskTest(unittest.TestCase):
111
113
  self.assertEqual('UNSUBMITTED', task.status()['state'])
112
114
 
113
115
  def testActive(self):
114
- task = batch.Task('an id', 'a task type', 'a state')
116
+ name = 'projects/test-project/operations/an id'
117
+ task = batch.Task('an id', 'a task type', 'a state', name=name)
115
118
  with mock.patch.object(
116
119
  data, 'getOperation', return_value=RUNNING_OPERATION
117
120
  ):
ee/tests/blob_test.py CHANGED
@@ -75,9 +75,9 @@ class BlobTest(apitestcase.ApiTestCase):
75
75
  self.assertEqual({'value': 'fakeValue'}, result.getInfo())
76
76
 
77
77
  def test_wrong_arg_type(self):
78
- message = 'Blob url must be a string: <class \'int\'> -> "123"'
78
+ message = r'url must be a string or ComputedObject: <class \'int\'> -> "13"'
79
79
  with self.assertRaisesRegex(ValueError, message):
80
- ee.Blob(123) # pytype: disable=wrong-arg-types
80
+ ee.Blob(13) # pytype: disable=wrong-arg-types
81
81
 
82
82
  def test_does_not_start_with_gs(self):
83
83
  url = 'http://example.com/something'
ee/tests/data_test.py CHANGED
@@ -49,6 +49,7 @@ class DataTest(unittest.TestCase):
49
49
 
50
50
  def tearDown(self):
51
51
  super().tearDown()
52
+ ee.data.reset()
52
53
  mock.patch.stopall()
53
54
 
54
55
  def testIsInitialized(self):
@@ -56,6 +57,27 @@ class DataTest(unittest.TestCase):
56
57
  with apitestcase.UsingCloudApi():
57
58
  self.assertTrue(ee.data.is_initialized())
58
59
 
60
+ @mock.patch.object(ee.data, '_install_cloud_api_resource', return_value=None)
61
+ def testInitialize(self, mock_install_cloud_api_resource):
62
+ ee.data.initialize()
63
+
64
+ self.assertTrue(ee.data.is_initialized())
65
+ mock_install_cloud_api_resource.assert_called_once()
66
+
67
+ @mock.patch.object(ee.data, '_install_cloud_api_resource', return_value=None)
68
+ def testInitializeWithProject(self, unused_mock_install_cloud_api_resource):
69
+ ee.data.initialize(project='my-project')
70
+
71
+ self.assertTrue(ee.data.is_initialized())
72
+ self.assertEqual(ee.data._cloud_api_user_project, 'my-project')
73
+
74
+ @mock.patch.object(ee.data, '_install_cloud_api_resource', return_value=None)
75
+ def testInitializeWithNoProject(self, unused_mock_install_cloud_api_resource):
76
+ ee.data.initialize()
77
+
78
+ self.assertTrue(ee.data.is_initialized())
79
+ self.assertEqual(ee.data._cloud_api_user_project, 'earthengine-legacy')
80
+
59
81
  def testSetMaxRetries_badValues(self):
60
82
  with self.assertRaises(ValueError):
61
83
  ee.data.setMaxRetries(-1)
@@ -108,6 +130,112 @@ class DataTest(unittest.TestCase):
108
130
  with apitestcase.UsingCloudApi(mock_http=mock_http):
109
131
  self.assertEqual([], ee.data.listOperations())
110
132
 
133
+ def testGetOperation(self):
134
+ cloud_api_resource = mock.MagicMock()
135
+ with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource):
136
+ name = 'projects/test-project/operations/foo'
137
+ cloud_api_resource.projects().operations().get.execute.return_value = {
138
+ 'name': name,
139
+ 'done': False,
140
+ }
141
+ ee.data.getOperation(name)
142
+ cloud_api_resource.projects().operations().get.assert_called_once_with(
143
+ name=name
144
+ )
145
+
146
+ def testGetTaskStatus(self):
147
+ cloud_api_resource = mock.MagicMock()
148
+ with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource):
149
+ cloud_api_resource.projects().operations().get.return_value.execute.return_value = {
150
+ 'name': 'projects/earthengine-legacy/operations/foo',
151
+ 'done': False,
152
+ 'metadata': {'state': 'RUNNING'},
153
+ }
154
+ result = ee.data.getTaskStatus('foo')
155
+ cloud_api_resource.projects().operations().get.assert_called_once_with(
156
+ name='projects/earthengine-legacy/operations/foo'
157
+ )
158
+ self.assertEqual(
159
+ result,
160
+ [{
161
+ 'id': 'foo',
162
+ 'state': 'RUNNING',
163
+ 'name': 'projects/earthengine-legacy/operations/foo',
164
+ }],
165
+ )
166
+
167
+ def testGetTaskStatus_withNotFound(self):
168
+ cloud_api_resource = mock.MagicMock()
169
+ with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource):
170
+ cloud_api_resource.projects().operations().get.return_value.execute.side_effect = [
171
+ {
172
+ 'name': 'projects/earthengine-legacy/operations/foo',
173
+ 'done': False,
174
+ 'metadata': {'state': 'RUNNING'},
175
+ },
176
+ NotFoundError(),
177
+ {
178
+ 'name': 'projects/earthengine-legacy/operations/bar',
179
+ 'done': True,
180
+ 'metadata': {'state': 'SUCCEEDED'},
181
+ },
182
+ ]
183
+ result = ee.data.getTaskStatus(['foo', 'missing', 'bar'])
184
+ cloud_api_resource.projects().operations().get.assert_has_calls([
185
+ mock.call(name='projects/earthengine-legacy/operations/foo'),
186
+ mock.call().execute(num_retries=5),
187
+ mock.call(name='projects/earthengine-legacy/operations/missing'),
188
+ mock.call().execute(num_retries=5),
189
+ mock.call(name='projects/earthengine-legacy/operations/bar'),
190
+ mock.call().execute(num_retries=5),
191
+ ])
192
+ self.assertEqual(
193
+ 3,
194
+ cloud_api_resource.projects()
195
+ .operations()
196
+ .get.return_value.execute.call_count,
197
+ )
198
+ self.assertEqual(
199
+ result,
200
+ [
201
+ {
202
+ 'id': 'foo',
203
+ 'state': 'RUNNING',
204
+ 'name': 'projects/earthengine-legacy/operations/foo',
205
+ },
206
+ {
207
+ 'id': 'missing',
208
+ 'state': 'UNKNOWN',
209
+ 'name': 'projects/earthengine-legacy/operations/missing',
210
+ },
211
+ {
212
+ 'id': 'bar',
213
+ 'state': 'COMPLETED',
214
+ 'name': 'projects/earthengine-legacy/operations/bar',
215
+ },
216
+ ],
217
+ )
218
+
219
+ def testCancelOperation(self):
220
+ cloud_api_resource = mock.MagicMock()
221
+ with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource):
222
+ cancel_mock = cloud_api_resource.projects().operations().cancel
223
+ cancel_mock.execute.return_value = {}
224
+ ee.data.cancelOperation('projects/test-project/operations/foo')
225
+ cancel_mock.assert_called_once_with(
226
+ name='projects/test-project/operations/foo', body={}
227
+ )
228
+
229
+ def testCancelTask(self):
230
+ cloud_api_resource = mock.MagicMock()
231
+ with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource):
232
+ cancel_mock = cloud_api_resource.projects().operations().cancel
233
+ cancel_mock.execute.return_value = {}
234
+ ee.data.cancelTask('foo')
235
+ cancel_mock.assert_called_once_with(
236
+ name='projects/earthengine-legacy/operations/foo', body={}
237
+ )
238
+
111
239
  def testCreateAsset(self):
112
240
  cloud_api_resource = mock.MagicMock()
113
241
  with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource):
ee/tests/ee_test.py CHANGED
@@ -49,7 +49,7 @@ class EETestCase(apitestcase.ApiTestCase):
49
49
  ee.Reset()
50
50
  self.assertFalse(ee.data._initialized)
51
51
  self.assertIsNone(ee.data._api_base_url)
52
- self.assertIsNone(ee.data._cloud_api_user_project)
52
+ self.assertEqual(ee.data._cloud_api_user_project, 'earthengine-legacy')
53
53
  self.assertEqual(ee.ApiFunction._api, {})
54
54
  self.assertFalse(ee.Image._initialized)
55
55