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

Files changed (48) hide show
  1. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12rc0.dist-info}/METADATA +1 -1
  2. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12rc0.dist-info}/RECORD +48 -46
  3. ee/__init__.py +5 -5
  4. ee/_cloud_api_utils.py +33 -10
  5. ee/_state.py +105 -0
  6. ee/apifunction.py +1 -1
  7. ee/apitestcase.py +15 -21
  8. ee/batch.py +1 -1
  9. ee/cli/commands.py +153 -63
  10. ee/cli/eecli.py +1 -1
  11. ee/cli/utils.py +25 -15
  12. ee/collection.py +27 -18
  13. ee/computedobject.py +5 -5
  14. ee/customfunction.py +3 -3
  15. ee/data.py +104 -210
  16. ee/ee_array.py +4 -2
  17. ee/ee_number.py +1 -1
  18. ee/ee_string.py +18 -26
  19. ee/ee_types.py +2 -2
  20. ee/element.py +1 -1
  21. ee/featurecollection.py +10 -7
  22. ee/filter.py +2 -2
  23. ee/geometry.py +20 -21
  24. ee/image.py +7 -12
  25. ee/imagecollection.py +3 -3
  26. ee/mapclient.py +9 -9
  27. ee/oauth.py +13 -6
  28. ee/tests/_cloud_api_utils_test.py +16 -0
  29. ee/tests/_helpers_test.py +9 -9
  30. ee/tests/_state_test.py +49 -0
  31. ee/tests/apifunction_test.py +5 -5
  32. ee/tests/batch_test.py +61 -50
  33. ee/tests/collection_test.py +13 -13
  34. ee/tests/data_test.py +65 -60
  35. ee/tests/dictionary_test.py +9 -9
  36. ee/tests/ee_number_test.py +32 -26
  37. ee/tests/ee_string_test.py +8 -0
  38. ee/tests/ee_test.py +37 -19
  39. ee/tests/element_test.py +2 -2
  40. ee/tests/feature_test.py +6 -6
  41. ee/tests/function_test.py +5 -5
  42. ee/tests/geometry_test.py +73 -51
  43. ee/tests/oauth_test.py +21 -2
  44. ee/tests/serializer_test.py +8 -8
  45. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12rc0.dist-info}/WHEEL +0 -0
  46. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12rc0.dist-info}/entry_points.txt +0 -0
  47. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12rc0.dist-info}/licenses/LICENSE +0 -0
  48. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12rc0.dist-info}/top_level.txt +0 -0
ee/cli/commands.py CHANGED
@@ -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
 
ee/cli/eecli.py CHANGED
@@ -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.',
ee/cli/utils.py CHANGED
@@ -40,12 +40,6 @@ CONFIG_PARAMS: dict[str, Union[str, list[str], None]] = {
40
40
  'url': 'https://earthengine.googleapis.com',
41
41
  }
42
42
 
43
- TASK_FINISHED_STATES: tuple[str, str, str] = (
44
- ee.batch.Task.State.COMPLETED,
45
- ee.batch.Task.State.FAILED,
46
- ee.batch.Task.State.CANCELLED,
47
- )
48
-
49
43
 
50
44
  class CommandLineConfig:
51
45
  """Holds the configuration parameters used by the EE command line interface.
@@ -248,6 +242,15 @@ def truncate(string: str, length: int) -> str:
248
242
  return string
249
243
 
250
244
 
245
+ def _task_id_to_operation_name(task_id: str) -> str:
246
+ """Converts a task ID to an operation name."""
247
+ # pylint: disable=protected-access
248
+ return ee._cloud_api_utils.convert_task_id_to_operation_name(
249
+ ee.data._get_state().cloud_api_user_project, task_id
250
+ )
251
+ # pylint: enable=protected-access
252
+
253
+
251
254
  def wait_for_task(
252
255
  task_id: str, timeout: float, log_progress: bool = True
253
256
  ) -> None:
@@ -257,10 +260,10 @@ def wait_for_task(
257
260
  last_check = 0
258
261
  while True:
259
262
  elapsed = time.time() - start
260
- status = ee.data.getTaskStatus(task_id)[0]
261
- state = status['state']
262
- if state in TASK_FINISHED_STATES:
263
- error_message = status.get('error_message', None)
263
+ status = ee.data.getOperation(_task_id_to_operation_name(task_id))
264
+ state = status['metadata']['state']
265
+ if status.get('done', False):
266
+ error_message = status.get('error', {}).get('message')
264
267
  print('Task %s ended at state: %s after %.2f seconds'
265
268
  % (task_id, state, elapsed))
266
269
  if error_message:
@@ -299,14 +302,21 @@ def wait_for_tasks(
299
302
  for thread in threads:
300
303
  thread.join()
301
304
 
302
- status_list = ee.data.getTaskStatus(task_id_list)
305
+ get_state = lambda task_id: ee.data.getOperation(
306
+ _task_id_to_operation_name(task_id)
307
+ )['metadata']['state']
308
+ status_list = [get_state(task_id) for task_id in task_id_list]
303
309
  status_counts = collections.defaultdict(int)
304
310
  for status in status_list:
305
- status_counts[status['state']] += 1
306
- num_incomplete = (len(status_list) - status_counts['COMPLETED']
307
- - status_counts['FAILED'] - status_counts['CANCELLED'])
311
+ status_counts[status] += 1
312
+ num_incomplete = (
313
+ len(status_list)
314
+ - status_counts['SUCCEEDED']
315
+ - status_counts['FAILED']
316
+ - status_counts['CANCELLED']
317
+ )
308
318
  print('Finished waiting for tasks.\n Status summary:')
309
- print(' %d tasks completed successfully.' % status_counts['COMPLETED'])
319
+ print(' %d tasks completed successfully.' % status_counts['SUCCEEDED'])
310
320
  print(' %d tasks failed.' % status_counts['FAILED'])
311
321
  print(' %d tasks cancelled.' % status_counts['CANCELLED'])
312
322
  print(' %d tasks are still incomplete (timed-out)' % num_incomplete)
ee/collection.py CHANGED
@@ -6,7 +6,7 @@ This class is never intended to be instantiated by the user.
6
6
  from __future__ import annotations
7
7
 
8
8
  import datetime
9
- from typing import Any, Callable
9
+ from typing import Any, Callable, Generic, TypeVar
10
10
 
11
11
  from ee import _arg_types
12
12
  from ee import _utils
@@ -26,8 +26,14 @@ from ee import function
26
26
  from ee import geometry as ee_geometry
27
27
  from ee import image
28
28
 
29
+ # TODO: Remove this TypeVar and replace with typing.Self once
30
+ # Python 3.11 is the minimum version.
31
+ T = TypeVar('T', bound='Collection')
29
32
 
30
- class Collection(element.Element):
33
+ ElementType = TypeVar('ElementType')
34
+
35
+
36
+ class Collection(Generic[ElementType], element.Element):
31
37
  """Base class for ImageCollection and FeatureCollection."""
32
38
 
33
39
  _initialized = False
@@ -442,7 +448,7 @@ class Collection(element.Element):
442
448
  'Collection.errorMatrix', self, actual, predicted, order
443
449
  )
444
450
 
445
- def filter(self, new_filter: str | ee_filter.Filter) -> Any:
451
+ def filter(self: T, new_filter: str | ee_filter.Filter) -> T:
446
452
  """Apply a filter to this collection.
447
453
 
448
454
  Args:
@@ -459,8 +465,8 @@ class Collection(element.Element):
459
465
 
460
466
  @deprecation.CanUseDeprecated
461
467
  def filterMetadata(
462
- self, name: str, operator: str, value: int | str
463
- ) -> Any:
468
+ self: T, name: str, operator: str, value: int | str
469
+ ) -> T:
464
470
  """Shortcut to add a metadata filter to a collection.
465
471
 
466
472
  This is equivalent to self.filter(Filter().metadata(...)).
@@ -468,7 +474,7 @@ class Collection(element.Element):
468
474
  Args:
469
475
  name: Name of a property to filter.
470
476
  operator: Name of a comparison operator as defined
471
- by FilterCollection. Possible values are: "equals", "less_than",
477
+ by FilterCollection. Possible values are: "equals", "less_than",
472
478
  "greater_than", "not_equals", "not_less_than", "not_greater_than",
473
479
  "starts_with", "ends_with", "not_starts_with", "not_ends_with",
474
480
  "contains", "not_contains".
@@ -480,8 +486,13 @@ class Collection(element.Element):
480
486
  return self.filter(ee_filter.Filter.metadata_(name, operator, value))
481
487
 
482
488
  def filterBounds(
483
- self, geometry: dict[str, Any] | ee_geometry.Geometry
484
- ) -> Any:
489
+ self: T,
490
+ geometry: (
491
+ dict[str, Any]
492
+ | ee_geometry.Geometry
493
+ | featurecollection.FeatureCollection
494
+ ),
495
+ ) -> T:
485
496
  """Shortcut to add a geometry filter to a collection.
486
497
 
487
498
  Items in the collection with a footprint that fails to intersect
@@ -505,12 +516,10 @@ class Collection(element.Element):
505
516
  # TODO(user): Any --> DateRange
506
517
  @_utils.accept_opt_prefix('opt_end')
507
518
  def filterDate(
508
- self,
519
+ self: T,
509
520
  start: datetime.datetime | ee_date.Date | int | str | Any,
510
- end: None | (
511
- datetime.datetime | ee_date.Date | int | str | Any
512
- ) = None,
513
- ) -> Any:
521
+ end: None | (datetime.datetime | ee_date.Date | int | str | Any) = None,
522
+ ) -> T:
514
523
  """Shortcut to filter a collection with a date range.
515
524
 
516
525
  Items in the collection with a system:time_start property that doesn't
@@ -529,7 +538,7 @@ class Collection(element.Element):
529
538
  """
530
539
  return self.filter(ee_filter.Filter.date(start, end))
531
540
 
532
- def first(self) -> element.Element:
541
+ def first(self) -> ElementType:
533
542
  """Returns the first entry from a given collection."""
534
543
 
535
544
  return apifunction.ApiFunction.call_('Collection.first', self)
@@ -660,10 +669,10 @@ class Collection(element.Element):
660
669
  # TODO(user): Can dropNulls default to False?
661
670
  @_utils.accept_opt_prefix('opt_dropNulls')
662
671
  def map(
663
- self,
672
+ self: T,
664
673
  algorithm: Callable[[Any], Any],
665
674
  dropNulls: bool | None = None, # pylint: disable=invalid-name
666
- ) -> Any:
675
+ ) -> T:
667
676
  """Maps an algorithm over a collection.
668
677
 
669
678
  Args:
@@ -822,12 +831,12 @@ class Collection(element.Element):
822
831
 
823
832
  # TODO(user): Make ascending default to True
824
833
  @_utils.accept_opt_prefix('opt_ascending')
825
- def sort(self, prop: str, ascending: bool | None = None) -> Any:
834
+ def sort(self: T, prop: str, ascending: bool | None = None) -> T:
826
835
  """Sort a collection by the specified property.
827
836
 
828
837
  Args:
829
838
  prop: The property to sort by.
830
- ascending: Whether to sort in ascending or descending order. The default
839
+ ascending: Whether to sort in ascending or descending order. The default
831
840
  is true (ascending).
832
841
 
833
842
  Returns:
ee/computedobject.py CHANGED
@@ -38,11 +38,11 @@ class ComputedObject(encodable.Encodable, metaclass=ComputedObjectMetaclass):
38
38
  necessary to interact well with the rest of the API.
39
39
 
40
40
  ComputedObjects come in two flavors:
41
- 1. If func != null and args != null, the ComputedObject is encoded as an
42
- invocation of func with args.
43
- 2. If func == null and args == null, the ComputedObject is a variable
41
+ 1. If func is not None and args is not None, the ComputedObject is encoded as
42
+ an invocation of func with args.
43
+ 2. If func is None and args is None, the ComputedObject is a variable
44
44
  reference. The variable name is stored in its varName member. Note that
45
- in this case, varName may still be null; this allows the name to be
45
+ in this case, varName may still be None; this allows the name to be
46
46
  deterministically generated at a later time. This is used to generate
47
47
  deterministic variable names for mapped functions, ensuring that nested
48
48
  mapping calls do not use the same variable name.
@@ -184,7 +184,7 @@ class ComputedObject(encodable.Encodable, metaclass=ComputedObjectMetaclass):
184
184
 
185
185
  def __str__(self) -> str:
186
186
  """Writes out the object in a human-readable form."""
187
- return 'ee.{}({})'.format(self.name(), serializer.toReadableJSON(self))
187
+ return f'ee.{self.name()}({serializer.toReadableJSON(self)})'
188
188
 
189
189
  def isVariable(self) -> bool:
190
190
  """Returns whether this computed object is a variable reference."""
ee/customfunction.py CHANGED
@@ -171,19 +171,19 @@ class CustomFunction(function.Function, encodable.Encodable):
171
171
  return CountNodes(expression['values'].values())
172
172
 
173
173
  # There are three function building phases, which each call body():
174
- # 1 - Check Return. The constructor verifies that body() returns a result,
174
+ # 1 - Check Return. The constructor verifies that body() returns a result,
175
175
  # but does not try to serialize the result. If the function tries to use
176
176
  # unbound variables (e.g., using .getInfo() or print()), ComputedObject will
177
177
  # throw an exception when these calls try to serialize themselves, so that
178
178
  # unbound variables are not passed in server calls.
179
- # 2 - Count Functions. We serialize the result here. At this point all
179
+ # 2 - Count Functions. We serialize the result here. At this point all
180
180
  # variables must have names for serialization to succeed, but we don't yet
181
181
  # know the correct function depth. So we serialize with unbound_name set to
182
182
  # '<unbound>', which should silently succeed. If this does end up in server
183
183
  # calls, the function is very unusual: the first call doesn't use unbound
184
184
  # variables but the second call does. In this rare case we will return
185
185
  # server errors complaining about <unbound>.
186
- # 3 - Final Serialize. Finally, the constructor calls body() with the
186
+ # 3 - Final Serialize. Finally, the constructor calls body() with the
187
187
  # correct, depth-dependent names, which are used when the CustomFunction
188
188
  # is serialized and sent to the server.
189
189
  serialized_body = serializer.encode(