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/tests/oauth_test.py CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  """Test for the oauth module."""
3
3
 
4
+ import http.client
5
+ import io
4
6
  import json
5
7
  import sys
6
8
  import tempfile
@@ -8,6 +10,7 @@ from unittest import mock
8
10
  import urllib.parse
9
11
 
10
12
  import unittest
13
+ from ee import ee_exception
11
14
  from ee import oauth
12
15
 
13
16
 
@@ -17,7 +20,7 @@ class OAuthTest(unittest.TestCase):
17
20
  super().setUp()
18
21
  self.test_tmpdir = tempfile.mkdtemp()
19
22
 
20
- def testRequestToken(self):
23
+ def test_request_token(self):
21
24
 
22
25
  class MockResponse:
23
26
 
@@ -33,20 +36,33 @@ class OAuthTest(unittest.TestCase):
33
36
  self.assertEqual('xyz', parsed[b'code_verifier'][0].decode())
34
37
  return MockResponse(parsed[b'code'][0])
35
38
 
36
- with mock.patch('urllib.request.urlopen', new=mock_urlopen):
39
+ with mock.patch.object(urllib.request, 'urlopen', new=mock_urlopen):
37
40
  auth_code = '123'
38
41
  verifier = 'xyz'
39
42
  refresh_token = oauth.request_token(auth_code, verifier)
40
43
  self.assertEqual('123456', refresh_token)
41
44
 
42
- def testWriteToken(self):
45
+ def test_request_token_http_error(self):
46
+ mock_fp = io.BytesIO(b'error details')
47
+ http_error = urllib.error.HTTPError(
48
+ 'url', 400, 'message', hdrs=http.client.HTTPMessage(), fp=mock_fp
49
+ )
50
+ with mock.patch.object(urllib.request, 'urlopen', side_effect=http_error):
51
+ with self.assertRaisesRegex(
52
+ Exception,
53
+ r"Problem requesting tokens.*HTTP Error 400: message.*b'error"
54
+ r" details'",
55
+ ):
56
+ oauth.request_token('auth_code', 'code_verifier')
57
+
58
+ def test_write_token(self):
43
59
 
44
60
  def mock_credentials_path():
45
- return self.test_tmpdir + '/tempfile'
61
+ return f'{self.test_tmpdir}/tempfile'
46
62
 
47
- oauth_pkg = 'ee.oauth'
48
- with mock.patch(
49
- oauth_pkg + '.get_credentials_path', new=mock_credentials_path):
63
+ with mock.patch.object(
64
+ oauth, 'get_credentials_path', new=mock_credentials_path
65
+ ):
50
66
  client_info = dict(refresh_token='123')
51
67
  oauth.write_private_json(oauth.get_credentials_path(), client_info)
52
68
 
@@ -54,6 +70,46 @@ class OAuthTest(unittest.TestCase):
54
70
  token = json.load(f)
55
71
  self.assertEqual({'refresh_token': '123'}, token)
56
72
 
73
+ def test_get_credentials_arguments(self):
74
+ credentials_path = f'{self.test_tmpdir}/temp_creds'
75
+
76
+ creds = {
77
+ 'refresh_token': 'REFRESH_TOKEN',
78
+ 'client_id': 'CLIENT_ID',
79
+ 'project': 'PROJECT',
80
+ }
81
+ with open(credentials_path, 'w') as f:
82
+ json.dump(creds, f)
83
+
84
+ with mock.patch.object(
85
+ oauth, 'get_credentials_path', return_value=credentials_path
86
+ ):
87
+ args = oauth.get_credentials_arguments()
88
+
89
+ expected_args = {
90
+ 'token_uri': oauth.TOKEN_URI,
91
+ 'refresh_token': 'REFRESH_TOKEN',
92
+ 'client_id': 'CLIENT_ID',
93
+ 'client_secret': oauth.CLIENT_SECRET,
94
+ 'scopes': oauth.SCOPES,
95
+ 'quota_project_id': 'PROJECT',
96
+ }
97
+ self.assertEqual(expected_args, args)
98
+
99
+ def test_is_valid_credentials(self):
100
+ self.assertFalse(oauth.is_valid_credentials(None))
101
+
102
+ mock_credentials_valid = mock.MagicMock()
103
+ self.assertTrue(oauth.is_valid_credentials(mock_credentials_valid))
104
+ mock_credentials_valid.refresh.assert_called_once()
105
+
106
+ mock_credentials_invalid = mock.MagicMock()
107
+ mock_credentials_invalid.refresh.side_effect = (
108
+ oauth.google.auth.exceptions.RefreshError()
109
+ )
110
+ self.assertFalse(oauth.is_valid_credentials(mock_credentials_invalid))
111
+ mock_credentials_invalid.refresh.assert_called_once()
112
+
57
113
  def test_in_colab_shell(self):
58
114
  with mock.patch.dict(sys.modules, {'google.colab': None}):
59
115
  self.assertFalse(oauth.in_colab_shell())
@@ -77,5 +133,131 @@ class OAuthTest(unittest.TestCase):
77
133
  )
78
134
  )
79
135
 
136
+ def test_colab_mode_with_nonstandard_scopes_raises_exception(self):
137
+ with self.assertRaisesRegex(
138
+ ee_exception.EEException,
139
+ 'Scopes cannot be customized when auth_mode is "colab".'
140
+ ):
141
+ oauth.authenticate(
142
+ auth_mode='colab',
143
+ scopes=['https://www.googleapis.com/auth/earthengine.readonly']
144
+ )
145
+
146
+ def test_colab_auth_mode_with_standard_scopes_succeeds(self):
147
+ # Should not raise an exception if the scopes are not narrowed.
148
+ with mock.patch.dict(sys.modules, {'google.colab': mock.MagicMock()}):
149
+ try:
150
+ oauth.authenticate(auth_mode='colab', scopes=oauth.SCOPES)
151
+ except ee_exception.EEException:
152
+ self.fail('authenticate raised an exception unexpectedly.')
153
+
154
+ def test_authenticate_appdefault(self):
155
+ with mock.patch.object(
156
+ oauth,
157
+ '_valid_credentials_exist',
158
+ return_value=False,
159
+ ), mock.patch.object(
160
+ sys, 'stderr', new_callable=io.StringIO
161
+ ) as mock_stderr:
162
+ oauth.authenticate(auth_mode='appdefault')
163
+ self.assertIn('appdefault no longer necessary', mock_stderr.getvalue())
164
+
165
+ @mock.patch.object(oauth, '_load_gcloud_credentials')
166
+ @mock.patch.object(oauth, '_valid_credentials_exist', return_value=False)
167
+ @mock.patch.object(oauth, 'in_colab_shell', return_value=False)
168
+ @mock.patch.object(oauth, '_in_jupyter_shell', return_value=False)
169
+ @mock.patch.object(oauth, '_localhost_is_viable', return_value=False)
170
+ def test_authenticate_default_gcloud(
171
+ self,
172
+ mock_localhost_viable,
173
+ mock_jupyter,
174
+ mock_colab,
175
+ mock_valid_creds,
176
+ mock_load_gcloud,
177
+ ):
178
+ del (
179
+ mock_localhost_viable,
180
+ mock_jupyter,
181
+ mock_colab,
182
+ mock_valid_creds,
183
+ ) # Unused
184
+ oauth.authenticate()
185
+ mock_load_gcloud.assert_called_once_with(None, None, False)
186
+
187
+ def test_localhost_fetch_code(self):
188
+ mock_server = mock.MagicMock()
189
+ mock_server.url = 'http://localhost:8085'
190
+ mock_server.fetch_code.return_value = 'FETCHED_CODE'
191
+ with mock.patch.object(
192
+ oauth, '_start_server', return_value=mock_server
193
+ ), mock.patch.object(oauth, '_obtain_and_write_token') as mock_obtain:
194
+ flow = oauth.Flow(auth_mode='localhost')
195
+ flow.save_code()
196
+ mock_server.fetch_code.assert_called_once()
197
+ mock_obtain.assert_called_once_with(
198
+ 'FETCHED_CODE',
199
+ flow.code_verifier,
200
+ flow.scopes,
201
+ 'http://localhost:8085',
202
+ )
203
+
204
+ @mock.patch.object(oauth, '_display_auth_instructions_for_noninteractive')
205
+ def test_display_instructions_quiet(self, mock_display):
206
+ flow = oauth.Flow(auth_mode='notebook')
207
+ self.assertTrue(flow.display_instructions(quiet=True))
208
+ mock_display.assert_called_once_with(flow.auth_url, flow.code_verifier)
209
+
210
+ @mock.patch.object(oauth, '_display_auth_instructions_with_print')
211
+ @mock.patch.object(oauth, 'in_colab_shell', return_value=True)
212
+ def test_display_instructions_colab(self, mock_in_colab, mock_display_print):
213
+ del mock_in_colab # Unused
214
+ flow = oauth.Flow(auth_mode='notebook')
215
+ self.assertTrue(flow.display_instructions())
216
+ mock_display_print.assert_called_once_with(flow.auth_url, None)
217
+
218
+ @mock.patch.object(oauth, '_display_auth_instructions_with_html')
219
+ @mock.patch.object(oauth, '_in_jupyter_shell', return_value=True)
220
+ def test_display_instructions_jupyter(
221
+ self, mock_in_jupyter, mock_display_html
222
+ ):
223
+ del mock_in_jupyter # Unused
224
+ flow = oauth.Flow(auth_mode='notebook')
225
+ self.assertTrue(flow.display_instructions())
226
+ mock_display_html.assert_called_once_with(flow.auth_url, None)
227
+
228
+ @mock.patch.object(oauth, '_display_auth_instructions_with_print')
229
+ @mock.patch.object(oauth, 'in_colab_shell', return_value=False)
230
+ @mock.patch.object(oauth, '_in_jupyter_shell', return_value=False)
231
+ def test_display_instructions_print(
232
+ self, mock_in_jupyter, mock_in_colab, mock_display_print
233
+ ):
234
+ del mock_in_jupyter, mock_in_colab # Unused
235
+ flow = oauth.Flow(auth_mode='notebook')
236
+ self.assertTrue(flow.display_instructions())
237
+ mock_display_print.assert_called_once_with(flow.auth_url, None)
238
+
239
+ @mock.patch.object(oauth, '_display_auth_instructions_with_print')
240
+ @mock.patch.object(oauth, 'in_colab_shell', return_value=True)
241
+ @mock.patch.object(oauth.http.server, 'HTTPServer')
242
+ def test_display_instructions_localhost_colab(
243
+ self, mock_http_server, mock_in_colab, mock_display_print
244
+ ):
245
+ del mock_http_server, mock_in_colab # Unused
246
+ flow = oauth.Flow(auth_mode='localhost')
247
+ self.assertTrue(flow.display_instructions())
248
+ mock_display_print.assert_called_once_with(
249
+ flow.auth_url, oauth.WAITING_CODA
250
+ )
251
+
252
+ def test_flow_unknown_auth_mode(self):
253
+ with self.assertRaisesRegex(ee_exception.EEException, 'Unknown auth_mode'):
254
+ oauth.Flow(auth_mode='unknown')
255
+
256
+ def test_flow_localhost_with_port(self):
257
+ with mock.patch.object(oauth, '_start_server') as mock_start_server:
258
+ oauth.Flow(auth_mode='localhost:1234')
259
+ mock_start_server.assert_called_once_with(1234)
260
+
261
+
80
262
  if __name__ == '__main__':
81
263
  unittest.main()
@@ -3,7 +3,7 @@
3
3
 
4
4
  import enum
5
5
  import json
6
- from typing import Any, Dict
6
+ from typing import Any
7
7
 
8
8
  import unittest
9
9
  import ee
@@ -25,15 +25,15 @@ PIXELTYPE = 'PixelType'
25
25
 
26
26
 
27
27
  def make_expression_graph(
28
- function_invocation_value: Dict[str, Any]
29
- ) -> Dict[str, Any]:
28
+ function_invocation_value: dict[str, Any]
29
+ ) -> dict[str, Any]:
30
30
  return {
31
31
  'result': '0',
32
32
  'values': {'0': {'functionInvocationValue': function_invocation_value}},
33
33
  }
34
34
 
35
35
 
36
- def pixeltype_function_expr(value: Type) -> Dict[str, Any]:
36
+ def pixeltype_function_expr(value: Type) -> dict[str, Any]:
37
37
  return {
38
38
  'functionInvocationValue': {
39
39
  'functionName': 'PixelType',
@@ -42,7 +42,7 @@ def pixeltype_function_expr(value: Type) -> Dict[str, Any]:
42
42
  }
43
43
 
44
44
 
45
- def pixeltype_noargs_expr(type_name: str) -> Dict[str, Any]:
45
+ def pixeltype_noargs_expr(type_name: str) -> dict[str, Any]:
46
46
  return {
47
47
  'result': '0',
48
48
  'values': {
@@ -78,7 +78,7 @@ class PixelTypeTest(apitestcase.ApiTestCase):
78
78
 
79
79
  self.assertFalse(pixeltype.isVariable())
80
80
  self.assertEqual(
81
- set([DIMENSIONS_KEY, MAX_VALUE_KEY, MIN_VALUE_KEY, PRECISION_KEY]),
81
+ {DIMENSIONS_KEY, MAX_VALUE_KEY, MIN_VALUE_KEY, PRECISION_KEY},
82
82
  set(pixeltype.args),
83
83
  )
84
84
  expected_dimensions = {'result': '0', 'values': {'0': {'constantValue': 2}}}
@@ -294,7 +294,6 @@ class PixelTypeTest(apitestcase.ApiTestCase):
294
294
  expect = pixeltype_noargs_expr('int16')
295
295
  expression = ee.PixelType.int16()
296
296
  result = json.loads(expression.serialize())
297
- print(json.dumps(result, indent=2))
298
297
  self.assertEqual(expect, result)
299
298
 
300
299
  def test_int32(self):
@@ -2,7 +2,7 @@
2
2
  """Tests for the ee.Projection module."""
3
3
 
4
4
  import json
5
- from typing import Any, Dict
5
+ from typing import Any
6
6
 
7
7
  import unittest
8
8
  import ee
@@ -24,8 +24,8 @@ PROJECTION_EPSG_4326 = {
24
24
 
25
25
 
26
26
  def make_expression_graph(
27
- function_invocation_value: Dict[str, Any],
28
- ) -> Dict[str, Any]:
27
+ function_invocation_value: dict[str, Any],
28
+ ) -> dict[str, Any]:
29
29
  return {
30
30
  'result': '0',
31
31
  'values': {'0': {'functionInvocationValue': function_invocation_value}},
@@ -44,7 +44,7 @@ class ProjectionTest(apitestcase.ApiTestCase):
44
44
  projection_func = ee.ApiFunction.lookup('Projection')
45
45
  self.assertEqual(projection_func, projection.func)
46
46
  self.assertFalse(projection.isVariable())
47
- self.assertEqual(set(['crs', 'transform']), set(projection.args))
47
+ self.assertEqual({'crs', 'transform'}, set(projection.args))
48
48
  self.assertEqual(EPSG_4326, projection.args['crs'])
49
49
  expected_transform = {
50
50
  'result': '0',
@@ -71,7 +71,7 @@ class ProjectionTest(apitestcase.ApiTestCase):
71
71
 
72
72
  projection_func = ee.ApiFunction.lookup('Projection')
73
73
  self.assertEqual(projection_func, projection.func)
74
- self.assertEqual(set(['crs', 'transformWkt']), set(projection.args))
74
+ self.assertEqual({'crs', 'transformWkt'}, set(projection.args))
75
75
  self.assertEqual(EPSG_4326, projection.args['crs'])
76
76
  expected_transform_wkt = {
77
77
  'result': '0',
@@ -169,7 +169,6 @@ class ProjectionTest(apitestcase.ApiTestCase):
169
169
  })
170
170
  expression = ee.Projection(EPSG_4326).nominalScale()
171
171
  result = json.loads(expression.serialize())
172
- print(result)
173
172
  self.assertEqual(expect, result)
174
173
 
175
174
  def test_scale(self):
ee/tests/reducer_test.py CHANGED
@@ -2,7 +2,7 @@
2
2
  """Tests for ee.Reducer module."""
3
3
 
4
4
  import json
5
- from typing import Any, Dict
5
+ from typing import Any
6
6
 
7
7
  import unittest
8
8
  import ee
@@ -12,8 +12,8 @@ TO_LIST = 'Reducer.toList'
12
12
 
13
13
 
14
14
  def make_expression_graph(
15
- function_invocation_value: Dict[str, Any],
16
- ) -> Dict[str, Any]:
15
+ function_invocation_value: dict[str, Any],
16
+ ) -> dict[str, Any]:
17
17
  return {
18
18
  'result': '0',
19
19
  'values': {'0': {'functionInvocationValue': function_invocation_value}},
@@ -809,6 +809,19 @@ class ReducerTest(apitestcase.ApiTestCase):
809
809
  result = json.loads(expression.serialize())
810
810
  self.assertEqual(expect, result)
811
811
 
812
+ def test_ridge_regression_kwargs_with_lambda(self):
813
+ with self.assertRaisesRegex(
814
+ ValueError, 'lambda_ cannot be set when providing kwargs.'
815
+ ):
816
+ ee.Reducer.ridgeRegression(1, lambda_=3, **{'lambda': 4})
817
+
818
+ def test_ridge_regression_unexpected_kwargs(self):
819
+ with self.assertRaisesRegex(
820
+ ValueError,
821
+ r"Unexpected arguments: \['unexpected'\]\. Expected: lambda.",
822
+ ):
823
+ ee.Reducer.ridgeRegression(1, unexpected=4)
824
+
812
825
  def test_robust_linear_regression(self):
813
826
  num_x = 1
814
827
  num_y = 2
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
  """Test for the ee.serializer module."""
3
3
 
4
+ from collections.abc import Callable
4
5
  import datetime
5
6
  import json
6
- from typing import Any, Callable, Dict, List, Union
7
+ from typing import Any
7
8
 
8
9
  import unittest
9
10
  import ee
@@ -11,7 +12,7 @@ from ee import apitestcase
11
12
  from ee import serializer
12
13
 
13
14
 
14
- def _max_depth(x: Union[Dict[str, Any], List[Any], str]) -> int:
15
+ def _max_depth(x: dict[str, Any] | list[Any] | str) -> int:
15
16
  """Computes the maximum nesting level of some dict, list, or str."""
16
17
  if isinstance(x, dict):
17
18
  return 1 + max(_max_depth(v) for v in x.values())
@@ -23,7 +24,7 @@ def _max_depth(x: Union[Dict[str, Any], List[Any], str]) -> int:
23
24
 
24
25
  class DatetimeToMicrosecondsTest(unittest.TestCase):
25
26
 
26
- def testDatetimeToMicrosecondsNaive(self):
27
+ def test_datetime_to_microseconds_naive(self):
27
28
  self.assertEqual(
28
29
  0,
29
30
  serializer.DatetimeToMicroseconds(
@@ -51,7 +52,7 @@ class DatetimeToMicrosecondsTest(unittest.TestCase):
51
52
  serializer.DatetimeToMicroseconds(datetime.datetime(1906, 4, 18)),
52
53
  )
53
54
 
54
- def testDatetimeToMicroseconds(self):
55
+ def test_datetime_to_microseconds(self):
55
56
  self.assertEqual(
56
57
  0,
57
58
  serializer.DatetimeToMicroseconds(
@@ -88,7 +89,7 @@ class DatetimeToMicrosecondsTest(unittest.TestCase):
88
89
 
89
90
  class SerializerTest(apitestcase.ApiTestCase):
90
91
 
91
- def testSerialization(self):
92
+ def test_serialization(self):
92
93
  """Verifies a complex serialization case."""
93
94
 
94
95
  class ByteString(ee.Encodable):
@@ -103,13 +104,13 @@ class SerializerTest(apitestcase.ApiTestCase):
103
104
  self._value = value
104
105
 
105
106
  # pylint: disable-next=g-bad-name
106
- def encode(self, encoder: Callable[[Any], Any]) -> Dict[str, Any]:
107
+ def encode(self, encoder: Callable[[Any], Any]) -> dict[str, Any]:
107
108
  del encoder # Unused.
108
109
  return {'type': 'Bytes', 'value': self._value}
109
110
 
110
111
  def encode_cloud_value(
111
112
  self, encoder: Callable[[Any], Any]
112
- ) -> Dict[str, str]:
113
+ ) -> dict[str, str]:
113
114
  del encoder # Unused.
114
115
  # Proto3 JSON embedding of "bytes" values uses base64 encoding, which
115
116
  # this already is.
@@ -160,7 +161,7 @@ class SerializerTest(apitestcase.ApiTestCase):
160
161
  decoded_encoded_json = json.loads(encoded_json)
161
162
  self.assertEqual(encoded, decoded_encoded_json)
162
163
 
163
- def testRepeats(self):
164
+ def test_repeats(self):
164
165
  """Verifies serialization finds and removes repeated values."""
165
166
  # pylint: disable-next=no-member
166
167
  test1 = ee.Image(5).mask(ee.Image(5))
@@ -257,7 +258,7 @@ class SerializerTest(apitestcase.ApiTestCase):
257
258
  expected_cloud_pretty,
258
259
  serializer.encode(test1, is_compound=False, for_cloud_api=True))
259
260
 
260
- def testDepthLimit_withAlgorithms(self):
261
+ def test_depth_limit_with_algorithms(self):
261
262
  x = ee.Number(0)
262
263
  for i in range(100):
263
264
  x = x.add(ee.Number(i))
@@ -266,24 +267,50 @@ class SerializerTest(apitestcase.ApiTestCase):
266
267
  # on the test.
267
268
  self.assertLess(_max_depth(encoded), 60)
268
269
 
269
- def testDepthLimit_withLists(self):
270
+ def test_depth_limit_with_lists(self):
270
271
  x = ee.List([0])
271
272
  for i in range(100):
272
273
  x = ee.List([i, x])
273
274
  encoded = serializer.encode(x, for_cloud_api=True)
274
275
  self.assertLess(_max_depth(encoded), 60)
275
276
 
276
- def testDepthLimit_withDictionaries(self):
277
+ def test_depth_limit_with_dictionaries(self):
277
278
  x = ee.Dictionary({0: 0})
278
279
  for i in range(100):
279
280
  x = ee.Dictionary({i: x})
280
281
  encoded = serializer.encode(x, for_cloud_api=True)
281
282
  self.assertLess(_max_depth(encoded), 60)
282
283
 
283
- def testToJsonOptParams(self):
284
+ def test_to_json_opt_params(self):
284
285
  self.assertIn('\n', serializer.toJSON(ee.Image(0), opt_pretty=True))
285
286
  self.assertNotIn('\n', serializer.toJSON(ee.Image(0), opt_pretty=False))
286
287
 
288
+ def test_datetime_serialization_legacy(self):
289
+ a_date = datetime.datetime(2014, 8, 10, tzinfo=datetime.timezone.utc)
290
+ # 1407628800000000 microseconds.
291
+ expected = {
292
+ 'type': 'CompoundValue',
293
+ 'scope': [],
294
+ 'value': {
295
+ 'type': 'Invocation',
296
+ 'functionName': 'Date',
297
+ 'arguments': {'value': 1407628800000.0},
298
+ },
299
+ }
300
+ self.assertEqual(expected, serializer.encode(a_date, for_cloud_api=False))
301
+
302
+ def test_single_value_no_compound(self):
303
+ """Verifies serialization of a single non-primitive value."""
304
+ self.assertEqual([1], serializer.encode([1], for_cloud_api=False))
305
+
306
+ def test_unencodable_object(self):
307
+ class Unencodable:
308
+ pass
309
+ with self.assertRaisesRegex(ee.EEException, 'Cannot encode object: .*'):
310
+ serializer.encode(Unencodable(), for_cloud_api=False)
311
+ with self.assertRaisesRegex(ee.EEException, 'Cannot encode object: .*'):
312
+ serializer.encode(Unencodable(), for_cloud_api=True)
313
+
287
314
 
288
315
  if __name__ == '__main__':
289
316
  unittest.main()
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  """Tests for the table_converter module."""
3
3
 
4
- from typing import Any, Dict, Optional, Type
4
+ import builtins
5
+ from typing import Any
6
+ from unittest import mock
5
7
 
6
8
  from absl.testing import parameterized
7
9
  import geopandas
@@ -15,8 +17,8 @@ from ee import table_converter
15
17
  class TableConverterTest(parameterized.TestCase):
16
18
 
17
19
  def _make_feature(
18
- self, geometry: Dict[str, Any], properties: Dict[str, Any]
19
- ) -> Dict[str, Any]:
20
+ self, geometry: dict[str, Any], properties: dict[str, Any]
21
+ ) -> dict[str, Any]:
20
22
  return {'type': 'Feature', 'geometry': geometry, 'properties': properties}
21
23
 
22
24
  @parameterized.named_parameters(
@@ -28,8 +30,8 @@ class TableConverterTest(parameterized.TestCase):
28
30
  def test_from_file_format(
29
31
  self,
30
32
  data_format: str,
31
- expected: Optional[Type[table_converter.TableConverter]],
32
- ) -> None:
33
+ expected: type[table_converter.TableConverter] | None,
34
+ ):
33
35
  """Verifies `from_file_format` returns the correct converter class."""
34
36
  if expected is None:
35
37
  self.assertIsNone(table_converter.from_file_format(data_format))
@@ -38,7 +40,17 @@ class TableConverterTest(parameterized.TestCase):
38
40
  table_converter.from_file_format(data_format), expected
39
41
  )
40
42
 
41
- def test_pandas_converter(self) -> None:
43
+ def test_from_file_format_instance(self):
44
+ """Verifies `from_file_format` returns the same instance."""
45
+ converter = table_converter.PandasConverter()
46
+ self.assertIs(table_converter.from_file_format(converter), converter)
47
+
48
+ def test_table_converter_fails(self):
49
+ """Verifies `TableConverter` cannot be used for conversion."""
50
+ with self.assertRaises(NotImplementedError):
51
+ table_converter.TableConverter().do_conversion(iter([]))
52
+
53
+ def test_pandas_converter(self):
42
54
  """Verifies `PandasConverter` does the correct conversion."""
43
55
  converter = table_converter.PandasConverter()
44
56
 
@@ -69,7 +81,23 @@ class TableConverterTest(parameterized.TestCase):
69
81
  ]),
70
82
  )
71
83
 
72
- def test_geopandas_converter(self) -> None:
84
+ def test_pandas_converter_importerror(self):
85
+ """Ensures ImportError is raised when pandas is not available."""
86
+ real_import = builtins.__import__
87
+
88
+ def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
89
+ if name == 'pandas':
90
+ raise ImportError
91
+ return real_import(name, globals, locals, fromlist, level)
92
+
93
+ with mock.patch('builtins.__import__', mock_import):
94
+ converter = table_converter.PandasConverter()
95
+ with self.assertRaisesRegex(
96
+ ImportError, 'Using format PANDAS_DATAFRAME requires pandas.'
97
+ ):
98
+ converter.do_conversion(iter([]))
99
+
100
+ def test_geopandas_converter(self):
73
101
  """Verifies `GeoPandasConverter` does the correct conversion."""
74
102
  converter = table_converter.GeoPandasConverter()
75
103
 
@@ -105,6 +133,22 @@ class TableConverterTest(parameterized.TestCase):
105
133
  geopandas.GeoDataFrame.from_features(feature_coll),
106
134
  )
107
135
 
136
+ def test_geopandas_converter_importerror(self):
137
+ """Ensures ImportError is raised when geopandas is not available."""
138
+ real_import = builtins.__import__
139
+
140
+ def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
141
+ if name == 'geopandas':
142
+ raise ImportError
143
+ return real_import(name, globals, locals, fromlist, level)
144
+
145
+ with mock.patch('builtins.__import__', mock_import):
146
+ converter = table_converter.GeoPandasConverter()
147
+ with self.assertRaisesRegex(
148
+ ImportError, 'Using format GEOPANDAS_GEODATAFRAME requires geopandas.'
149
+ ):
150
+ converter.do_conversion(iter([]))
151
+
108
152
 
109
153
  if __name__ == '__main__':
110
154
  unittest.main()
ee/tests/terrain_test.py CHANGED
@@ -2,7 +2,7 @@
2
2
  """Tests for the ee.Terrain module."""
3
3
 
4
4
  import json
5
- from typing import Any, Dict
5
+ from typing import Any
6
6
 
7
7
  import unittest
8
8
  import ee
@@ -19,8 +19,8 @@ IMAGE_EXPRESSION_1 = {
19
19
 
20
20
 
21
21
  def make_expression_graph(
22
- function_invocation_value: Dict[str, Any],
23
- ) -> Dict[str, Any]:
22
+ function_invocation_value: dict[str, Any],
23
+ ) -> dict[str, Any]:
24
24
  return {
25
25
  'result': '0',
26
26
  'values': {'0': {'functionInvocationValue': function_invocation_value}},
@@ -141,6 +141,14 @@ class TerrainTest(apitestcase.ApiTestCase):
141
141
  result = json.loads(expression.serialize())
142
142
  self.assertEqual(expect, result)
143
143
 
144
+ def test_init(self):
145
+ message = r'Terrain should not be used as an object.*'
146
+ with self.assertRaisesRegex(RuntimeError, message):
147
+ ee.Terrain()
148
+
149
+ def test_name(self):
150
+ self.assertEqual('Terrain', ee.Terrain.name())
151
+
144
152
 
145
153
  if __name__ == '__main__':
146
154
  unittest.main()