earthengine-api 1.5.13rc0__py3-none-any.whl → 1.7.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of earthengine-api might be problematic. Click here for more details.

Files changed (102) hide show
  1. {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/METADATA +3 -3
  2. earthengine_api-1.7.4.dist-info/RECORD +109 -0
  3. {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/WHEEL +1 -1
  4. ee/__init__.py +29 -28
  5. ee/_arg_types.py +7 -6
  6. ee/_cloud_api_utils.py +95 -78
  7. ee/_helpers.py +17 -13
  8. ee/_state.py +105 -0
  9. ee/_utils.py +2 -1
  10. ee/apifunction.py +21 -19
  11. ee/apitestcase.py +33 -38
  12. ee/batch.py +87 -77
  13. ee/blob.py +10 -12
  14. ee/classifier.py +57 -59
  15. ee/cli/commands.py +178 -114
  16. ee/cli/eecli.py +1 -1
  17. ee/cli/utils.py +61 -42
  18. ee/clusterer.py +39 -41
  19. ee/collection.py +64 -54
  20. ee/computedobject.py +19 -16
  21. ee/confusionmatrix.py +9 -9
  22. ee/customfunction.py +13 -12
  23. ee/data.py +220 -322
  24. ee/daterange.py +10 -10
  25. ee/deprecation.py +21 -13
  26. ee/deserializer.py +25 -20
  27. ee/dictionary.py +11 -11
  28. ee/ee_array.py +22 -20
  29. ee/ee_date.py +23 -23
  30. ee/ee_list.py +15 -16
  31. ee/ee_number.py +11 -21
  32. ee/ee_string.py +24 -32
  33. ee/ee_types.py +4 -4
  34. ee/element.py +15 -15
  35. ee/encodable.py +7 -4
  36. ee/errormargin.py +4 -4
  37. ee/feature.py +68 -71
  38. ee/featurecollection.py +41 -40
  39. ee/filter.py +90 -92
  40. ee/function.py +8 -8
  41. ee/geometry.py +95 -93
  42. ee/image.py +238 -236
  43. ee/image_converter.py +4 -4
  44. ee/imagecollection.py +30 -27
  45. ee/join.py +13 -15
  46. ee/kernel.py +55 -57
  47. ee/mapclient.py +9 -9
  48. ee/model.py +29 -31
  49. ee/oauth.py +76 -63
  50. ee/pixeltype.py +6 -6
  51. ee/projection.py +5 -4
  52. ee/reducer.py +41 -41
  53. ee/serializer.py +14 -14
  54. ee/table_converter.py +7 -6
  55. ee/terrain.py +7 -9
  56. ee/tests/_cloud_api_utils_test.py +21 -6
  57. ee/tests/_helpers_test.py +57 -4
  58. ee/tests/_state_test.py +49 -0
  59. ee/tests/algorithms.json +85 -2
  60. ee/tests/apifunction_test.py +5 -5
  61. ee/tests/batch_test.py +135 -57
  62. ee/tests/blob_test.py +5 -5
  63. ee/tests/classifier_test.py +3 -3
  64. ee/tests/clusterer_test.py +3 -3
  65. ee/tests/collection_test.py +48 -13
  66. ee/tests/confusionmatrix_test.py +3 -3
  67. ee/tests/data_test.py +484 -55
  68. ee/tests/daterange_test.py +4 -4
  69. ee/tests/deprecation_test.py +6 -4
  70. ee/tests/deserializer_test.py +64 -5
  71. ee/tests/dictionary_test.py +12 -12
  72. ee/tests/ee_array_test.py +3 -3
  73. ee/tests/ee_date_test.py +4 -4
  74. ee/tests/ee_list_test.py +3 -3
  75. ee/tests/ee_number_test.py +75 -30
  76. ee/tests/ee_string_test.py +11 -3
  77. ee/tests/ee_test.py +40 -22
  78. ee/tests/element_test.py +2 -2
  79. ee/tests/errormargin_test.py +1 -1
  80. ee/tests/feature_test.py +10 -10
  81. ee/tests/featurecollection_test.py +3 -3
  82. ee/tests/filter_test.py +4 -4
  83. ee/tests/function_test.py +5 -5
  84. ee/tests/geometry_point_test.py +3 -3
  85. ee/tests/geometry_test.py +93 -52
  86. ee/tests/image_converter_test.py +1 -3
  87. ee/tests/image_test.py +3 -3
  88. ee/tests/imagecollection_test.py +3 -3
  89. ee/tests/join_test.py +3 -3
  90. ee/tests/kernel_test.py +7 -3
  91. ee/tests/model_test.py +17 -5
  92. ee/tests/oauth_test.py +189 -7
  93. ee/tests/pixeltype_test.py +6 -7
  94. ee/tests/projection_test.py +5 -6
  95. ee/tests/reducer_test.py +16 -3
  96. ee/tests/serializer_test.py +39 -12
  97. ee/tests/table_converter_test.py +51 -7
  98. ee/tests/terrain_test.py +11 -3
  99. earthengine_api-1.5.13rc0.dist-info/RECORD +0 -107
  100. {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/entry_points.txt +0 -0
  101. {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/licenses/LICENSE +0 -0
  102. {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/top_level.txt +0 -0
ee/model.py CHANGED
@@ -1,7 +1,5 @@
1
1
  """A wrapper for Models."""
2
2
 
3
- from typing import Optional
4
-
5
3
  from ee import _arg_types
6
4
  from ee import apifunction
7
5
  from ee import computedobject
@@ -66,26 +64,26 @@ class Model(computedobject.ComputedObject):
66
64
  @deprecation.Deprecated('Migrate to Vertex AI')
67
65
  def fromAiPlatformPredictor(
68
66
  # pylint: disable=invalid-name
69
- projectName: Optional[_arg_types.Any] = None,
70
- projectId: Optional[_arg_types.String] = None,
71
- modelName: Optional[_arg_types.String] = None,
67
+ projectName: _arg_types.Any | None = None,
68
+ projectId: _arg_types.String | None = None,
69
+ modelName: _arg_types.String | None = None,
72
70
  # pylint: enable=invalid-name
73
- version: Optional[_arg_types.String] = None,
74
- region: Optional[_arg_types.String] = None,
71
+ version: _arg_types.String | None = None,
72
+ region: _arg_types.String | None = None,
75
73
  # pylint: disable=invalid-name
76
- inputProperties: Optional[_arg_types.List] = None,
77
- inputTypeOverride: Optional[_arg_types.Dictionary] = None,
78
- inputShapes: Optional[_arg_types.Dictionary] = None,
74
+ inputProperties: _arg_types.List | None = None,
75
+ inputTypeOverride: _arg_types.Dictionary | None = None,
76
+ inputShapes: _arg_types.Dictionary | None = None,
79
77
  # pylint: enable=invalid-name
80
- proj: Optional[_arg_types.Projection] = None,
78
+ proj: _arg_types.Projection | None = None,
81
79
  # pylint: disable=invalid-name
82
- fixInputProj: Optional[_arg_types.Bool] = None,
83
- inputTileSize: Optional[_arg_types.List] = None,
84
- inputOverlapSize: Optional[_arg_types.List] = None,
85
- outputTileSize: Optional[_arg_types.List] = None,
86
- outputBands: Optional[_arg_types.Dictionary] = None,
87
- outputProperties: Optional[_arg_types.Dictionary] = None,
88
- outputMultiplier: Optional[_arg_types.Number] = None,
80
+ fixInputProj: _arg_types.Bool | None = None,
81
+ inputTileSize: _arg_types.List | None = None,
82
+ inputOverlapSize: _arg_types.List | None = None,
83
+ outputTileSize: _arg_types.List | None = None,
84
+ outputBands: _arg_types.Dictionary | None = None,
85
+ outputProperties: _arg_types.Dictionary | None = None,
86
+ outputMultiplier: _arg_types.Number | None = None,
89
87
  # pylint: enable=invalid-name
90
88
  ) -> 'Model':
91
89
  """Returns an ee.Model from a description of an AI Platform prediction model.
@@ -168,21 +166,21 @@ class Model(computedobject.ComputedObject):
168
166
  def fromVertexAi(
169
167
  endpoint: _arg_types.String,
170
168
  # pylint: disable=invalid-name
171
- inputProperties: Optional[_arg_types.List] = None,
172
- inputTypeOverride: Optional[_arg_types.Dictionary] = None,
173
- inputShapes: Optional[_arg_types.Dictionary] = None,
169
+ inputProperties: _arg_types.List | None = None,
170
+ inputTypeOverride: _arg_types.Dictionary | None = None,
171
+ inputShapes: _arg_types.Dictionary | None = None,
174
172
  # pylint: enable=invalid-name
175
- proj: Optional[_arg_types.Projection] = None,
173
+ proj: _arg_types.Projection | None = None,
176
174
  # pylint: disable=invalid-name
177
- fixInputProj: Optional[_arg_types.Bool] = None,
178
- inputTileSize: Optional[_arg_types.List] = None,
179
- inputOverlapSize: Optional[_arg_types.List] = None,
180
- outputTileSize: Optional[_arg_types.List] = None,
181
- outputBands: Optional[_arg_types.Dictionary] = None,
182
- outputProperties: Optional[_arg_types.Dictionary] = None,
183
- outputMultiplier: Optional[_arg_types.Number] = None,
184
- maxPayloadBytes: Optional[_arg_types.Integer] = None,
185
- payloadFormat: Optional[_arg_types.String] = None,
175
+ fixInputProj: _arg_types.Bool | None = None,
176
+ inputTileSize: _arg_types.List | None = None,
177
+ inputOverlapSize: _arg_types.List | None = None,
178
+ outputTileSize: _arg_types.List | None = None,
179
+ outputBands: _arg_types.Dictionary | None = None,
180
+ outputProperties: _arg_types.Dictionary | None = None,
181
+ outputMultiplier: _arg_types.Number | None = None,
182
+ maxPayloadBytes: _arg_types.Integer | None = None,
183
+ payloadFormat: _arg_types.String | None = None,
186
184
  # pylint: enable=invalid-name
187
185
  ) -> 'Model':
188
186
  """Returns an ee.Model from a description of a Vertex AI model endpoint.
ee/oauth.py CHANGED
@@ -9,6 +9,7 @@ Typical use-case consists of:
9
9
  """
10
10
 
11
11
  import base64
12
+ from collections.abc import Sequence
12
13
  import errno
13
14
  import hashlib
14
15
  import http.server
@@ -17,7 +18,7 @@ import os
17
18
  import shutil
18
19
  import subprocess
19
20
  import sys
20
- from typing import Any, Dict, Optional, Sequence, Union
21
+ from typing import Any
21
22
  import urllib.error
22
23
  import urllib.parse
23
24
  import urllib.request
@@ -87,7 +88,7 @@ def get_credentials_path() -> str:
87
88
  return cred_path
88
89
 
89
90
 
90
- def get_credentials_arguments() -> Dict[str, Any]:
91
+ def get_credentials_arguments() -> dict[str, Any]:
91
92
  with open(get_credentials_path()) as creds:
92
93
  stored = json.load(creds)
93
94
  args = {}
@@ -100,13 +101,13 @@ def get_credentials_arguments() -> Dict[str, Any]:
100
101
  return args
101
102
 
102
103
 
103
- def is_sdk_credentials(credentials: Optional[Any]) -> bool:
104
+ def is_sdk_credentials(credentials: Any | None) -> bool:
104
105
  return is_sdk_project(project_number_from_credentials(credentials))
105
106
 
106
107
 
107
108
  def project_number_from_credentials(
108
- credentials: Optional[Any],
109
- ) -> Optional[str]:
109
+ credentials: Any | None,
110
+ ) -> str | None:
110
111
  client_id = credentials and getattr(credentials, 'client_id', None)
111
112
  return _project_number_from_client_id(client_id)
112
113
 
@@ -115,7 +116,7 @@ def is_sdk_project(project: str) -> bool:
115
116
  return project in SDK_PROJECTS
116
117
 
117
118
 
118
- def get_appdefault_project() -> Optional[str]:
119
+ def get_appdefault_project() -> str | None:
119
120
  try:
120
121
  adc_path = _cloud_sdk.get_application_default_credentials_path()
121
122
  with open(adc_path) as adc_json:
@@ -133,7 +134,7 @@ def _valid_credentials_exist() -> bool:
133
134
  return False
134
135
 
135
136
 
136
- def is_valid_credentials(credentials: Optional[Any]) -> bool:
137
+ def is_valid_credentials(credentials: Any | None) -> bool:
137
138
  if credentials is None:
138
139
  return False
139
140
  try:
@@ -145,8 +146,8 @@ def is_valid_credentials(credentials: Optional[Any]) -> bool:
145
146
 
146
147
  def get_authorization_url(
147
148
  code_challenge: str,
148
- scopes: Optional[Sequence[str]] = None,
149
- redirect_uri: Optional[str] = None,
149
+ scopes: Sequence[str] | None = None,
150
+ redirect_uri: str | None = None,
150
151
  ) -> str:
151
152
  """Returns a URL to generate an auth code."""
152
153
 
@@ -163,9 +164,9 @@ def get_authorization_url(
163
164
  def request_token(
164
165
  auth_code: str,
165
166
  code_verifier: str,
166
- client_id: Optional[str] = None,
167
- client_secret: Optional[str] = None,
168
- redirect_uri: Optional[str] = None,
167
+ client_id: str | None = None,
168
+ client_secret: str | None = None,
169
+ redirect_uri: str | None = None,
169
170
  ) -> str:
170
171
  """Uses authorization code to request tokens."""
171
172
 
@@ -184,14 +185,14 @@ def request_token(
184
185
  urllib.parse.urlencode(request_args).encode()).read().decode()
185
186
  except urllib.error.HTTPError as e:
186
187
  # pylint:disable=broad-exception-raised,raise-missing-from
187
- raise Exception('Problem requesting tokens. Please try again. %s %s' %
188
+ raise Exception('Problem requesting tokens. Please try again. %s %s' %
188
189
  (e, e.read()))
189
190
  # pylint:enable=broad-exception-raised,raise-missing-from
190
191
 
191
192
  return json.loads(response)['refresh_token']
192
193
 
193
194
 
194
- def write_private_json(json_path: str, info_dict: Dict[str, Any]) -> None:
195
+ def write_private_json(json_path: str, info_dict: dict[str, Any]) -> None:
195
196
  """Attempts to write the passed token to the given user directory."""
196
197
 
197
198
  dirname = os.path.dirname(json_path)
@@ -200,7 +201,7 @@ def write_private_json(json_path: str, info_dict: Dict[str, Any]) -> None:
200
201
  except OSError as e:
201
202
  if e.errno != errno.EEXIST:
202
203
  # pylint:disable=broad-exception-raised,raise-missing-from
203
- raise Exception('Error creating directory %s: %s' % (dirname, e))
204
+ raise Exception(f'Error creating directory {dirname}: {e}')
204
205
  # pylint:enable=broad-exception-raised,raise-missing-from
205
206
 
206
207
  file_content = json.dumps(info_dict)
@@ -225,7 +226,7 @@ def _in_jupyter_shell() -> bool:
225
226
  """Tests if the code is being executed within Jupyter."""
226
227
  try:
227
228
  import ipykernel.zmqshell
228
- return isinstance(IPython.get_ipython(),
229
+ return isinstance(IPython.get_ipython(), # pylint: disable=undefined-variable
229
230
  ipykernel.zmqshell.ZMQInteractiveShell)
230
231
  except ImportError:
231
232
  return False
@@ -233,7 +234,7 @@ def _in_jupyter_shell() -> bool:
233
234
  return False
234
235
 
235
236
 
236
- def _project_number_from_client_id(client_id: Optional[str]) -> Optional[str]:
237
+ def _project_number_from_client_id(client_id: str | None) -> str | None:
237
238
  """Returns the project number associated with the given OAuth client ID."""
238
239
  # Client IDs are of the form:
239
240
  # PROJECTNUMBER-BASE32STUFF.apps.googleusercontent.com.
@@ -242,10 +243,10 @@ def _project_number_from_client_id(client_id: Optional[str]) -> Optional[str]:
242
243
 
243
244
 
244
245
  def _obtain_and_write_token(
245
- auth_code: Optional[str] = None,
246
- code_verifier: Optional[str] = None,
247
- scopes: Optional[Sequence[str]] = None,
248
- redirect_uri: Optional[str] = None,
246
+ auth_code: str | None = None,
247
+ code_verifier: str | None = None,
248
+ scopes: Sequence[str] | None = None,
249
+ redirect_uri: str | None = None,
249
250
  ) -> None:
250
251
  """Obtains and writes credentials token based on an authorization code."""
251
252
  fetch_data = {}
@@ -282,7 +283,7 @@ def _obtain_and_write_token(
282
283
 
283
284
 
284
285
  def _display_auth_instructions_for_noninteractive(
285
- auth_url: str, code_verifier: Union[bytes, str]
286
+ auth_url: str, code_verifier: bytes | str
286
287
  ) -> None:
287
288
  """Displays instructions for authenticating without blocking for user input."""
288
289
  # Python 3 `bytes` should be decoded to `str` if used as an argument of
@@ -292,38 +293,43 @@ def _display_auth_instructions_for_noninteractive(
292
293
  else:
293
294
  code_verifier_str = code_verifier
294
295
 
295
- print('Paste the following address into a web browser:\n'
296
- '\n'
297
- ' {0}\n'
298
- '\n'
299
- 'On the web page, please authorize access to your '
300
- 'Earth Engine account and copy the authentication code. '
301
- 'Next authenticate with the following command:\n'
302
- '\n'
303
- ' earthengine authenticate --code-verifier={1} '
304
- '--authorization-code=PLACE_AUTH_CODE_HERE\n'.format(
305
- auth_url, code_verifier_str))
296
+ print(
297
+ 'Paste the following address into a web browser:\n'
298
+ '\n'
299
+ ' {}\n'
300
+ '\n'
301
+ 'On the web page, please authorize access to your '
302
+ 'Earth Engine account and copy the authentication code. '
303
+ 'Next authenticate with the following command:\n'
304
+ '\n'
305
+ ' earthengine authenticate --code-verifier={} '
306
+ '--authorization-code=PLACE_AUTH_CODE_HERE\n'.format(
307
+ auth_url, code_verifier_str
308
+ )
309
+ )
306
310
 
307
311
 
308
312
  def _display_auth_instructions_with_print(
309
- auth_url: str, coda: Optional[str] = None
313
+ auth_url: str, coda: str | None = None
310
314
  ) -> None:
311
315
  """Displays instructions for authenticating using a print statement."""
312
- print('To authorize access needed by Earth Engine, open the following '
313
- 'URL in a web browser and follow the instructions. If the web '
314
- 'browser does not start automatically, please manually browse the '
315
- 'URL below.\n'
316
- '\n'
317
- ' {0}\n'
318
- '\n{1}'.format(auth_url, coda or PASTE_CODA))
316
+ print(
317
+ 'To authorize access needed by Earth Engine, open the following '
318
+ 'URL in a web browser and follow the instructions. If the web '
319
+ 'browser does not start automatically, please manually browse the '
320
+ 'URL below.\n'
321
+ '\n'
322
+ ' {}\n'
323
+ '\n{}'.format(auth_url, coda or PASTE_CODA)
324
+ )
319
325
 
320
326
 
321
327
  def _display_auth_instructions_with_html(
322
- auth_url: str, coda: Optional[str] = None
328
+ auth_url: str, coda: str | None = None
323
329
  ) -> None:
324
330
  """Displays instructions for authenticating using HTML code."""
325
331
  try:
326
- IPython.display.display(IPython.display.HTML(
332
+ IPython.display.display(IPython.display.HTML( # pylint: disable=undefined-variable
327
333
  """<p>To authorize access needed by Earth Engine, open the following
328
334
  URL in a web browser and follow the instructions:</p>
329
335
  <p><a href={0}>{0}</a></p>
@@ -339,14 +345,14 @@ def _base64param(byte_string: bytes) -> bytes:
339
345
  return base64.urlsafe_b64encode(byte_string).rstrip(b'=')
340
346
 
341
347
 
342
- def _nonce_table(*nonce_keys: str) -> Dict[str, str]:
348
+ def _nonce_table(*nonce_keys: str) -> dict[str, str]:
343
349
  """Makes random nonces, and adds PKCE challenges for each _verifier nonce."""
344
350
  table = {}
345
351
  for key in nonce_keys:
346
352
  table[key] = _base64param(os.urandom(32))
347
353
  if key.endswith('_verifier'):
348
354
  # Generate a challenge that the server will use to ensure that requests
349
- # only work with our verifiers. https://tools.ietf.org/html/rfc7636
355
+ # only work with our verifiers. https://tools.ietf.org/html/rfc7636
350
356
  pkce_challenge = _base64param(hashlib.sha256(table[key]).digest())
351
357
  table[key.replace('_verifier', '_challenge')] = pkce_challenge
352
358
  return {k: v.decode() for k, v in table.items()}
@@ -376,8 +382,8 @@ def _no_gcloud() -> bool:
376
382
 
377
383
 
378
384
  def _load_gcloud_credentials(
379
- scopes: Optional[Sequence[str]] = None,
380
- quiet: Optional[bool] = None,
385
+ scopes: Sequence[str] | None = None,
386
+ quiet: bool | None = None,
381
387
  run_gcloud_legacy: bool = False,
382
388
  ) -> None:
383
389
  """Initializes credentials by running gcloud flows."""
@@ -429,7 +435,7 @@ def _start_server(port: int):
429
435
  class Handler(http.server.BaseHTTPRequestHandler):
430
436
  """Handles the OAuth callback and reports a success page."""
431
437
 
432
- code: Optional[str] = None
438
+ code: str | None = None
433
439
 
434
440
  def do_GET(self) -> None: # pylint: disable=invalid-name
435
441
  Handler.code = urllib.parse.parse_qs(
@@ -439,7 +445,7 @@ def _start_server(port: int):
439
445
  self.end_headers()
440
446
  self.wfile.write(
441
447
  b'\n\nGoogle Earth Engine authorization successful!\n\n\n'
442
- b'Credentials have been retrieved. Please close this window.\n\n'
448
+ b'Credentials have been retrieved. Please close this window.\n\n'
443
449
  b' \xf0\x9f\x8c\x8d \xe2\x9a\x99\xef\xb8\x8f \xf0\x9f\x8c\x8f'
444
450
  b' \xe2\x9a\x99\xef\xb8\x8f \xf0\x9f\x8c\x8e ') # Earth emoji
445
451
 
@@ -454,7 +460,7 @@ def _start_server(port: int):
454
460
  self.server = http.server.HTTPServer(('localhost', port), Handler)
455
461
  self.url = 'http://localhost:%s' % self.server.server_address[1]
456
462
 
457
- def fetch_code(self) -> Optional[str]:
463
+ def fetch_code(self) -> str | None:
458
464
  self.server.handle_request() # Blocks until a single request arrives.
459
465
  self.server.server_close()
460
466
  return Handler.code
@@ -463,24 +469,24 @@ def _start_server(port: int):
463
469
 
464
470
 
465
471
  def authenticate(
466
- cli_authorization_code: Optional[str] = None,
467
- quiet: Optional[bool] = None,
468
- cli_code_verifier: Optional[str] = None,
469
- auth_mode: Optional[str] = None,
470
- scopes: Optional[Sequence[str]] = None,
472
+ cli_authorization_code: str | None = None,
473
+ quiet: bool | None = None,
474
+ cli_code_verifier: str | None = None,
475
+ auth_mode: str | None = None,
476
+ scopes: Sequence[str] | None = None,
471
477
  force: bool = False,
472
- ) -> Optional[bool]:
478
+ ) -> bool | None:
473
479
  """Prompts the user to authorize access to Earth Engine via OAuth2.
474
480
 
475
481
  Args:
476
- cli_authorization_code: An optional authorization code. Supports CLI mode,
482
+ cli_authorization_code: An optional authorization code. Supports CLI mode,
477
483
  where the code is passed as an argument to `earthengine authenticate`.
478
484
  quiet: If true, do not require interactive prompts and force --no-browser
479
485
  mode for gcloud-legacy. If false, never supply --no-browser. Default is
480
486
  None, which autodetects the --no-browser setting.
481
- cli_code_verifier: PKCE verifier to prevent auth code stealing. Must be
487
+ cli_code_verifier: PKCE verifier to prevent auth code stealing. Must be
482
488
  provided if cli_authorization_code is given.
483
- auth_mode: The authorization mode. One of:
489
+ auth_mode: The authorization mode. One of:
484
490
  "colab" - use the Colab authentication flow.
485
491
  "notebook" - send user to notebook authenticator page. Intended for
486
492
  web users who do not run code locally. Credentials expire in 7 days.
@@ -508,6 +514,13 @@ def authenticate(
508
514
  Exception: on invalid arguments.
509
515
  """
510
516
 
517
+ if auth_mode == 'colab' and scopes is not None and set(scopes) != set(SCOPES):
518
+ raise ee_exception.EEException(
519
+ 'Scopes cannot be customized when auth_mode is "colab". Please see'
520
+ ' https://developers.google.com/earth-engine/guides/auth#quick_reference_guide_and_table'
521
+ ' for more information.'
522
+ )
523
+
511
524
  if cli_authorization_code:
512
525
  _obtain_and_write_token(cli_authorization_code, cli_code_verifier, scopes)
513
526
  return
@@ -551,11 +564,11 @@ class Flow:
551
564
  """Holds state for auth flows."""
552
565
  code_verifier: str
553
566
  scopes: Sequence[str]
554
- server: Optional[Any]
567
+ server: Any | None
555
568
  auth_url: str
556
569
 
557
570
  def __init__(
558
- self, auth_mode: str = 'notebook', scopes: Optional[Sequence[str]] = None
571
+ self, auth_mode: str = 'notebook', scopes: Sequence[str] | None = None
559
572
  ):
560
573
  """Initializes auth URL and PKCE verifier, for use in save_code().
561
574
 
@@ -588,7 +601,7 @@ class Flow:
588
601
  # pylint:disable-next=broad-exception-raised
589
602
  raise ee_exception.EEException('Unknown auth_mode "%s"' % auth_mode)
590
603
 
591
- def save_code(self, code: Optional[str] = None) -> None:
604
+ def save_code(self, code: str | None = None) -> None:
592
605
  """Fetches auth code if not given, and saves the generated credentials."""
593
606
  redirect_uri = None
594
607
  if self.server and not code:
@@ -596,7 +609,7 @@ class Flow:
596
609
  code = self.server.fetch_code() # Waits for oauth callback
597
610
  _obtain_and_write_token(code, self.code_verifier, self.scopes, redirect_uri)
598
611
 
599
- def display_instructions(self, quiet: Optional[bool] = None) -> bool:
612
+ def display_instructions(self, quiet: bool | None = None) -> bool:
600
613
  """Prints to stdout, and returns True if a browser should be opened."""
601
614
 
602
615
  if quiet:
ee/pixeltype.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """A wrapper for PixelTypes."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Any, Dict, Optional, Union
4
+ from typing import Any
5
5
 
6
6
  from ee import _arg_types
7
7
  from ee import apifunction
@@ -52,12 +52,12 @@ class PixelType(computedobject.ComputedObject):
52
52
 
53
53
  def __init__(
54
54
  self,
55
- precision: Union[_arg_types.String],
55
+ precision: _arg_types.String,
56
56
  # pylint: disable=invalid-name
57
- minValue: Optional[_arg_types.Number] = None,
58
- maxValue: Optional[_arg_types.Number] = None,
57
+ minValue: _arg_types.Number | None = None,
58
+ maxValue: _arg_types.Number | None = None,
59
59
  # pylint: enable=invalid-name
60
- dimensions: Optional[_arg_types.Integer] = None,
60
+ dimensions: _arg_types.Integer | None = None,
61
61
  ):
62
62
  """Creates a PixelType wrapper.
63
63
 
@@ -72,7 +72,7 @@ class PixelType(computedobject.ComputedObject):
72
72
  """
73
73
  self.initialize()
74
74
 
75
- args: Dict[str, Any] = {'precision': precision}
75
+ args: dict[str, Any] = {'precision': precision}
76
76
  if minValue is not None:
77
77
  args['minValue'] = minValue
78
78
  if maxValue is not None:
ee/projection.py CHANGED
@@ -1,7 +1,8 @@
1
1
  """A wrapper for Projections."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Any, Dict, Optional, Sequence, Union
4
+ from collections.abc import Sequence
5
+ from typing import Any, Union
5
6
 
6
7
  from ee import _arg_types
7
8
  from ee import apifunction
@@ -45,9 +46,9 @@ class Projection(computedobject.ComputedObject):
45
46
  def __init__(
46
47
  self,
47
48
  crs: _arg_types.String,
48
- transform: Optional[_NumberSequenceType] = None,
49
+ transform: _NumberSequenceType | None = None,
49
50
  # pylint: disable-next=invalid-name
50
- transformWkt: Optional[_arg_types.String] = None,
51
+ transformWkt: _arg_types.String | None = None,
51
52
  ):
52
53
  """Creates a Projection wrapper.
53
54
 
@@ -80,7 +81,7 @@ class Projection(computedobject.ComputedObject):
80
81
  super().__init__(crs.func, crs.args, crs.varName)
81
82
  return
82
83
 
83
- args: Dict[str, Any] = {'crs': crs}
84
+ args: dict[str, Any] = {'crs': crs}
84
85
  if transform is not None:
85
86
  args['transform'] = transform
86
87
  if transformWkt is not None: