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.
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/METADATA +3 -3
- earthengine_api-1.7.4.dist-info/RECORD +109 -0
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/WHEEL +1 -1
- ee/__init__.py +29 -28
- ee/_arg_types.py +7 -6
- ee/_cloud_api_utils.py +95 -78
- ee/_helpers.py +17 -13
- ee/_state.py +105 -0
- ee/_utils.py +2 -1
- ee/apifunction.py +21 -19
- ee/apitestcase.py +33 -38
- ee/batch.py +87 -77
- ee/blob.py +10 -12
- ee/classifier.py +57 -59
- ee/cli/commands.py +178 -114
- ee/cli/eecli.py +1 -1
- ee/cli/utils.py +61 -42
- ee/clusterer.py +39 -41
- ee/collection.py +64 -54
- ee/computedobject.py +19 -16
- ee/confusionmatrix.py +9 -9
- ee/customfunction.py +13 -12
- ee/data.py +220 -322
- ee/daterange.py +10 -10
- ee/deprecation.py +21 -13
- ee/deserializer.py +25 -20
- ee/dictionary.py +11 -11
- ee/ee_array.py +22 -20
- ee/ee_date.py +23 -23
- ee/ee_list.py +15 -16
- ee/ee_number.py +11 -21
- ee/ee_string.py +24 -32
- ee/ee_types.py +4 -4
- ee/element.py +15 -15
- ee/encodable.py +7 -4
- ee/errormargin.py +4 -4
- ee/feature.py +68 -71
- ee/featurecollection.py +41 -40
- ee/filter.py +90 -92
- ee/function.py +8 -8
- ee/geometry.py +95 -93
- ee/image.py +238 -236
- ee/image_converter.py +4 -4
- ee/imagecollection.py +30 -27
- ee/join.py +13 -15
- ee/kernel.py +55 -57
- ee/mapclient.py +9 -9
- ee/model.py +29 -31
- ee/oauth.py +76 -63
- ee/pixeltype.py +6 -6
- ee/projection.py +5 -4
- ee/reducer.py +41 -41
- ee/serializer.py +14 -14
- ee/table_converter.py +7 -6
- ee/terrain.py +7 -9
- ee/tests/_cloud_api_utils_test.py +21 -6
- ee/tests/_helpers_test.py +57 -4
- ee/tests/_state_test.py +49 -0
- ee/tests/algorithms.json +85 -2
- ee/tests/apifunction_test.py +5 -5
- ee/tests/batch_test.py +135 -57
- ee/tests/blob_test.py +5 -5
- ee/tests/classifier_test.py +3 -3
- ee/tests/clusterer_test.py +3 -3
- ee/tests/collection_test.py +48 -13
- ee/tests/confusionmatrix_test.py +3 -3
- ee/tests/data_test.py +484 -55
- ee/tests/daterange_test.py +4 -4
- ee/tests/deprecation_test.py +6 -4
- ee/tests/deserializer_test.py +64 -5
- ee/tests/dictionary_test.py +12 -12
- ee/tests/ee_array_test.py +3 -3
- ee/tests/ee_date_test.py +4 -4
- ee/tests/ee_list_test.py +3 -3
- ee/tests/ee_number_test.py +75 -30
- ee/tests/ee_string_test.py +11 -3
- ee/tests/ee_test.py +40 -22
- ee/tests/element_test.py +2 -2
- ee/tests/errormargin_test.py +1 -1
- ee/tests/feature_test.py +10 -10
- ee/tests/featurecollection_test.py +3 -3
- ee/tests/filter_test.py +4 -4
- ee/tests/function_test.py +5 -5
- ee/tests/geometry_point_test.py +3 -3
- ee/tests/geometry_test.py +93 -52
- ee/tests/image_converter_test.py +1 -3
- ee/tests/image_test.py +3 -3
- ee/tests/imagecollection_test.py +3 -3
- ee/tests/join_test.py +3 -3
- ee/tests/kernel_test.py +7 -3
- ee/tests/model_test.py +17 -5
- ee/tests/oauth_test.py +189 -7
- ee/tests/pixeltype_test.py +6 -7
- ee/tests/projection_test.py +5 -6
- ee/tests/reducer_test.py +16 -3
- ee/tests/serializer_test.py +39 -12
- ee/tests/table_converter_test.py +51 -7
- ee/tests/terrain_test.py +11 -3
- earthengine_api-1.5.13rc0.dist-info/RECORD +0 -107
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/entry_points.txt +0 -0
- {earthengine_api-1.5.13rc0.dist-info → earthengine_api-1.7.4.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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(
|
|
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
|
|
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
|
|
61
|
+
return f'{self.test_tmpdir}/tempfile'
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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()
|
ee/tests/pixeltype_test.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import enum
|
|
5
5
|
import json
|
|
6
|
-
from typing import Any
|
|
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:
|
|
29
|
-
) ->
|
|
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) ->
|
|
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) ->
|
|
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
|
-
|
|
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):
|
ee/tests/projection_test.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""Tests for the ee.Projection module."""
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
-
from typing import Any
|
|
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:
|
|
28
|
-
) ->
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
16
|
-
) ->
|
|
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
|
ee/tests/serializer_test.py
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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]) ->
|
|
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
|
-
) ->
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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()
|
ee/tests/table_converter_test.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Tests for the table_converter module."""
|
|
3
3
|
|
|
4
|
-
|
|
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:
|
|
19
|
-
) ->
|
|
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:
|
|
32
|
-
)
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
23
|
-
) ->
|
|
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()
|