ee-client 2.2.0__tar.gz → 2.2.1__tar.gz
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.
- {ee_client-2.2.0 → ee_client-2.2.1}/PKG-INFO +1 -1
- {ee_client-2.2.0 → ee_client-2.2.1}/ee_client.egg-info/PKG-INFO +1 -1
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/__init__.py +1 -1
- ee_client-2.2.1/eeclient/exceptions.py +100 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/sepal_credential_mixin.py +36 -7
- {ee_client-2.2.0 → ee_client-2.2.1}/pyproject.toml +2 -2
- ee_client-2.2.0/eeclient/exceptions.py +0 -26
- {ee_client-2.2.0 → ee_client-2.2.1}/LICENSE +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/README.rst +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/ee_client.egg-info/SOURCES.txt +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/ee_client.egg-info/dependency_links.txt +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/ee_client.egg-info/requires.txt +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/ee_client.egg-info/top_level.txt +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/client.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/data.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/export/__init__.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/export/image.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/export/table.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/helpers.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/interfaces/__init__.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/interfaces/export.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/interfaces/operations.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/interfaces/tasks.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/models.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/oauth_app.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/eeclient/tasks.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/setup.cfg +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/tests/test_client.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/tests/test_data.py +0 -0
- {ee_client-2.2.0 → ee_client-2.2.1}/tests/test_models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: extends the capabilities of the earthengine-api by providing custom session management and client interactions
|
|
5
5
|
Author-email: Daniel Guerrero <dfgm2006@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/dfguerrerom/ee-client
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ee-client
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: extends the capabilities of the earthengine-api by providing custom session management and client interactions
|
|
5
5
|
Author-email: Daniel Guerrero <dfgm2006@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/dfguerrerom/ee-client
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from ee.ee_exception import EEException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EERestException(EEException):
|
|
5
|
+
def __init__(self, error):
|
|
6
|
+
self.message = error.get("message", "EE responded with an error")
|
|
7
|
+
super().__init__(self.message)
|
|
8
|
+
self.code = error.get("code", -1)
|
|
9
|
+
self.status = error.get("status", "UNDEFINED")
|
|
10
|
+
self.details = error.get("details")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EEClientError(Exception):
|
|
14
|
+
"""Custom exception class for EEClient errors."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, error):
|
|
17
|
+
if isinstance(error, str):
|
|
18
|
+
self.message = error
|
|
19
|
+
self.code = -1
|
|
20
|
+
self.status = "UNDEFINED"
|
|
21
|
+
self.details = None
|
|
22
|
+
else:
|
|
23
|
+
self.message = error.get("message", "EEClient responded with an error")
|
|
24
|
+
self.code = error.get("code", -1)
|
|
25
|
+
self.status = error.get("status", "UNDEFINED")
|
|
26
|
+
self.details = error.get("details")
|
|
27
|
+
super().__init__(self.message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CredentialsFileNotFoundError(EEClientError):
|
|
31
|
+
"""Raised when local credentials file is not found."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, file_path: str):
|
|
34
|
+
self.file_path = file_path
|
|
35
|
+
error = {
|
|
36
|
+
"code": 404,
|
|
37
|
+
"message": (
|
|
38
|
+
"There was an error when trying to authenticate with Google "
|
|
39
|
+
"Earth Engine. Make sure your GEE account is properly "
|
|
40
|
+
"connected to SEPAL. For more information, please visit "
|
|
41
|
+
"<a href='https://docs.sepal.io/en/latest/setup/gee.html' "
|
|
42
|
+
"target='_blank'> the documentation </a>."
|
|
43
|
+
),
|
|
44
|
+
"status": "CREDENTIALS_NOT_FOUND",
|
|
45
|
+
"details": f"Credentials file not found at {file_path}",
|
|
46
|
+
}
|
|
47
|
+
super().__init__(error)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CredentialsFileUnknownError(EEClientError):
|
|
51
|
+
"""Raised when unknown error occurred while accessing credentials file."""
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
error = {
|
|
55
|
+
"code": 500,
|
|
56
|
+
"message": (
|
|
57
|
+
"There was an error when trying to authenticate with Google "
|
|
58
|
+
"Earth Engine. Please re-authenticate your GEE account with "
|
|
59
|
+
"SEPAL. For more information, visit "
|
|
60
|
+
"<a href='https://docs.sepal.io/en/latest/setup/gee.html' "
|
|
61
|
+
"target='_blank'> the documentation </a>."
|
|
62
|
+
),
|
|
63
|
+
"status": "CREDENTIALS_ERROR",
|
|
64
|
+
"details": "Unknown error occurred while accessing credentials file",
|
|
65
|
+
}
|
|
66
|
+
super().__init__(error)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SepalCredentialsUnavailableError(EEClientError):
|
|
70
|
+
"""Raised when SEPAL API cannot provide credentials (e.g., 500 error)."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, status_code: int | None = None):
|
|
73
|
+
self.status_code = status_code
|
|
74
|
+
error = {
|
|
75
|
+
"code": status_code or 500,
|
|
76
|
+
"message": (
|
|
77
|
+
"There was an error when trying to authenticate with Google "
|
|
78
|
+
"Earth Engine. Make sure your GEE account is properly "
|
|
79
|
+
"connected to SEPAL. Please visit "
|
|
80
|
+
"<a href='https://docs.sepal.io/en/latest/setup/gee.html' "
|
|
81
|
+
"target='_blank'> the documentation </a> for more information."
|
|
82
|
+
),
|
|
83
|
+
"status": "SEPAL_CREDENTIALS_UNAVAILABLE",
|
|
84
|
+
"details": (
|
|
85
|
+
f"SEPAL API returned error {status_code}"
|
|
86
|
+
if status_code
|
|
87
|
+
else "SEPAL API is unable to provide credentials"
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
super().__init__(error)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# {'code': 401, 'message': 'Request had invalid authentication credentials.
|
|
94
|
+
# Expected OAuth 2 access token, login cookie or other valid authentication
|
|
95
|
+
# credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
|
|
96
|
+
# 'status': 'UNAUTHENTICATED'}
|
|
97
|
+
# Exception in _run_task: Request had invalid authentication credentials.
|
|
98
|
+
# Expected OAuth 2 access token, login cookie or other valid authentication
|
|
99
|
+
# credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.
|
|
100
|
+
# when that error happens, I need to re-set the credentials
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
from eeclient.models import SepalHeaders, GoogleTokens
|
|
2
|
+
from eeclient.exceptions import (
|
|
3
|
+
CredentialsFileNotFoundError,
|
|
4
|
+
CredentialsFileUnknownError,
|
|
5
|
+
SepalCredentialsUnavailableError,
|
|
6
|
+
)
|
|
2
7
|
import os
|
|
3
8
|
import logging
|
|
4
9
|
import json
|
|
@@ -84,10 +89,14 @@ class SepalCredentialMixin:
|
|
|
84
89
|
def _load_credentials_from_file(self):
|
|
85
90
|
"""Load credentials from file and update internal state"""
|
|
86
91
|
if not self.credentials_path.exists():
|
|
87
|
-
raise
|
|
92
|
+
raise CredentialsFileNotFoundError(str(self.credentials_path))
|
|
88
93
|
|
|
89
94
|
try:
|
|
90
|
-
|
|
95
|
+
file_content = self.credentials_path.read_text().strip()
|
|
96
|
+
if not file_content:
|
|
97
|
+
raise CredentialsFileNotFoundError(str(self.credentials_path))
|
|
98
|
+
|
|
99
|
+
credentials_data = json.loads(file_content)
|
|
91
100
|
self._credentials = GoogleTokens.model_validate(credentials_data)
|
|
92
101
|
self.access_token = self._credentials.access_token
|
|
93
102
|
self.project_id = self._credentials.project_id
|
|
@@ -166,6 +175,12 @@ class SepalCredentialMixin:
|
|
|
166
175
|
f"Project: {self.project_id}"
|
|
167
176
|
)
|
|
168
177
|
return
|
|
178
|
+
elif response.status_code == 500:
|
|
179
|
+
self.logger.error(
|
|
180
|
+
"SEPAL API returned 500 error - "
|
|
181
|
+
"credentials not available on server"
|
|
182
|
+
)
|
|
183
|
+
raise SepalCredentialsUnavailableError(500)
|
|
169
184
|
else:
|
|
170
185
|
self.logger.debug(
|
|
171
186
|
f"Attempt {attempt}/{self.max_retries} failed with "
|
|
@@ -191,6 +206,8 @@ class SepalCredentialMixin:
|
|
|
191
206
|
)
|
|
192
207
|
attempt = 0
|
|
193
208
|
|
|
209
|
+
log.debug(f"Credentials path: {self.credentials_path}")
|
|
210
|
+
|
|
194
211
|
while attempt < self.max_retries:
|
|
195
212
|
attempt += 1
|
|
196
213
|
try:
|
|
@@ -214,11 +231,17 @@ class SepalCredentialMixin:
|
|
|
214
231
|
f"Credentials from file are still expired."
|
|
215
232
|
)
|
|
216
233
|
|
|
234
|
+
except CredentialsFileNotFoundError:
|
|
235
|
+
self.logger.error(
|
|
236
|
+
f"Credentials file not found: {self.credentials_path}"
|
|
237
|
+
)
|
|
238
|
+
raise
|
|
217
239
|
except Exception as e:
|
|
218
240
|
self.logger.error(
|
|
219
241
|
f"Attempt {attempt}/{self.max_retries} "
|
|
220
242
|
f"encountered an exception while reading file: {e}"
|
|
221
243
|
)
|
|
244
|
+
raise
|
|
222
245
|
|
|
223
246
|
# Wait before retrying (the file might be updated externally)
|
|
224
247
|
if attempt < self.max_retries:
|
|
@@ -226,11 +249,7 @@ class SepalCredentialMixin:
|
|
|
226
249
|
self.logger.debug(f"Waiting {wait_time} seconds before retry...")
|
|
227
250
|
await asyncio.sleep(wait_time)
|
|
228
251
|
|
|
229
|
-
raise
|
|
230
|
-
f"Failed to retrieve valid credentials from file after "
|
|
231
|
-
f"{self.max_retries} attempts. File may not be automatically "
|
|
232
|
-
f"updated or credentials are permanently expired."
|
|
233
|
-
)
|
|
252
|
+
raise CredentialsFileUnknownError()
|
|
234
253
|
|
|
235
254
|
def set_credentials_sync(self) -> None:
|
|
236
255
|
"""
|
|
@@ -282,6 +301,13 @@ class SepalCredentialMixin:
|
|
|
282
301
|
f"Project: {self.project_id}"
|
|
283
302
|
)
|
|
284
303
|
return
|
|
304
|
+
elif response.status_code == 500:
|
|
305
|
+
self.logger.error(
|
|
306
|
+
"SEPAL API returned 500 error - "
|
|
307
|
+
"credentials not available on server"
|
|
308
|
+
)
|
|
309
|
+
session.close()
|
|
310
|
+
raise SepalCredentialsUnavailableError(500)
|
|
285
311
|
else:
|
|
286
312
|
self.logger.debug(
|
|
287
313
|
f"Attempt {attempt}/{self.max_retries} failed with "
|
|
@@ -336,6 +362,9 @@ class SepalCredentialMixin:
|
|
|
336
362
|
f"Credentials from file are still expired."
|
|
337
363
|
)
|
|
338
364
|
|
|
365
|
+
except CredentialsFileNotFoundError:
|
|
366
|
+
# Re-raise immediately - no point in retrying if file doesn't exist
|
|
367
|
+
raise
|
|
339
368
|
except Exception as e:
|
|
340
369
|
self.logger.error(
|
|
341
370
|
f"Attempt {attempt}/{self.max_retries} "
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ee-client"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.1"
|
|
8
8
|
description = "extends the capabilities of the earthengine-api by providing custom session management and client interactions"
|
|
9
9
|
readme = { file = "README.rst", content-type = "text/x-rst" }
|
|
10
10
|
authors = [
|
|
@@ -66,7 +66,7 @@ branch = true
|
|
|
66
66
|
[tool.commitizen]
|
|
67
67
|
tag_format = "v$major.$minor.$patch$prerelease"
|
|
68
68
|
update_changelog_on_bump = false
|
|
69
|
-
version = "2.2.
|
|
69
|
+
version = "2.2.1"
|
|
70
70
|
version_files = [
|
|
71
71
|
"pyproject.toml:version",
|
|
72
72
|
"eeclient/__init__.py:__version__",
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from ee.ee_exception import EEException
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class EERestException(EEException):
|
|
5
|
-
def __init__(self, error):
|
|
6
|
-
self.message = error.get("message", "EE responded with an error")
|
|
7
|
-
super().__init__(self.message)
|
|
8
|
-
self.code = error.get("code", -1)
|
|
9
|
-
self.status = error.get("status", "UNDEFINED")
|
|
10
|
-
self.details = error.get("details")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class EEClientError(Exception):
|
|
14
|
-
"""Custom exception class for EEClient errors."""
|
|
15
|
-
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# {'code': 401, 'message': 'Request had invalid authentication credentials.
|
|
20
|
-
# Expected OAuth 2 access token, login cookie or other valid authentication
|
|
21
|
-
# credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
|
|
22
|
-
# 'status': 'UNAUTHENTICATED'}
|
|
23
|
-
# Exception in _run_task: Request had invalid authentication credentials.
|
|
24
|
-
# Expected OAuth 2 access token, login cookie or other valid authentication
|
|
25
|
-
# credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.
|
|
26
|
-
# when that error happens, I need to re-set the credentials
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|