earthengine-api 1.6.11__py3-none-any.whl → 1.6.12__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.
Files changed (48) hide show
  1. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/METADATA +1 -1
  2. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.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.12.dist-info}/WHEEL +0 -0
  46. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/entry_points.txt +0 -0
  47. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/licenses/LICENSE +0 -0
  48. {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/top_level.txt +0 -0
ee/ee_string.py CHANGED
@@ -37,19 +37,19 @@ class String(computedobject.ComputedObject):
37
37
  if isinstance(string, str):
38
38
  super().__init__(None, None)
39
39
  self._string = string
40
-
41
40
  elif isinstance(string, computedobject.ComputedObject):
42
41
  if self.is_func_returning_same(string):
43
42
  # If it's a call that's already returning a String, just cast.
44
43
  super().__init__(string.func, string.args, string.varName)
45
44
  else:
45
+ # Wrap some other ComputedObject.
46
46
  super().__init__(
47
47
  apifunction.ApiFunction(self.name()), {'input': string}
48
48
  )
49
49
  self._string = None
50
50
  else:
51
51
  raise ee_exception.EEException(
52
- 'Invalid argument specified for ee.String(): %s' % string)
52
+ f'Invalid argument specified for ee.String(): {string}')
53
53
 
54
54
  @classmethod
55
55
  def initialize(cls) -> None:
@@ -91,8 +91,7 @@ class String(computedobject.ComputedObject):
91
91
  Returns:
92
92
  Returns the result of joining self and string2.
93
93
  """
94
-
95
- return apifunction.ApiFunction.call_(self.name() + '.cat', self, string2)
94
+ return apifunction.ApiFunction.call_(f'{self.name()}.cat', self, string2)
96
95
 
97
96
  def compareTo(self, string2: _arg_types.String) -> ee_number.Number:
98
97
  """Compares two strings lexicographically.
@@ -107,15 +106,15 @@ class String(computedobject.ComputedObject):
107
106
  """
108
107
 
109
108
  return apifunction.ApiFunction.call_(
110
- self.name() + '.compareTo', self, string2
109
+ f'{self.name()}.compareTo', self, string2
111
110
  )
112
111
 
113
112
  def decodeJSON(self) -> computedobject.ComputedObject:
114
113
  """Decodes self as a JSON string."""
115
-
116
- return apifunction.ApiFunction.call_(self.name() + '.decodeJSON', self)
114
+ return apifunction.ApiFunction.call_(f'{self.name()}.decodeJSON', self)
117
115
 
118
116
  @staticmethod
117
+ # pylint: disable=redefined-builtin
119
118
  def encodeJSON(object: _arg_types.Any) -> String:
120
119
  """Returns an ee.String with an object encoded as JSON.
121
120
 
@@ -124,7 +123,7 @@ class String(computedobject.ComputedObject):
124
123
  Args:
125
124
  object: The object to encode.
126
125
  """
127
-
126
+ # object is probably not an ee.String, so use "String." instead of object.
128
127
  return apifunction.ApiFunction.call_('String.encodeJSON', object)
129
128
 
130
129
  def equals(self, target: _arg_types.String) -> computedobject.ComputedObject:
@@ -137,8 +136,7 @@ class String(computedobject.ComputedObject):
137
136
  True if the target is a string and is lexicographically equal to the
138
137
  reference, or false otherwise.
139
138
  """
140
-
141
- return apifunction.ApiFunction.call_(self.name() + '.equals', self, target)
139
+ return apifunction.ApiFunction.call_(f'{self.name()}.equals', self, target)
142
140
 
143
141
  def index(self, pattern: _arg_types.String) -> ee_number.Number:
144
142
  """Searches a string for the first occurrence of a substring.
@@ -149,13 +147,11 @@ class String(computedobject.ComputedObject):
149
147
  Returns:
150
148
  The index of the first match, or -1.
151
149
  """
152
-
153
- return apifunction.ApiFunction.call_(self.name() + '.index', self, pattern)
150
+ return apifunction.ApiFunction.call_(f'{self.name()}.index', self, pattern)
154
151
 
155
152
  def length(self) -> ee_number.Number:
156
153
  """Returns the length of a string."""
157
-
158
- return apifunction.ApiFunction.call_(self.name() + '.length', self)
154
+ return apifunction.ApiFunction.call_(f'{self.name()}.length', self)
159
155
 
160
156
  def match(
161
157
  self, regex: _arg_types.String, flags: _arg_types.String | None = None
@@ -172,7 +168,7 @@ class String(computedobject.ComputedObject):
172
168
  """
173
169
 
174
170
  return apifunction.ApiFunction.call_(
175
- self.name() + '.match', self, regex, flags
171
+ f'{self.name()}.match', self, regex, flags
176
172
  )
177
173
 
178
174
  def replace(
@@ -194,7 +190,7 @@ class String(computedobject.ComputedObject):
194
190
  """
195
191
 
196
192
  return apifunction.ApiFunction.call_(
197
- self.name() + '.replace', self, regex, replacement, flags
193
+ f'{self.name()}.replace', self, regex, replacement, flags
198
194
  )
199
195
 
200
196
  def rindex(self, pattern: _arg_types.String) -> ee_number.Number:
@@ -206,8 +202,7 @@ class String(computedobject.ComputedObject):
206
202
  Returns:
207
203
  The index of the first match or -1 if no match.
208
204
  """
209
-
210
- return apifunction.ApiFunction.call_(self.name() + '.rindex', self, pattern)
205
+ return apifunction.ApiFunction.call_(f'{self.name()}.rindex', self, pattern)
211
206
 
212
207
  def slice(
213
208
  self, start: _arg_types.Integer, end: _arg_types.Integer | None = None
@@ -228,7 +223,7 @@ class String(computedobject.ComputedObject):
228
223
  """
229
224
 
230
225
  return apifunction.ApiFunction.call_(
231
- self.name() + '.slice', self, start, end
226
+ f'{self.name()}.slice', self, start, end
232
227
  )
233
228
 
234
229
  def split(
@@ -246,20 +241,17 @@ class String(computedobject.ComputedObject):
246
241
  """
247
242
 
248
243
  return apifunction.ApiFunction.call_(
249
- self.name() + '.split', self, regex, flags
244
+ f'{self.name()}.split', self, regex, flags
250
245
  )
251
246
 
252
247
  def toLowerCase(self) -> String:
253
248
  """Converts all of the characters in a string to lower case."""
254
-
255
- return apifunction.ApiFunction.call_(self.name() + '.toLowerCase', self)
249
+ return apifunction.ApiFunction.call_(f'{self.name()}.toLowerCase', self)
256
250
 
257
251
  def toUpperCase(self) -> String:
258
252
  """Converts all of the characters in a string to upper case."""
259
-
260
- return apifunction.ApiFunction.call_(self.name() + '.toUpperCase', self)
253
+ return apifunction.ApiFunction.call_(f'{self.name()}.toUpperCase', self)
261
254
 
262
255
  def trim(self) -> String:
263
256
  """Returns a string with any leading and trailing whitespace removed."""
264
-
265
- return apifunction.ApiFunction.call_(self.name() + '.trim', self)
257
+ return apifunction.ApiFunction.call_(f'{self.name()}.trim', self)
ee/ee_types.py CHANGED
@@ -9,7 +9,7 @@ from typing import Any
9
9
  from ee import computedobject
10
10
 
11
11
 
12
- # A dictionary of the classes in the ee module. Set by registerClasses.
12
+ # A dictionary of the classes in the ee module. Set by registerClasses.
13
13
  _registered_classes = {}
14
14
 
15
15
 
@@ -48,7 +48,7 @@ def classToName(a_class: type[Any]) -> str:
48
48
 
49
49
  # TODO(user): Any -> Optional[type[Any]].
50
50
  def nameToClass(name: str) -> Any:
51
- """Converts a class name to a class. Returns None if not an ee class.
51
+ """Converts a class name to a class. Returns None if not an ee class.
52
52
 
53
53
  Args:
54
54
  name: The class name.
ee/element.py CHANGED
@@ -187,7 +187,7 @@ class Element(computedobject.ComputedObject):
187
187
  """Returns properties from a feature as a dictionary.
188
188
 
189
189
  Args:
190
- properties: The list of properties to extract. Defaults to all non-system
190
+ properties: The list of properties to extract. Defaults to all non-system
191
191
  properties
192
192
  """
193
193
 
ee/featurecollection.py CHANGED
@@ -22,7 +22,7 @@ from ee import geometry
22
22
  from ee import image
23
23
 
24
24
 
25
- class FeatureCollection(collection.Collection):
25
+ class FeatureCollection(collection.Collection[feature.Feature]):
26
26
  """A representation of a FeatureCollection."""
27
27
 
28
28
  _initialized = False
@@ -47,10 +47,13 @@ class FeatureCollection(collection.Collection):
47
47
  """Constructs a collection features.
48
48
 
49
49
  Args:
50
- args: constructor argument. One of: 1) A string - assumed to be the name
51
- of a collection. 2) A geometry. 3) A feature. 4) An array of features.
52
- 5) A GeoJSON FeatureCollection. 6) A computed object - reinterpreted as
53
- a collection.
50
+ args: constructor argument. One of:
51
+ 1) A string - assumed to be the name of a collection.
52
+ 2) A geometry.
53
+ 3) A feature.
54
+ 4) An array of features.
55
+ 5) A GeoJSON FeatureCollection.
56
+ 6) A computed object - reinterpreted as a collection.
54
57
  column: The name of the geometry column to use. Only useful with the
55
58
  string constructor.
56
59
 
@@ -187,9 +190,9 @@ class FeatureCollection(collection.Collection):
187
190
  propertySelectors: An array of names or regexes specifying the properties
188
191
  to select.
189
192
  newProperties: An array of strings specifying the new names for the
190
- selected properties. If supplied, the length must match the number
193
+ selected properties. If supplied, the length must match the number
191
194
  of properties selected.
192
- retainGeometry: A boolean. When false, the result will have no geometry.
195
+ retainGeometry: A boolean. When false, the result will have no geometry.
193
196
  *args: Selector elements as varargs.
194
197
 
195
198
  Returns:
ee/filter.py CHANGED
@@ -108,7 +108,7 @@ class Filter(computedobject.ComputedObject):
108
108
  These are implicitly ANDed.
109
109
 
110
110
  Args:
111
- new_filter: The filter to append to this one. Possible types are:
111
+ new_filter: The filter to append to this one. Possible types are:
112
112
  1) another fully constructed Filter,
113
113
  2) a JSON representation of a filter,
114
114
  3) an array of 1 or 2.
@@ -141,7 +141,7 @@ class Filter(computedobject.ComputedObject):
141
141
  Returns:
142
142
  The new filter.
143
143
 
144
- Deprecated. Use ee.Filter.eq(), ee.Filter.gte(), etc.'
144
+ Deprecated. Use ee.Filter.eq(), ee.Filter.gte(), etc.'
145
145
  """
146
146
  operator = operator.lower()
147
147
 
ee/geometry.py CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import collections.abc
6
- from collections.abc import Sequence
5
+ from collections.abc import Iterable, Sequence
7
6
  import json
8
7
  import math
9
8
  from typing import Any
@@ -79,7 +78,7 @@ class Geometry(computedobject.ComputedObject):
79
78
  if options:
80
79
  raise ee_exception.EEException(
81
80
  'Setting the CRS or geodesic on a computed Geometry is not '
82
- 'supported. Use Geometry.transform().')
81
+ 'supported. Use Geometry.transform().')
83
82
  else:
84
83
  super().__init__(geo_json.func, geo_json.args, geo_json.varName)
85
84
  return
@@ -157,7 +156,7 @@ class Geometry(computedobject.ComputedObject):
157
156
  if isinstance(name, str):
158
157
  return name
159
158
  raise ee_exception.EEException(
160
- 'Invalid CRS declaration in GeoJSON: ' + json.dumps(crs)
159
+ f'Invalid CRS declaration in GeoJSON: {json.dumps(crs)}'
161
160
  )
162
161
 
163
162
  @classmethod
@@ -342,12 +341,12 @@ class Geometry(computedobject.ComputedObject):
342
341
  # negated, we also reject NaN.
343
342
  if not south <= 90:
344
343
  raise ee_exception.EEException(
345
- 'Geometry.BBox: south must be at most +90°, but was {}°'.format(
346
- south))
344
+ f'Geometry.BBox: south must be at most +90°, but was {south}°'
345
+ )
347
346
  if not north >= -90:
348
347
  raise ee_exception.EEException(
349
- 'Geometry.BBox: north must be at least -90°, but was {}°'.format(
350
- north))
348
+ f'Geometry.BBox: north must be at least -90°, but was {north}°'
349
+ )
351
350
  # On the other hand, allow a box whose extent lies past the pole, but
352
351
  # canonicalize it to being exactly the pole.
353
352
  south = max(south, -90)
@@ -402,7 +401,7 @@ class Geometry(computedobject.ComputedObject):
402
401
  """Constructs an ee.Geometry describing a LineString.
403
402
 
404
403
  Args:
405
- coords: A list of at least two points. May be a list of coordinates in
404
+ coords: A list of at least two points. May be a list of coordinates in
406
405
  the GeoJSON 'LineString' format, a list of at least two ee.Geometry
407
406
  objects describing a point, or a list of at least four numbers
408
407
  defining the [x,y] coordinates of at least two points.
@@ -477,7 +476,7 @@ class Geometry(computedobject.ComputedObject):
477
476
  """Constructs an ee.Geometry describing a MultiLineString.
478
477
 
479
478
  Create a GeoJSON MultiLineString from either a list of points, or an array
480
- of lines (each an array of Points). If a list of points is specified,
479
+ of lines (each an array of Points). If a list of points is specified,
481
480
  only a single line is created.
482
481
 
483
482
  Args:
@@ -647,12 +646,12 @@ class Geometry(computedobject.ComputedObject):
647
646
  )
648
647
  return json.dumps(self.toGeoJSON())
649
648
 
650
- def serialize(self, for_cloud_api=True):
649
+ def serialize(self, for_cloud_api: bool = True) -> str:
651
650
  """Returns the serialized representation of this object."""
652
651
  return serializer.toJSON(self, for_cloud_api=for_cloud_api)
653
652
 
654
653
  def __str__(self) -> str:
655
- return 'ee.Geometry(%s)' % serializer.toReadableJSON(self)
654
+ return f'ee.Geometry({serializer.toReadableJSON(self)})'
656
655
 
657
656
  def __repr__(self) -> str:
658
657
  return self.__str__()
@@ -702,10 +701,10 @@ class Geometry(computedobject.ComputedObject):
702
701
  Returns:
703
702
  The number of nested arrays or -1 on error.
704
703
  """
705
- if not isinstance(shape, collections.abc.Iterable):
704
+ if not isinstance(shape, Iterable):
706
705
  return -1
707
706
 
708
- if (shape and isinstance(shape[0], collections.abc.Iterable) and
707
+ if (shape and isinstance(shape[0], Iterable) and
709
708
  not isinstance(shape[0], str)):
710
709
  count = Geometry._isValidCoordinates(shape[0])
711
710
  # If more than 1 ring or polygon, they should have the same nesting.
@@ -730,7 +729,7 @@ class Geometry(computedobject.ComputedObject):
730
729
  """Create a line from a list of points.
731
730
 
732
731
  Args:
733
- coordinates: The points to convert. Must be list of numbers of
732
+ coordinates: The points to convert. Must be list of numbers of
734
733
  even length, in the format [x1, y1, x2, y2, ...]
735
734
 
736
735
  Returns:
@@ -742,13 +741,13 @@ class Geometry(computedobject.ComputedObject):
742
741
  return coordinates
743
742
  if len(coordinates) % 2 != 0:
744
743
  raise ee_exception.EEException(
745
- 'Invalid number of coordinates: %s' % len(coordinates))
744
+ f'Invalid number of coordinates: {len(coordinates)}'
745
+ )
746
746
 
747
- line = []
748
- for i in range(0, len(coordinates), 2):
749
- pt = [coordinates[i], coordinates[i + 1]]
750
- line.append(pt)
751
- return line
747
+ return [
748
+ [coordinates[i], coordinates[i + 1]]
749
+ for i in range(0, len(coordinates), 2)
750
+ ]
752
751
 
753
752
  @staticmethod
754
753
  def _parseArgs(ctor_name: str, depth: int, args: Any) -> dict[str, Any]:
ee/image.py CHANGED
@@ -430,8 +430,7 @@ class Image(element.Element):
430
430
  # pylint: enable=protected-access
431
431
  return band_image
432
432
 
433
- if params['format'] == 'ZIPPED_GEO_TIFF_PER_BAND' and params.get(
434
- 'bands') and len(params.get('bands')):
433
+ if params['format'] == 'ZIPPED_GEO_TIFF_PER_BAND' and params.get('bands'):
435
434
  # Build a new image based on the constituent band images.
436
435
  image = Image.combine_(
437
436
  [_build_image_per_band(band) for band in params['bands']])
@@ -3579,7 +3578,6 @@ class Image(element.Element):
3579
3578
  # pylint: enable=invalid-name
3580
3579
  **kwargs,
3581
3580
  ) -> Image:
3582
- # pylint: disable=g-doc-args
3583
3581
  """Returns an image with the values of a band remapped.
3584
3582
 
3585
3583
  Maps from input values to output values, represented by two parallel lists.
@@ -3589,24 +3587,21 @@ class Image(element.Element):
3589
3587
  floating point precision errors.
3590
3588
 
3591
3589
  Args:
3592
- from: The source values (numbers or ee.Array). All values in this list
3590
+ from_: The source values (numbers or ee.Array). All values in this list
3593
3591
  will be mapped to the corresponding value in 'to'.
3594
3592
  to: The destination values (numbers or ee.Array). These are used to
3595
3593
  replace the corresponding values in 'from'. Must have the same number of
3596
- values as 'from'.
3594
+ values as 'from_'.
3597
3595
  defaultValue: The default value to replace values that weren't matched by
3598
- a value in 'from'. If not specified, unmatched values are masked out.
3596
+ a value in 'from_'. If not specified, unmatched values are masked out.
3599
3597
  bandName: The name of the band to remap. If not specified, the first band
3600
3598
  in the image is used.
3601
3599
  """
3602
- # pylint: enable=g-doc-args
3603
3600
 
3601
+ if 'from' in kwargs:
3602
+ from_ = kwargs.pop('from')
3604
3603
  if kwargs:
3605
- if kwargs.keys() != {'from'}:
3606
- raise TypeError(
3607
- f'Unexpected arguments: {list(kwargs.keys())}. Expected: from.'
3608
- )
3609
- from_ = kwargs['from']
3604
+ raise TypeError(f'remap() got unexpected keyword arguments: {list(kwargs)}')
3610
3605
 
3611
3606
  if not from_:
3612
3607
  raise TypeError('from is required.')
ee/imagecollection.py CHANGED
@@ -20,7 +20,7 @@ from ee import image
20
20
  REDUCE_PREFIX = 'reduce'
21
21
 
22
22
 
23
- class ImageCollection(collection.Collection):
23
+ class ImageCollection(collection.Collection[image.Image]):
24
24
  """Representation for an Earth Engine ImageCollection."""
25
25
 
26
26
  _initialized = False
@@ -114,7 +114,7 @@ class ImageCollection(collection.Collection):
114
114
  selectors: An array of names, regexes or numeric indices specifying the
115
115
  bands to select.
116
116
  names: An array of strings specifying the new names for the selected
117
- bands. If supplied, the length must match the number of bands selected.
117
+ bands. If supplied, the length must match the number of bands selected.
118
118
  *args: Selector elements as varargs.
119
119
 
120
120
  Returns:
@@ -194,7 +194,7 @@ class ImageCollection(collection.Collection):
194
194
  """Get the URL for an animated video thumbnail of the given collection.
195
195
 
196
196
  Note: Videos can only be created when the image visualization
197
- creates an RGB or RGBA image. This can be done by mapping a visualization
197
+ creates an RGB or RGBA image. This can be done by mapping a visualization
198
198
  onto the collection or specifying three bands in the params.
199
199
 
200
200
  Args:
ee/mapclient.py CHANGED
@@ -8,7 +8,7 @@ This currently has several spots that are hard-coded for 256x256 tiles, even
8
8
  though MapOverlay tries to track this.
9
9
 
10
10
  Supports mouse-based pan and zoom as well as tile upsampling while waiting
11
- for new tiles to load. The map to display is specified by a MapOverlay, and
11
+ for new tiles to load. The map to display is specified by a MapOverlay, and
12
12
  added to the GUI on creation or manually using addOverlay()
13
13
  gui = MapClient(MakeOverlay(mapid))
14
14
 
@@ -35,7 +35,7 @@ import urllib.request
35
35
  from PIL import Image
36
36
  from PIL import ImageTk
37
37
 
38
- # The default URL to fetch tiles from. We could pull this from the EE library,
38
+ # The default URL to fetch tiles from. We could pull this from the EE library,
39
39
  # however this doesn't have any other dependencies on that yet, so let's not.
40
40
  BASE_URL = 'https://earthengine.googleapis.com'
41
41
 
@@ -52,7 +52,7 @@ class MapClient(threading.Thread):
52
52
  """Initialize the MapClient UI.
53
53
 
54
54
  Args:
55
- opt_overlay: A mapoverlay to display. If not specified, the default
55
+ opt_overlay: A mapoverlay to display. If not specified, the default
56
56
  Google Maps basemap is used.
57
57
  opt_width: The default width of the frame to construct.
58
58
  opt_height: The default height of the frame to construct.
@@ -178,7 +178,7 @@ class MapClient(threading.Thread):
178
178
  image: The image tile to display.
179
179
  key: A tuple containing the key of the image (level, x, y)
180
180
  overlay: The overlay this tile belongs to.
181
- layer: The layer number this overlay corresponds to. Only used
181
+ layer: The layer number this overlay corresponds to. Only used
182
182
  for caching purposes.
183
183
  """
184
184
  # TODO(user): This function is called from multiple threads, and
@@ -205,7 +205,7 @@ class MapClient(threading.Thread):
205
205
 
206
206
  Args:
207
207
  event: The event that caused this zoom request.
208
- direction: The direction to zoom. +1 for higher zoom, -1 for lower.
208
+ direction: The direction to zoom. +1 for higher zoom, -1 for lower.
209
209
  """
210
210
  if self.level + direction >= 0:
211
211
  # Discard everything cached in the MapClient, and flush the fetch queues.
@@ -319,13 +319,13 @@ class MapOverlay:
319
319
  """Get the requested tile.
320
320
 
321
321
  If the requested tile is already cached, it's returned (sent to the
322
- callback) directly. If it's not cached, a check is made to see if
322
+ callback) directly. If it's not cached, a check is made to see if
323
323
  a lower-res version is cached, and if so that's interpolated up, before
324
324
  a request for the actual tile is made.
325
325
 
326
326
  Args:
327
327
  key: The key of the tile to fetch.
328
- callback: The callback to call when the tile is available. The callback
328
+ callback: The callback to call when the tile is available. The callback
329
329
  may be called more than once if a low-res version is available.
330
330
  """
331
331
  result = self.GetCachedTile(key)
@@ -456,12 +456,12 @@ def addToMap(eeobject, vis_params=None, *args):
456
456
 
457
457
  Args:
458
458
  eeobject: the object to add to the map.
459
- vis_params: a dictionary of visualization parameters. See
459
+ vis_params: a dictionary of visualization parameters. See
460
460
  ee.data.getMapId().
461
461
  *args: unused arguments, left for compatibility with the JS API.
462
462
 
463
463
  This call exists to be an equivalent to the playground addToMap() call.
464
- It uses a global MapInstance to hang on to "the map". If the MapInstance
464
+ It uses a global MapInstance to hang on to "the map". If the MapInstance
465
465
  isn't initialized, this creates a new one.
466
466
  """
467
467
  del args # Unused.
ee/oauth.py CHANGED
@@ -185,7 +185,7 @@ def request_token(
185
185
  urllib.parse.urlencode(request_args).encode()).read().decode()
186
186
  except urllib.error.HTTPError as e:
187
187
  # pylint:disable=broad-exception-raised,raise-missing-from
188
- raise Exception('Problem requesting tokens. Please try again. %s %s' %
188
+ raise Exception('Problem requesting tokens. Please try again. %s %s' %
189
189
  (e, e.read()))
190
190
  # pylint:enable=broad-exception-raised,raise-missing-from
191
191
 
@@ -352,7 +352,7 @@ def _nonce_table(*nonce_keys: str) -> dict[str, str]:
352
352
  table[key] = _base64param(os.urandom(32))
353
353
  if key.endswith('_verifier'):
354
354
  # Generate a challenge that the server will use to ensure that requests
355
- # only work with our verifiers. https://tools.ietf.org/html/rfc7636
355
+ # only work with our verifiers. https://tools.ietf.org/html/rfc7636
356
356
  pkce_challenge = _base64param(hashlib.sha256(table[key]).digest())
357
357
  table[key.replace('_verifier', '_challenge')] = pkce_challenge
358
358
  return {k: v.decode() for k, v in table.items()}
@@ -445,7 +445,7 @@ def _start_server(port: int):
445
445
  self.end_headers()
446
446
  self.wfile.write(
447
447
  b'\n\nGoogle Earth Engine authorization successful!\n\n\n'
448
- b'Credentials have been retrieved. Please close this window.\n\n'
448
+ b'Credentials have been retrieved. Please close this window.\n\n'
449
449
  b' \xf0\x9f\x8c\x8d \xe2\x9a\x99\xef\xb8\x8f \xf0\x9f\x8c\x8f'
450
450
  b' \xe2\x9a\x99\xef\xb8\x8f \xf0\x9f\x8c\x8e ') # Earth emoji
451
451
 
@@ -479,14 +479,14 @@ def authenticate(
479
479
  """Prompts the user to authorize access to Earth Engine via OAuth2.
480
480
 
481
481
  Args:
482
- cli_authorization_code: An optional authorization code. Supports CLI mode,
482
+ cli_authorization_code: An optional authorization code. Supports CLI mode,
483
483
  where the code is passed as an argument to `earthengine authenticate`.
484
484
  quiet: If true, do not require interactive prompts and force --no-browser
485
485
  mode for gcloud-legacy. If false, never supply --no-browser. Default is
486
486
  None, which autodetects the --no-browser setting.
487
- 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
488
488
  provided if cli_authorization_code is given.
489
- auth_mode: The authorization mode. One of:
489
+ auth_mode: The authorization mode. One of:
490
490
  "colab" - use the Colab authentication flow.
491
491
  "notebook" - send user to notebook authenticator page. Intended for
492
492
  web users who do not run code locally. Credentials expire in 7 days.
@@ -514,6 +514,13 @@ def authenticate(
514
514
  Exception: on invalid arguments.
515
515
  """
516
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
+
517
524
  if cli_authorization_code:
518
525
  _obtain_and_write_token(cli_authorization_code, cli_code_verifier, scopes)
519
526
  return
@@ -550,6 +550,22 @@ class CloudApiUtilsTest(unittest.TestCase):
550
550
  expected,
551
551
  _cloud_api_utils.convert_sources_to_one_platform_sources(old_sources))
552
552
 
553
+ def test_convert_to_operation_state(self):
554
+ def convert(state):
555
+ return _cloud_api_utils.convert_to_operation_state(state)
556
+
557
+ self.assertEqual('CANCELLED', convert('CANCELLED'))
558
+ self.assertEqual('CANCELLING', convert('CANCEL_REQUESTED'))
559
+ self.assertEqual('CANCELLING', convert('CANCELLING'))
560
+ self.assertEqual('FAILED', convert('FAILED'))
561
+ self.assertEqual('PENDING', convert('PENDING'))
562
+ self.assertEqual('PENDING', convert('READY'))
563
+ self.assertEqual('RUNNING', convert('RUNNING'))
564
+ self.assertEqual('SUCCEEDED', convert('COMPLETED'))
565
+ self.assertEqual('SUCCEEDED', convert('SUCCEEDED'))
566
+ self.assertEqual('UNKNOWN', convert('random_string'))
567
+ self.assertEqual('UNKNOWN', convert('UNKNOWN'))
568
+
553
569
 
554
570
  if __name__ == '__main__':
555
571
  unittest.main()
ee/tests/_helpers_test.py CHANGED
@@ -23,19 +23,19 @@ from ee import oauth
23
23
 
24
24
  class ServiceAccountCredentialsTest(unittest.TestCase):
25
25
 
26
- def testNoArgs(self):
26
+ def test_no_args(self):
27
27
  with self.assertRaisesRegex(ValueError, 'At least one of'):
28
28
  ee.ServiceAccountCredentials()
29
29
 
30
30
  @mock.patch('google.oauth2.service_account.Credentials')
31
- def testJsonFile(self, mock_credentials):
31
+ def test_json_file(self, mock_credentials):
32
32
  ee.ServiceAccountCredentials(key_file='foo.json')
33
33
  mock_credentials.from_service_account_file.assert_called_with(
34
34
  'foo.json', scopes=oauth.SCOPES
35
35
  )
36
36
 
37
37
  @mock.patch('google.oauth2.service_account.Credentials')
38
- def testJsonKeyData(self, mock_credentials):
38
+ def test_json_key_data(self, mock_credentials):
39
39
  key_data = {'client_email': 'foo@bar.com'}
40
40
  ee.ServiceAccountCredentials(key_data=json.dumps(key_data))
41
41
  mock_credentials.from_service_account_info.assert_called_with(
@@ -44,7 +44,7 @@ class ServiceAccountCredentialsTest(unittest.TestCase):
44
44
 
45
45
  @mock.patch('google.auth.crypt.RSASigner')
46
46
  @mock.patch('google.oauth2.service_account.Credentials')
47
- def testPemKeyData(self, mock_credentials, mock_signer):
47
+ def test_pem_key_data(self, mock_credentials, mock_signer):
48
48
  ee.ServiceAccountCredentials(email='foo@bar.com', key_data='pem_key_data')
49
49
  mock_signer.from_string.assert_called_with('pem_key_data')
50
50
  self.assertEqual(
@@ -53,7 +53,7 @@ class ServiceAccountCredentialsTest(unittest.TestCase):
53
53
 
54
54
  @mock.patch('google.auth.crypt.RSASigner')
55
55
  @mock.patch('google.oauth2.service_account.Credentials')
56
- def testPemFile(self, mock_credentials, mock_signer):
56
+ def test_pem_file(self, mock_credentials, mock_signer):
57
57
  with mock.patch.object(
58
58
  _helpers, 'open', mock.mock_open(read_data='pem_key_data')
59
59
  ):
@@ -63,7 +63,7 @@ class ServiceAccountCredentialsTest(unittest.TestCase):
63
63
  mock_credentials.call_args[0][1], 'foo@bar.com'
64
64
  )
65
65
 
66
- def testBadJsonKeyData(self):
66
+ def test_bad_json_key_data(self):
67
67
  # This causes a different failure based on where the test is run.
68
68
  message = r'Could not deserialize key data|No key could be detected'
69
69
  with self.assertRaisesRegex(ValueError, message):
@@ -80,21 +80,21 @@ class ProfilingTest(apitestcase.ApiTestCase):
80
80
  ) and value.func == apifunction.ApiFunction.lookup('Profile.getProfiles')
81
81
  return f'hooked={hooked} getProfiles={is_get_profiles}'
82
82
 
83
- def testProfilePrinting(self):
83
+ def test_profile_printing(self):
84
84
  ee.data.computeValue = self.MockValue
85
85
  out = io.StringIO()
86
86
  with ee.profilePrinting(destination=out):
87
87
  self.assertEqual('hooked=True getProfiles=False', ee.Number(1).getInfo())
88
88
  self.assertEqual('hooked=False getProfiles=True', out.getvalue())
89
89
 
90
- def testProfilePrintingDefaultSmoke(self):
90
+ def test_profile_printing_default_smoke(self):
91
91
  # This will print to sys.stderr, so we can't make any assertions about the
92
92
  # output. But we can check that it doesn't fail.
93
93
  ee.data.computeValue = self.MockValue
94
94
  with ee.profilePrinting():
95
95
  self.assertEqual('hooked=True getProfiles=False', ee.Number(1).getInfo())
96
96
 
97
- def testProfilePrintingErrorGettingProfiles(self):
97
+ def test_profile_printing_error_getting_profiles(self):
98
98
  ee.data.computeValue = self.MockValue
99
99
  mock = unittest.mock.Mock()
100
100
  mock.call.side_effect = ee_exception.EEException('test')