terrakio-core 0.4.98__py3-none-any.whl → 0.4.98.1b3__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 terrakio-core might be problematic. Click here for more details.
- terrakio_core/async_client.py +26 -169
- terrakio_core/config.py +3 -44
- terrakio_core/endpoints/auth.py +96 -47
- terrakio_core/endpoints/dataset_management.py +120 -54
- terrakio_core/endpoints/group_management.py +269 -76
- terrakio_core/endpoints/mass_stats.py +704 -581
- terrakio_core/endpoints/model_management.py +213 -109
- terrakio_core/endpoints/user_management.py +106 -21
- terrakio_core/exceptions.py +371 -1
- terrakio_core/sync_client.py +9 -124
- {terrakio_core-0.4.98.dist-info → terrakio_core-0.4.98.1b3.dist-info}/METADATA +2 -1
- terrakio_core-0.4.98.1b3.dist-info/RECORD +23 -0
- terrakio_core-0.4.98.dist-info/RECORD +0 -23
- {terrakio_core-0.4.98.dist-info → terrakio_core-0.4.98.1b3.dist-info}/WHEEL +0 -0
terrakio_core/async_client.py
CHANGED
|
@@ -9,7 +9,7 @@ from geopandas import GeoDataFrame
|
|
|
9
9
|
from shapely.geometry.base import BaseGeometry as ShapelyGeometry
|
|
10
10
|
from shapely.geometry import mapping
|
|
11
11
|
from .client import BaseClient
|
|
12
|
-
from .exceptions import APIError
|
|
12
|
+
from .exceptions import APIError, NetworkError
|
|
13
13
|
from .endpoints.dataset_management import DatasetManagement
|
|
14
14
|
from .endpoints.user_management import UserManagement
|
|
15
15
|
from .endpoints.mass_stats import MassStats
|
|
@@ -36,13 +36,10 @@ class AsyncClient(BaseClient):
|
|
|
36
36
|
|
|
37
37
|
async def _terrakio_request(self, method: str, endpoint: str, **kwargs):
|
|
38
38
|
if self.session is None:
|
|
39
|
-
# To this:
|
|
40
39
|
headers = {
|
|
41
40
|
'x-api-key': self.key,
|
|
42
41
|
'Authorization': self.token
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
# Only add Content-Type if it's a JSON request
|
|
46
43
|
if 'json' in kwargs:
|
|
47
44
|
headers['Content-Type'] = 'application/json'
|
|
48
45
|
clean_headers = {k: v for k, v in headers.items() if v is not None}
|
|
@@ -57,20 +54,17 @@ class AsyncClient(BaseClient):
|
|
|
57
54
|
for attempt in range(self.retry + 1):
|
|
58
55
|
try:
|
|
59
56
|
async with session.request(method, url, **kwargs) as response:
|
|
57
|
+
content = await response.text()
|
|
58
|
+
|
|
60
59
|
if not response.ok and self._should_retry(response.status, attempt):
|
|
61
60
|
self.logger.info(f"Request failed (attempt {attempt+1}/{self.retry+1}): {response.status}. Retrying...")
|
|
62
61
|
continue
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
except:
|
|
70
|
-
pass
|
|
71
|
-
raise APIError(error_msg, status_code=response.status)
|
|
72
|
-
return await self._parse_response(response)
|
|
73
|
-
|
|
62
|
+
if response.ok:
|
|
63
|
+
data = await self._parse_response(response)
|
|
64
|
+
return data, response.status
|
|
65
|
+
else:
|
|
66
|
+
error_data = await response.json()
|
|
67
|
+
return error_data, response.status
|
|
74
68
|
except aiohttp.ClientError as e:
|
|
75
69
|
last_exception = e
|
|
76
70
|
if attempt < self.retry:
|
|
@@ -78,8 +72,8 @@ class AsyncClient(BaseClient):
|
|
|
78
72
|
continue
|
|
79
73
|
else:
|
|
80
74
|
break
|
|
81
|
-
|
|
82
|
-
raise
|
|
75
|
+
|
|
76
|
+
raise NetworkError(f"Network failure after {self.retry+1} attempts: {last_exception}")
|
|
83
77
|
|
|
84
78
|
def _should_retry(self, status_code: int, attempt: int) -> bool:
|
|
85
79
|
"""Determine if the request should be retried based on status code."""
|
|
@@ -193,158 +187,21 @@ class AsyncClient(BaseClient):
|
|
|
193
187
|
|
|
194
188
|
return result
|
|
195
189
|
|
|
196
|
-
async def zonal_stats(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
gdf (GeoDataFrame): GeoDataFrame containing geometries
|
|
213
|
-
expr (str): Terrakio expression to evaluate, can include spatial aggregations
|
|
214
|
-
conc (int): Number of concurrent requests to make
|
|
215
|
-
in_crs (str): Input coordinate reference system
|
|
216
|
-
out_crs (str): Output coordinate reference system
|
|
217
|
-
resolution (int): Resolution parameter
|
|
218
|
-
geom_fix (bool): Whether to fix the geometry (default False)
|
|
219
|
-
mass_stats (bool): Whether to use mass stats for processing (default False)
|
|
220
|
-
id_column (Optional[str]): Name of the ID column to use (default None)
|
|
221
|
-
|
|
222
|
-
Returns:
|
|
223
|
-
geopandas.GeoDataFrame: GeoDataFrame with added columns for results
|
|
224
|
-
|
|
225
|
-
Raises:
|
|
226
|
-
ValueError: If concurrency is too high or if data exceeds memory limit without streaming
|
|
227
|
-
APIError: If the API request fails
|
|
228
|
-
"""
|
|
229
|
-
# the sync client didn't pass the self here, so the client is now async
|
|
230
|
-
return await _zonal_stats(
|
|
231
|
-
client=self,
|
|
232
|
-
gdf=gdf,
|
|
233
|
-
expr=expr,
|
|
234
|
-
conc=conc,
|
|
235
|
-
in_crs=in_crs,
|
|
236
|
-
out_crs=out_crs,
|
|
237
|
-
resolution=resolution,
|
|
238
|
-
geom_fix=geom_fix,
|
|
239
|
-
mass_stats=mass_stats,
|
|
240
|
-
id_column=id_column,
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
async def create_dataset_file(
|
|
244
|
-
self,
|
|
245
|
-
name: str,
|
|
246
|
-
aoi: str,
|
|
247
|
-
expression: str,
|
|
248
|
-
output: str,
|
|
249
|
-
in_crs: str = "epsg:4326",
|
|
250
|
-
res: float = 0.0001,
|
|
251
|
-
region: str = "aus",
|
|
252
|
-
to_crs: str = "epsg:4326",
|
|
253
|
-
overwrite: bool = True,
|
|
254
|
-
skip_existing: bool = False,
|
|
255
|
-
non_interactive: bool = True,
|
|
256
|
-
poll_interval: int = 30,
|
|
257
|
-
download_path: str = "/home/user/Downloads",
|
|
258
|
-
mask = True,
|
|
259
|
-
max_file_size_mb: int = 5120, # Default to 5GB
|
|
260
|
-
tile_size: int = 1024,
|
|
261
|
-
) -> dict:
|
|
262
|
-
"""
|
|
263
|
-
Create a dataset file using mass stats operations.
|
|
264
|
-
|
|
265
|
-
Args:
|
|
266
|
-
aoi (str): Area of interest
|
|
267
|
-
expression (str): Terrakio expression to evaluate
|
|
268
|
-
output (str): Output format
|
|
269
|
-
in_crs (str): Input coordinate reference system (default "epsg:4326")
|
|
270
|
-
res (float): Resolution (default 0.0001)
|
|
271
|
-
region (str): Region (default "aus")
|
|
272
|
-
to_crs (str): Target coordinate reference system (default "epsg:4326")
|
|
273
|
-
overwrite (bool): Whether to overwrite existing files (default True)
|
|
274
|
-
skip_existing (bool): Whether to skip existing files (default False)
|
|
275
|
-
non_interactive (bool): Whether to run non-interactively (default True)
|
|
276
|
-
poll_interval (int): Polling interval in seconds (default 30)
|
|
277
|
-
download_path (str): Download path (default "/home/user/Downloads")
|
|
278
|
-
|
|
279
|
-
Returns:
|
|
280
|
-
dict: Dictionary containing generation_task_id and combine_task_id
|
|
281
|
-
|
|
282
|
-
Raises:
|
|
283
|
-
ConfigurationError: If mass stats client is not properly configured
|
|
284
|
-
RuntimeError: If job fails
|
|
285
|
-
"""
|
|
286
|
-
return await _create_dataset_file(
|
|
287
|
-
client=self,
|
|
288
|
-
aoi=aoi,
|
|
289
|
-
expression=expression,
|
|
290
|
-
output=output,
|
|
291
|
-
in_crs=in_crs,
|
|
292
|
-
res=res,
|
|
293
|
-
region=region,
|
|
294
|
-
to_crs=to_crs,
|
|
295
|
-
overwrite=overwrite,
|
|
296
|
-
skip_existing=skip_existing,
|
|
297
|
-
non_interactive=non_interactive,
|
|
298
|
-
poll_interval=poll_interval,
|
|
299
|
-
download_path=download_path,
|
|
300
|
-
name=name,
|
|
301
|
-
mask=mask,
|
|
302
|
-
max_file_size_mb=max_file_size_mb,
|
|
303
|
-
tile_size=tile_size
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
async def geo_queries(
|
|
307
|
-
self,
|
|
308
|
-
queries: list[dict],
|
|
309
|
-
conc: int = 20,
|
|
310
|
-
):
|
|
311
|
-
"""
|
|
312
|
-
Execute multiple geo queries concurrently.
|
|
313
|
-
|
|
314
|
-
Args:
|
|
315
|
-
queries (list[dict]): List of dictionaries containing query parameters.
|
|
316
|
-
Each query must have 'expr', 'feature', and 'in_crs' keys.
|
|
317
|
-
conc (int): Number of concurrent requests to make (default 20, max 100)
|
|
318
|
-
|
|
319
|
-
Returns:
|
|
320
|
-
Union[float, geopandas.GeoDataFrame]:
|
|
321
|
-
- float: Average of all results if results are integers
|
|
322
|
-
- GeoDataFrame: GeoDataFrame with geometry and dataset columns if results are xarray datasets
|
|
323
|
-
|
|
324
|
-
Raises:
|
|
325
|
-
ValueError: If queries list is empty, concurrency is too high, or queries are malformed
|
|
326
|
-
APIError: If the API request fails
|
|
327
|
-
|
|
328
|
-
Example:
|
|
329
|
-
queries = [
|
|
330
|
-
{
|
|
331
|
-
'expr': 'WCF.wcf',
|
|
332
|
-
'feature': {'type': 'Feature', 'geometry': {...}, 'properties': {}},
|
|
333
|
-
'in_crs': 'epsg:4326'
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
'expr': 'NDVI.ndvi',
|
|
337
|
-
'feature': {'type': 'Feature', 'geometry': {...}, 'properties': {}},
|
|
338
|
-
'in_crs': 'epsg:4326'
|
|
339
|
-
}
|
|
340
|
-
]
|
|
341
|
-
result = await client.geo_queries(queries)
|
|
342
|
-
"""
|
|
343
|
-
return await _request_geoquery_list(
|
|
344
|
-
client=self,
|
|
345
|
-
quries=queries, # Note: keeping original parameter name for compatibility
|
|
346
|
-
conc=conc,
|
|
347
|
-
)
|
|
190
|
+
async def zonal_stats(self, *args, **kwargs):
|
|
191
|
+
"""Proxy to convenience zonal_stats with full argument passthrough."""
|
|
192
|
+
return await _zonal_stats(self, *args, **kwargs)
|
|
193
|
+
|
|
194
|
+
async def create_dataset_file(self, *args, **kwargs) -> dict:
|
|
195
|
+
"""Proxy to convenience create_dataset_file with full argument passthrough."""
|
|
196
|
+
kwargs.setdefault('download_path', "/home/user/Downloads")
|
|
197
|
+
kwargs.setdefault('region', "aus")
|
|
198
|
+
return await _create_dataset_file(self, *args, **kwargs)
|
|
199
|
+
|
|
200
|
+
async def geo_queries(self, *args, **kwargs):
|
|
201
|
+
"""Proxy to convenience request_geoquery_list with full argument passthrough."""
|
|
202
|
+
if 'queries' in kwargs:
|
|
203
|
+
kwargs['quries'] = kwargs.pop('queries')
|
|
204
|
+
return await _request_geoquery_list(self, *args, **kwargs)
|
|
348
205
|
|
|
349
206
|
async def __aenter__(self):
|
|
350
207
|
if self._session is None:
|
terrakio_core/config.py
CHANGED
|
@@ -7,7 +7,7 @@ from .exceptions import ConfigurationError
|
|
|
7
7
|
|
|
8
8
|
# Default configuration file locations
|
|
9
9
|
DEFAULT_CONFIG_FILE = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
|
|
10
|
-
DEFAULT_API_URL = "https://
|
|
10
|
+
DEFAULT_API_URL = "https://dev-au.terrak.io"
|
|
11
11
|
|
|
12
12
|
def read_config_file(config_file: str = DEFAULT_CONFIG_FILE, logger: logging.Logger = None) -> Dict[str, Any]:
|
|
13
13
|
"""
|
|
@@ -27,14 +27,8 @@ def read_config_file(config_file: str = DEFAULT_CONFIG_FILE, logger: logging.Log
|
|
|
27
27
|
file if one doesn't exist and returns appropriate status flags.
|
|
28
28
|
"""
|
|
29
29
|
config_path = Path(os.path.expanduser(config_file))
|
|
30
|
-
|
|
31
|
-
# that we need to login before using any of the functions
|
|
32
|
-
# Check if config file exists
|
|
30
|
+
|
|
33
31
|
if not config_path.exists():
|
|
34
|
-
# Create an empty config file
|
|
35
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
36
|
-
with open(config_path, 'w') as f:
|
|
37
|
-
json.dump({}, f)
|
|
38
32
|
logger.info("No API key found. Please provide an API key to use this client.")
|
|
39
33
|
return {
|
|
40
34
|
'url': DEFAULT_API_URL,
|
|
@@ -45,12 +39,9 @@ def read_config_file(config_file: str = DEFAULT_CONFIG_FILE, logger: logging.Log
|
|
|
45
39
|
}
|
|
46
40
|
|
|
47
41
|
try:
|
|
48
|
-
# Read the config file
|
|
49
42
|
with open(config_path, 'r') as f:
|
|
50
43
|
config_data = json.load(f)
|
|
51
44
|
|
|
52
|
-
# Read the config file data
|
|
53
|
-
# Check if config has an API key
|
|
54
45
|
if not config_data or 'TERRAKIO_API_KEY' not in config_data or not config_data.get('TERRAKIO_API_KEY'):
|
|
55
46
|
logger.info("No API key found. Please provide an API key to use this client.")
|
|
56
47
|
return {
|
|
@@ -61,11 +52,8 @@ def read_config_file(config_file: str = DEFAULT_CONFIG_FILE, logger: logging.Log
|
|
|
61
52
|
'token': config_data.get('PERSONAL_TOKEN')
|
|
62
53
|
}
|
|
63
54
|
logger.info(f"Currently logged in as: {config_data.get('EMAIL')}")
|
|
64
|
-
# this meanb that we have already logged in to the tkio account
|
|
65
55
|
|
|
66
|
-
# Convert the JSON config to our expected format
|
|
67
56
|
config = {
|
|
68
|
-
# Always use the default URL, not from config file
|
|
69
57
|
'url': DEFAULT_API_URL,
|
|
70
58
|
'key': config_data.get('TERRAKIO_API_KEY'),
|
|
71
59
|
'is_logged_in': True,
|
|
@@ -84,33 +72,4 @@ def read_config_file(config_file: str = DEFAULT_CONFIG_FILE, logger: logging.Log
|
|
|
84
72
|
'is_logged_in': False,
|
|
85
73
|
'user_email': None,
|
|
86
74
|
'token': None
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
def create_default_config(email: str, api_key: str, config_file: str = DEFAULT_CONFIG_FILE) -> None:
|
|
90
|
-
"""
|
|
91
|
-
Create a default configuration file in JSON format.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
email: User email
|
|
95
|
-
api_key: Terrakio API key
|
|
96
|
-
config_file: Path to configuration file
|
|
97
|
-
|
|
98
|
-
Raises:
|
|
99
|
-
ConfigurationError: If the configuration file can't be created
|
|
100
|
-
"""
|
|
101
|
-
config_path = Path(os.path.expanduser(config_file))
|
|
102
|
-
|
|
103
|
-
# Ensure directory exists
|
|
104
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
-
|
|
106
|
-
try:
|
|
107
|
-
config_data = {
|
|
108
|
-
"EMAIL": email,
|
|
109
|
-
"TERRAKIO_API_KEY": api_key
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
with open(config_path, 'w') as f:
|
|
113
|
-
json.dump(config_data, f, indent=2)
|
|
114
|
-
|
|
115
|
-
except Exception as e:
|
|
116
|
-
raise ConfigurationError(f"Failed to create configuration file: {e}")
|
|
75
|
+
}
|
terrakio_core/endpoints/auth.py
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import json
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from ..exceptions import (
|
|
6
|
+
APIKeyError,
|
|
7
|
+
AuthenticationExpireError,
|
|
8
|
+
InvalidUsernamePasswordError,
|
|
9
|
+
LoginError,
|
|
10
|
+
QuotaError,
|
|
11
|
+
RefreshAPIKeyError,
|
|
12
|
+
ResetPasswordError,
|
|
13
|
+
SignupError,
|
|
14
|
+
UserInfoError,
|
|
15
|
+
InvalidEmailFormatError,
|
|
16
|
+
EmailAlreadyExistsError,
|
|
17
|
+
)
|
|
18
|
+
from ..helper.decorators import require_api_key, require_auth, require_token
|
|
6
19
|
|
|
7
20
|
class AuthClient:
|
|
8
21
|
def __init__(self, client):
|
|
@@ -20,22 +33,23 @@ class AuthClient:
|
|
|
20
33
|
Dict containing the authentication token
|
|
21
34
|
|
|
22
35
|
Raises:
|
|
23
|
-
|
|
36
|
+
SignupError: If the signup request fails
|
|
24
37
|
"""
|
|
25
38
|
payload = {
|
|
26
39
|
"email": email,
|
|
27
40
|
"password": password
|
|
28
41
|
}
|
|
42
|
+
response, status = await self._client._terrakio_request("POST", "/users/signup", json=payload)
|
|
43
|
+
if status != 200:
|
|
44
|
+
if status == 422:
|
|
45
|
+
raise InvalidEmailFormatError(f"Invalid email format: {response}", status_code=status)
|
|
46
|
+
elif status == 409:
|
|
47
|
+
raise EmailAlreadyExistsError(f"Email already exists: {response}", status_code=status)
|
|
48
|
+
raise SignupError(f"Signup request failed: {response}", status_code=status)
|
|
49
|
+
else:
|
|
50
|
+
return response
|
|
29
51
|
|
|
30
|
-
|
|
31
|
-
try:
|
|
32
|
-
result = await self._client._terrakio_request("POST", "/users/signup", json=payload)
|
|
33
|
-
except Exception as e:
|
|
34
|
-
self._client.logger.info(f"Signup failed: {str(e)}")
|
|
35
|
-
raise APIError(f"Signup request failed: {str(e)}")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
async def login(self, email: str, password: str) -> Dict[str, str]:
|
|
52
|
+
async def login(self, email: str, password: str) -> None:
|
|
39
53
|
"""
|
|
40
54
|
Login a user with email and password.
|
|
41
55
|
|
|
@@ -44,7 +58,7 @@ class AuthClient:
|
|
|
44
58
|
password: User's password
|
|
45
59
|
|
|
46
60
|
Returns:
|
|
47
|
-
|
|
61
|
+
None
|
|
48
62
|
|
|
49
63
|
Raises:
|
|
50
64
|
APIError: If the login request fails
|
|
@@ -53,10 +67,14 @@ class AuthClient:
|
|
|
53
67
|
"email": email,
|
|
54
68
|
"password": password
|
|
55
69
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
response, status = await self._client._terrakio_request("POST", "/users/login", json=payload)
|
|
71
|
+
if status != 200:
|
|
72
|
+
if status == 401:
|
|
73
|
+
raise InvalidUsernamePasswordError(f"Invalid username or password: {response}", status_code=status)
|
|
74
|
+
else:
|
|
75
|
+
raise LoginError(f"Login request failed: {response}", status_code=status)
|
|
76
|
+
else:
|
|
77
|
+
token_response = response.get("token")
|
|
60
78
|
|
|
61
79
|
if token_response:
|
|
62
80
|
self._client.token = token_response
|
|
@@ -72,11 +90,6 @@ class AuthClient:
|
|
|
72
90
|
self._client.logger.info(f"Successfully authenticated as: {email}")
|
|
73
91
|
self._client.logger.info(f"Using Terrakio API at: {self._client.url}")
|
|
74
92
|
|
|
75
|
-
return {"token": token_response} if token_response else {"error": "Login failed"}
|
|
76
|
-
|
|
77
|
-
except Exception as e:
|
|
78
|
-
self._client.logger.info(f"Login failed: {str(e)}")
|
|
79
|
-
raise APIError(f"Login request failed: {str(e)}")
|
|
80
93
|
|
|
81
94
|
@require_token
|
|
82
95
|
async def view_api_key(self) -> str:
|
|
@@ -87,11 +100,18 @@ class AuthClient:
|
|
|
87
100
|
str: The API key
|
|
88
101
|
|
|
89
102
|
Raises:
|
|
90
|
-
|
|
103
|
+
AuthenticationExpireError: If authentication expired
|
|
104
|
+
APIKeyError: If the API key request fails
|
|
91
105
|
"""
|
|
92
|
-
|
|
93
|
-
api_key =
|
|
94
|
-
|
|
106
|
+
response, status = await self._client._terrakio_request("GET", "/users/key")
|
|
107
|
+
api_key = response.get("apiKey")
|
|
108
|
+
if status != 200:
|
|
109
|
+
if status == 400 and response.get("detail")["message"] == "Not authenticated":
|
|
110
|
+
raise AuthenticationExpireError(f"Authentication expired, please login again: {response}")
|
|
111
|
+
else:
|
|
112
|
+
raise APIKeyError(f"Error fetching API key: {response}", status_code=status)
|
|
113
|
+
else:
|
|
114
|
+
return api_key
|
|
95
115
|
|
|
96
116
|
@require_api_key
|
|
97
117
|
@require_token
|
|
@@ -103,17 +123,18 @@ class AuthClient:
|
|
|
103
123
|
str: The new API key
|
|
104
124
|
|
|
105
125
|
Raises:
|
|
106
|
-
|
|
126
|
+
RefreshAPIKeyError: If the API key refresh request fails
|
|
107
127
|
"""
|
|
108
|
-
|
|
109
|
-
self._client.key =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
response, status = await self._client._terrakio_request("POST", "/users/refresh_key")
|
|
129
|
+
self._client.key = response.get("apiKey")
|
|
130
|
+
if status != 200:
|
|
131
|
+
raise RefreshAPIKeyError(f"Error refreshing API key: {response}", status_code=status)
|
|
132
|
+
else:
|
|
133
|
+
self._update_config_key()
|
|
134
|
+
return self._client.key
|
|
114
135
|
|
|
115
136
|
@require_api_key
|
|
116
|
-
def get_user_info(self) -> Dict[str, Any]:
|
|
137
|
+
async def get_user_info(self) -> Dict[str, Any]:
|
|
117
138
|
"""
|
|
118
139
|
Get information about the authenticated user.
|
|
119
140
|
|
|
@@ -121,9 +142,45 @@ class AuthClient:
|
|
|
121
142
|
Dict[str, Any]: User information
|
|
122
143
|
|
|
123
144
|
Raises:
|
|
124
|
-
|
|
145
|
+
AuthenticationExpireError: If authentication expired
|
|
146
|
+
UserInfoError: If the user info request fails
|
|
125
147
|
"""
|
|
126
|
-
|
|
148
|
+
response, status = await self._client._terrakio_request("GET", "/users/info")
|
|
149
|
+
if status != 200:
|
|
150
|
+
if status == 400 and response.get("detail")["message"] == "Not authenticated":
|
|
151
|
+
raise AuthenticationExpireError(f"Authentication expired, please login again: {response}", status_code=status)
|
|
152
|
+
else:
|
|
153
|
+
raise UserInfoError(f"Error fetching user info: {response}", status_code=status)
|
|
154
|
+
else:
|
|
155
|
+
return response
|
|
156
|
+
|
|
157
|
+
@require_api_key
|
|
158
|
+
async def reset_password(self, email : str) -> Dict[str, Any]:
|
|
159
|
+
"""
|
|
160
|
+
Reset the password for a user by email.
|
|
161
|
+
"""
|
|
162
|
+
response, status = await self._client._terrakio_request("GET", f"/users/reset-password?email={email}")
|
|
163
|
+
if status != 200:
|
|
164
|
+
raise ResetPasswordError(f"Error resetting password: {response}", status_code=status)
|
|
165
|
+
else:
|
|
166
|
+
return response['message']
|
|
167
|
+
|
|
168
|
+
@require_api_key
|
|
169
|
+
async def get_user_quota(self):
|
|
170
|
+
"""
|
|
171
|
+
Get the user's quota.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Dict: User's quota
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
QuotaError: If the quota request fails
|
|
178
|
+
"""
|
|
179
|
+
response, status = await self._client._terrakio_request("GET", "/users/quota")
|
|
180
|
+
if status != 200:
|
|
181
|
+
raise QuotaError(f"Error fetching quota: {response}", status_code = status)
|
|
182
|
+
else:
|
|
183
|
+
return response
|
|
127
184
|
|
|
128
185
|
def _save_config(self, email: str, token: str):
|
|
129
186
|
"""
|
|
@@ -145,7 +202,6 @@ class AuthClient:
|
|
|
145
202
|
config["TERRAKIO_API_KEY"] = self._client.key
|
|
146
203
|
config["PERSONAL_TOKEN"] = token
|
|
147
204
|
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
148
|
-
|
|
149
205
|
with open(config_path, 'w') as f:
|
|
150
206
|
json.dump(config, f, indent=4)
|
|
151
207
|
|
|
@@ -177,11 +233,4 @@ class AuthClient:
|
|
|
177
233
|
self._client.logger.info(f"API key updated in {config_path}")
|
|
178
234
|
|
|
179
235
|
except Exception as e:
|
|
180
|
-
self._client.logger.info(f"Warning: Failed to update config file: {e}")
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# we have four different circumstances:
|
|
184
|
-
# same expression, different region
|
|
185
|
-
# same expression, same region
|
|
186
|
-
# different expression, same region
|
|
187
|
-
# different expression, different region
|
|
236
|
+
self._client.logger.info(f"Warning: Failed to update config file: {e}")
|