terrakio-core 0.2.3__py3-none-any.whl → 0.2.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 terrakio-core might be problematic. Click here for more details.
- terrakio_core/__init__.py +0 -0
- terrakio_core/auth.py +238 -0
- terrakio_core/client.py +1005 -0
- terrakio_core/config.py +81 -0
- terrakio_core/dataset_management.py +235 -0
- terrakio_core/exceptions.py +18 -0
- terrakio_core/group_access_management.py +232 -0
- terrakio_core/mass_stats.py +262 -0
- terrakio_core/space_management.py +101 -0
- terrakio_core/user_management.py +227 -0
- {terrakio_core-0.2.3.dist-info → terrakio_core-0.2.4.dist-info}/METADATA +1 -1
- terrakio_core-0.2.4.dist-info/RECORD +14 -0
- terrakio_core-0.2.4.dist-info/top_level.txt +1 -0
- terrakio_core-0.2.3.dist-info/RECORD +0 -4
- terrakio_core-0.2.3.dist-info/top_level.txt +0 -1
- {terrakio_core-0.2.3.dist-info → terrakio_core-0.2.4.dist-info}/WHEEL +0 -0
terrakio_core/client.py
ADDED
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Dict, Any, Optional, Union
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
import aiohttp
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import geopandas as gpd
|
|
10
|
+
import xarray as xr
|
|
11
|
+
from shapely.geometry import shape, mapping
|
|
12
|
+
from shapely.geometry.base import BaseGeometry as ShapelyGeometry
|
|
13
|
+
|
|
14
|
+
from .exceptions import APIError, ConfigurationError
|
|
15
|
+
|
|
16
|
+
class BaseClient:
|
|
17
|
+
def __init__(self, url: Optional[str] = None, key: Optional[str] = None,
|
|
18
|
+
auth_url: Optional[str] = "https://dev-au.terrak.io",
|
|
19
|
+
quiet: bool = False, config_file: Optional[str] = None,
|
|
20
|
+
verify: bool = True, timeout: int = 60):
|
|
21
|
+
self.quiet = quiet
|
|
22
|
+
self.verify = verify
|
|
23
|
+
self.timeout = timeout
|
|
24
|
+
self.auth_client = None
|
|
25
|
+
if auth_url:
|
|
26
|
+
from terrakio_core.auth import AuthClient
|
|
27
|
+
self.auth_client = AuthClient(
|
|
28
|
+
base_url=auth_url,
|
|
29
|
+
verify=verify,
|
|
30
|
+
timeout=timeout
|
|
31
|
+
)
|
|
32
|
+
self.url = url
|
|
33
|
+
self.key = key
|
|
34
|
+
if self.url is None or self.key is None:
|
|
35
|
+
from terrakio_core.config import read_config_file, DEFAULT_CONFIG_FILE
|
|
36
|
+
if config_file is None:
|
|
37
|
+
config_file = DEFAULT_CONFIG_FILE
|
|
38
|
+
try:
|
|
39
|
+
config = read_config_file(config_file)
|
|
40
|
+
if self.url is None:
|
|
41
|
+
self.url = config.get('url')
|
|
42
|
+
if self.key is None:
|
|
43
|
+
self.key = config.get('key')
|
|
44
|
+
except Exception as e:
|
|
45
|
+
raise ConfigurationError(
|
|
46
|
+
f"Failed to read configuration: {e}\n\n"
|
|
47
|
+
"To fix this issue:\n"
|
|
48
|
+
"1. Create a file at ~/.terrakioapirc with:\n"
|
|
49
|
+
"url: https://api.terrak.io\n"
|
|
50
|
+
"key: your-api-key\n\n"
|
|
51
|
+
"OR\n\n"
|
|
52
|
+
"2. Initialize the client with explicit parameters:\n"
|
|
53
|
+
"client = terrakio_api.Client(\n"
|
|
54
|
+
" url='https://api.terrak.io',\n"
|
|
55
|
+
" key='your-api-key'\n"
|
|
56
|
+
")"
|
|
57
|
+
)
|
|
58
|
+
if not self.url:
|
|
59
|
+
raise ConfigurationError("Missing API URL in configuration")
|
|
60
|
+
if not self.key:
|
|
61
|
+
raise ConfigurationError("Missing API key in configuration")
|
|
62
|
+
self.url = self.url.rstrip('/')
|
|
63
|
+
if not self.quiet:
|
|
64
|
+
print(f"Using Terrakio API at: {self.url}")
|
|
65
|
+
self.session = requests.Session()
|
|
66
|
+
self.session.headers.update({
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
'x-api-key': self.key
|
|
69
|
+
})
|
|
70
|
+
self.user_management = None
|
|
71
|
+
self.dataset_management = None
|
|
72
|
+
self.mass_stats = None
|
|
73
|
+
self._aiohttp_session = None
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
async def aiohttp_session(self):
|
|
77
|
+
if self._aiohttp_session is None or self._aiohttp_session.closed:
|
|
78
|
+
self._aiohttp_session = aiohttp.ClientSession(
|
|
79
|
+
headers={
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'x-api-key': self.key
|
|
82
|
+
},
|
|
83
|
+
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
|
84
|
+
)
|
|
85
|
+
return self._aiohttp_session
|
|
86
|
+
|
|
87
|
+
async def wcs_async(self, expr: str, feature: Union[Dict[str, Any], ShapelyGeometry],
|
|
88
|
+
in_crs: str = "epsg:4326", out_crs: str = "epsg:4326",
|
|
89
|
+
output: str = "csv", resolution: int = -1, **kwargs):
|
|
90
|
+
"""
|
|
91
|
+
Asynchronous version of the wcs() method using aiohttp.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
expr (str): The WCS expression to evaluate
|
|
95
|
+
feature (Union[Dict[str, Any], ShapelyGeometry]): The geographic feature
|
|
96
|
+
in_crs (str): Input coordinate reference system
|
|
97
|
+
out_crs (str): Output coordinate reference system
|
|
98
|
+
output (str): Output format ('csv' or 'netcdf')
|
|
99
|
+
resolution (int): Resolution parameter
|
|
100
|
+
**kwargs: Additional parameters to pass to the WCS request
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Union[pd.DataFrame, xr.Dataset, bytes]: The response data in the requested format
|
|
104
|
+
"""
|
|
105
|
+
if hasattr(feature, 'is_valid'):
|
|
106
|
+
from shapely.geometry import mapping
|
|
107
|
+
feature = {
|
|
108
|
+
"type": "Feature",
|
|
109
|
+
"geometry": mapping(feature),
|
|
110
|
+
"properties": {}
|
|
111
|
+
}
|
|
112
|
+
self.validate_feature(feature)
|
|
113
|
+
|
|
114
|
+
payload = {
|
|
115
|
+
"feature": feature,
|
|
116
|
+
"in_crs": in_crs,
|
|
117
|
+
"out_crs": out_crs,
|
|
118
|
+
"output": output,
|
|
119
|
+
"resolution": resolution,
|
|
120
|
+
"expr": expr,
|
|
121
|
+
**kwargs
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
request_url = f"{self.url}/geoquery"
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Get the shared aiohttp session
|
|
128
|
+
session = await self.aiohttp_session
|
|
129
|
+
async with session.post(request_url, json=payload, ssl=self.verify) as response:
|
|
130
|
+
if not response.ok:
|
|
131
|
+
error_msg = f"API request failed: {response.status} {response.reason}"
|
|
132
|
+
try:
|
|
133
|
+
error_data = await response.json()
|
|
134
|
+
if "detail" in error_data:
|
|
135
|
+
error_msg += f" - {error_data['detail']}"
|
|
136
|
+
except:
|
|
137
|
+
pass
|
|
138
|
+
raise APIError(error_msg)
|
|
139
|
+
|
|
140
|
+
content = await response.read()
|
|
141
|
+
|
|
142
|
+
if output.lower() == "csv":
|
|
143
|
+
import pandas as pd
|
|
144
|
+
df = pd.read_csv(BytesIO(content))
|
|
145
|
+
return df
|
|
146
|
+
elif output.lower() == "netcdf":
|
|
147
|
+
return xr.open_dataset(BytesIO(content))
|
|
148
|
+
else:
|
|
149
|
+
try:
|
|
150
|
+
return xr.open_dataset(BytesIO(content))
|
|
151
|
+
except ValueError:
|
|
152
|
+
import pandas as pd
|
|
153
|
+
try:
|
|
154
|
+
return pd.read_csv(BytesIO(content))
|
|
155
|
+
except:
|
|
156
|
+
return content
|
|
157
|
+
|
|
158
|
+
except aiohttp.ClientError as e:
|
|
159
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
raise
|
|
162
|
+
|
|
163
|
+
async def close_async(self):
|
|
164
|
+
"""Close the aiohttp session"""
|
|
165
|
+
if self._aiohttp_session and not self._aiohttp_session.closed:
|
|
166
|
+
await self._aiohttp_session.close()
|
|
167
|
+
self._aiohttp_session = None
|
|
168
|
+
|
|
169
|
+
async def __aenter__(self):
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
173
|
+
await self.close_async()
|
|
174
|
+
|
|
175
|
+
def validate_feature(self, feature: Dict[str, Any]) -> None:
|
|
176
|
+
if hasattr(feature, 'is_valid'):
|
|
177
|
+
from shapely.geometry import mapping
|
|
178
|
+
feature = {
|
|
179
|
+
"type": "Feature",
|
|
180
|
+
"geometry": mapping(feature),
|
|
181
|
+
"properties": {}
|
|
182
|
+
}
|
|
183
|
+
if not isinstance(feature, dict):
|
|
184
|
+
raise ValueError("Feature must be a dictionary or a Shapely geometry")
|
|
185
|
+
if feature.get("type") != "Feature":
|
|
186
|
+
raise ValueError("GeoJSON object must be of type 'Feature'")
|
|
187
|
+
if "geometry" not in feature:
|
|
188
|
+
raise ValueError("Feature must contain a 'geometry' field")
|
|
189
|
+
if "properties" not in feature:
|
|
190
|
+
raise ValueError("Feature must contain a 'properties' field")
|
|
191
|
+
try:
|
|
192
|
+
geometry = shape(feature["geometry"])
|
|
193
|
+
except Exception as e:
|
|
194
|
+
raise ValueError(f"Invalid geometry format: {str(e)}")
|
|
195
|
+
if not geometry.is_valid:
|
|
196
|
+
raise ValueError(f"Invalid geometry: {geometry.is_valid_reason}")
|
|
197
|
+
geom_type = feature["geometry"]["type"]
|
|
198
|
+
if geom_type == "Point":
|
|
199
|
+
if len(feature["geometry"]["coordinates"]) != 2:
|
|
200
|
+
raise ValueError("Point must have exactly 2 coordinates")
|
|
201
|
+
elif geom_type == "Polygon":
|
|
202
|
+
if not geometry.is_simple:
|
|
203
|
+
raise ValueError("Polygon must be simple (not self-intersecting)")
|
|
204
|
+
if geometry.area == 0:
|
|
205
|
+
raise ValueError("Polygon must have non-zero area")
|
|
206
|
+
coords = feature["geometry"]["coordinates"][0]
|
|
207
|
+
if coords[0] != coords[-1]:
|
|
208
|
+
raise ValueError("Polygon must be closed (first and last points must match)")
|
|
209
|
+
|
|
210
|
+
def signup(self, email: str, password: str) -> Dict[str, Any]:
|
|
211
|
+
if not self.auth_client:
|
|
212
|
+
raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
|
|
213
|
+
return self.auth_client.signup(email, password)
|
|
214
|
+
|
|
215
|
+
def login(self, email: str, password: str) -> Dict[str, str]:
|
|
216
|
+
if not self.auth_client:
|
|
217
|
+
raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
# First attempt to login
|
|
221
|
+
token_response = self.auth_client.login(email, password)
|
|
222
|
+
|
|
223
|
+
print("the token response is ", token_response)
|
|
224
|
+
# Only proceed with API key retrieval if login was successful
|
|
225
|
+
if token_response:
|
|
226
|
+
# After successful login, get the API key
|
|
227
|
+
api_key_response = self.view_api_key()
|
|
228
|
+
self.key = api_key_response
|
|
229
|
+
|
|
230
|
+
# Save email and API key to config file
|
|
231
|
+
import os
|
|
232
|
+
import json
|
|
233
|
+
config_path = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
|
|
234
|
+
try:
|
|
235
|
+
config = {"EMAIL": email, "TERRAKIO_API_KEY": self.key}
|
|
236
|
+
if os.path.exists(config_path):
|
|
237
|
+
with open(config_path, 'r') as f:
|
|
238
|
+
config = json.load(f)
|
|
239
|
+
config["EMAIL"] = email
|
|
240
|
+
config["TERRAKIO_API_KEY"] = self.key
|
|
241
|
+
|
|
242
|
+
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
243
|
+
with open(config_path, 'w') as f:
|
|
244
|
+
json.dump(config, f, indent=4)
|
|
245
|
+
|
|
246
|
+
if not self.quiet:
|
|
247
|
+
print(f"Successfully authenticated as: {email}")
|
|
248
|
+
print(f"API key saved to {config_path}")
|
|
249
|
+
except Exception as e:
|
|
250
|
+
if not self.quiet:
|
|
251
|
+
print(f"Warning: Failed to update config file: {e}")
|
|
252
|
+
|
|
253
|
+
return {"token": token_response} if token_response else {"error": "Login failed"}
|
|
254
|
+
except Exception as e:
|
|
255
|
+
if not self.quiet:
|
|
256
|
+
print(f"Login failed: {str(e)}")
|
|
257
|
+
raise
|
|
258
|
+
|
|
259
|
+
def refresh_api_key(self) -> str:
|
|
260
|
+
if not self.auth_client:
|
|
261
|
+
raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
|
|
262
|
+
if not self.auth_client.token:
|
|
263
|
+
raise ConfigurationError("Not authenticated. Call login() first.")
|
|
264
|
+
self.key = self.auth_client.refresh_api_key()
|
|
265
|
+
self.session.headers.update({'x-api-key': self.key})
|
|
266
|
+
import os
|
|
267
|
+
config_path = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
|
|
268
|
+
try:
|
|
269
|
+
config = {"EMAIL": "", "TERRAKIO_API_KEY": ""}
|
|
270
|
+
if os.path.exists(config_path):
|
|
271
|
+
with open(config_path, 'r') as f:
|
|
272
|
+
config = json.load(f)
|
|
273
|
+
config["TERRAKIO_API_KEY"] = self.key
|
|
274
|
+
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
275
|
+
with open(config_path, 'w') as f:
|
|
276
|
+
json.dump(config, f, indent=4)
|
|
277
|
+
if not self.quiet:
|
|
278
|
+
print(f"API key generated successfully and updated in {config_path}")
|
|
279
|
+
except Exception as e:
|
|
280
|
+
if not self.quiet:
|
|
281
|
+
print(f"Warning: Failed to update config file: {e}")
|
|
282
|
+
return self.key
|
|
283
|
+
|
|
284
|
+
def view_api_key(self) -> str:
|
|
285
|
+
if not self.auth_client:
|
|
286
|
+
raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
|
|
287
|
+
if not self.auth_client.token:
|
|
288
|
+
raise ConfigurationError("Not authenticated. Call login() first.")
|
|
289
|
+
self.key = self.auth_client.view_api_key()
|
|
290
|
+
self.session.headers.update({'x-api-key': self.key})
|
|
291
|
+
return self.key
|
|
292
|
+
|
|
293
|
+
def get_user_info(self) -> Dict[str, Any]:
|
|
294
|
+
if not self.auth_client:
|
|
295
|
+
raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
|
|
296
|
+
if not self.auth_client.token:
|
|
297
|
+
raise ConfigurationError("Not authenticated. Call login() first.")
|
|
298
|
+
return self.auth_client.get_user_info()
|
|
299
|
+
|
|
300
|
+
def wcs(self, expr: str, feature: Union[Dict[str, Any], ShapelyGeometry], in_crs: str = "epsg:4326",
|
|
301
|
+
out_crs: str = "epsg:4326", output: str = "csv", resolution: int = -1,
|
|
302
|
+
**kwargs):
|
|
303
|
+
if hasattr(feature, 'is_valid'):
|
|
304
|
+
from shapely.geometry import mapping
|
|
305
|
+
feature = {
|
|
306
|
+
"type": "Feature",
|
|
307
|
+
"geometry": mapping(feature),
|
|
308
|
+
"properties": {}
|
|
309
|
+
}
|
|
310
|
+
self.validate_feature(feature)
|
|
311
|
+
payload = {
|
|
312
|
+
"feature": feature,
|
|
313
|
+
"in_crs": in_crs,
|
|
314
|
+
"out_crs": out_crs,
|
|
315
|
+
"output": output,
|
|
316
|
+
"resolution": resolution,
|
|
317
|
+
"expr": expr,
|
|
318
|
+
**kwargs
|
|
319
|
+
}
|
|
320
|
+
request_url = f"{self.url}/geoquery"
|
|
321
|
+
try:
|
|
322
|
+
response = self.session.post(request_url, json=payload, timeout=self.timeout, verify=self.verify)
|
|
323
|
+
if not response.ok:
|
|
324
|
+
error_msg = f"API request failed: {response.status_code} {response.reason}"
|
|
325
|
+
try:
|
|
326
|
+
error_data = response.json()
|
|
327
|
+
if "detail" in error_data:
|
|
328
|
+
error_msg += f" - {error_data['detail']}"
|
|
329
|
+
except:
|
|
330
|
+
pass
|
|
331
|
+
raise APIError(error_msg)
|
|
332
|
+
if output.lower() == "csv":
|
|
333
|
+
import pandas as pd
|
|
334
|
+
return pd.read_csv(BytesIO(response.content))
|
|
335
|
+
elif output.lower() == "netcdf":
|
|
336
|
+
return xr.open_dataset(BytesIO(response.content))
|
|
337
|
+
else:
|
|
338
|
+
try:
|
|
339
|
+
return xr.open_dataset(BytesIO(response.content))
|
|
340
|
+
except ValueError:
|
|
341
|
+
import pandas as pd
|
|
342
|
+
try:
|
|
343
|
+
return pd.read_csv(BytesIO(response.content))
|
|
344
|
+
except:
|
|
345
|
+
return response.content
|
|
346
|
+
except requests.RequestException as e:
|
|
347
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
348
|
+
|
|
349
|
+
# Admin/protected methods
|
|
350
|
+
def _get_user_by_id(self, user_id: str):
|
|
351
|
+
if not self.user_management:
|
|
352
|
+
from terrakio_core.user_management import UserManagement
|
|
353
|
+
if not self.url or not self.key:
|
|
354
|
+
raise ConfigurationError("User management client not initialized. Make sure API URL and key are set.")
|
|
355
|
+
self.user_management = UserManagement(
|
|
356
|
+
api_url=self.url,
|
|
357
|
+
api_key=self.key,
|
|
358
|
+
verify=self.verify,
|
|
359
|
+
timeout=self.timeout
|
|
360
|
+
)
|
|
361
|
+
return self.user_management.get_user_by_id(user_id)
|
|
362
|
+
|
|
363
|
+
def _get_user_by_email(self, email: str):
|
|
364
|
+
if not self.user_management:
|
|
365
|
+
from terrakio_core.user_management import UserManagement
|
|
366
|
+
if not self.url or not self.key:
|
|
367
|
+
raise ConfigurationError("User management client not initialized. Make sure API URL and key are set.")
|
|
368
|
+
self.user_management = UserManagement(
|
|
369
|
+
api_url=self.url,
|
|
370
|
+
api_key=self.key,
|
|
371
|
+
verify=self.verify,
|
|
372
|
+
timeout=self.timeout
|
|
373
|
+
)
|
|
374
|
+
return self.user_management.get_user_by_email(email)
|
|
375
|
+
|
|
376
|
+
def _list_users(self, substring: str = None, uid: bool = False):
|
|
377
|
+
if not self.user_management:
|
|
378
|
+
from terrakio_core.user_management import UserManagement
|
|
379
|
+
if not self.url or not self.key:
|
|
380
|
+
raise ConfigurationError("User management client not initialized. Make sure API URL and key are set.")
|
|
381
|
+
self.user_management = UserManagement(
|
|
382
|
+
api_url=self.url,
|
|
383
|
+
api_key=self.key,
|
|
384
|
+
verify=self.verify,
|
|
385
|
+
timeout=self.timeout
|
|
386
|
+
)
|
|
387
|
+
return self.user_management.list_users(substring=substring, uid=uid)
|
|
388
|
+
|
|
389
|
+
def _edit_user(self, user_id: str, uid: str = None, email: str = None, role: str = None, apiKey: str = None, groups: list = None, quota: int = None):
|
|
390
|
+
if not self.user_management:
|
|
391
|
+
from terrakio_core.user_management import UserManagement
|
|
392
|
+
if not self.url or not self.key:
|
|
393
|
+
raise ConfigurationError("User management client not initialized. Make sure API URL and key are set.")
|
|
394
|
+
self.user_management = UserManagement(
|
|
395
|
+
api_url=self.url,
|
|
396
|
+
api_key=self.key,
|
|
397
|
+
verify=self.verify,
|
|
398
|
+
timeout=self.timeout
|
|
399
|
+
)
|
|
400
|
+
return self.user_management.edit_user(
|
|
401
|
+
user_id=user_id,
|
|
402
|
+
uid=uid,
|
|
403
|
+
email=email,
|
|
404
|
+
role=role,
|
|
405
|
+
apiKey=apiKey,
|
|
406
|
+
groups=groups,
|
|
407
|
+
quota=quota
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def _reset_quota(self, email: str, quota: int = None):
|
|
411
|
+
if not self.user_management:
|
|
412
|
+
from terrakio_core.user_management import UserManagement
|
|
413
|
+
if not self.url or not self.key:
|
|
414
|
+
raise ConfigurationError("User management client not initialized. Make sure API URL and key are set.")
|
|
415
|
+
self.user_management = UserManagement(
|
|
416
|
+
api_url=self.url,
|
|
417
|
+
api_key=self.key,
|
|
418
|
+
verify=self.verify,
|
|
419
|
+
timeout=self.timeout
|
|
420
|
+
)
|
|
421
|
+
return self.user_management.reset_quota(email=email, quota=quota)
|
|
422
|
+
|
|
423
|
+
def _delete_user(self, uid: str):
|
|
424
|
+
if not self.user_management:
|
|
425
|
+
from terrakio_core.user_management import UserManagement
|
|
426
|
+
if not self.url or not self.key:
|
|
427
|
+
raise ConfigurationError("User management client not initialized. Make sure API URL and key are set.")
|
|
428
|
+
self.user_management = UserManagement(
|
|
429
|
+
api_url=self.url,
|
|
430
|
+
api_key=self.key,
|
|
431
|
+
verify=self.verify,
|
|
432
|
+
timeout=self.timeout
|
|
433
|
+
)
|
|
434
|
+
return self.user_management.delete_user(uid=uid)
|
|
435
|
+
|
|
436
|
+
# Dataset management protected methods
|
|
437
|
+
def _get_dataset(self, name: str, collection: str = "terrakio-datasets"):
|
|
438
|
+
if not self.dataset_management:
|
|
439
|
+
from terrakio_core.dataset_management import DatasetManagement
|
|
440
|
+
if not self.url or not self.key:
|
|
441
|
+
raise ConfigurationError("Dataset management client not initialized. Make sure API URL and key are set.")
|
|
442
|
+
self.dataset_management = DatasetManagement(
|
|
443
|
+
api_url=self.url,
|
|
444
|
+
api_key=self.key,
|
|
445
|
+
verify=self.verify,
|
|
446
|
+
timeout=self.timeout
|
|
447
|
+
)
|
|
448
|
+
return self.dataset_management.get_dataset(name=name, collection=collection)
|
|
449
|
+
|
|
450
|
+
def _list_datasets(self, substring: str = None, collection: str = "terrakio-datasets"):
|
|
451
|
+
if not self.dataset_management:
|
|
452
|
+
from terrakio_core.dataset_management import DatasetManagement
|
|
453
|
+
if not self.url or not self.key:
|
|
454
|
+
raise ConfigurationError("Dataset management client not initialized. Make sure API URL and key are set.")
|
|
455
|
+
self.dataset_management = DatasetManagement(
|
|
456
|
+
api_url=self.url,
|
|
457
|
+
api_key=self.key,
|
|
458
|
+
verify=self.verify,
|
|
459
|
+
timeout=self.timeout
|
|
460
|
+
)
|
|
461
|
+
return self.dataset_management.list_datasets(substring=substring, collection=collection)
|
|
462
|
+
|
|
463
|
+
def _create_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs):
|
|
464
|
+
if not self.dataset_management:
|
|
465
|
+
from terrakio_core.dataset_management import DatasetManagement
|
|
466
|
+
if not self.url or not self.key:
|
|
467
|
+
raise ConfigurationError("Dataset management client not initialized. Make sure API URL and key are set.")
|
|
468
|
+
self.dataset_management = DatasetManagement(
|
|
469
|
+
api_url=self.url,
|
|
470
|
+
api_key=self.key,
|
|
471
|
+
verify=self.verify,
|
|
472
|
+
timeout=self.timeout
|
|
473
|
+
)
|
|
474
|
+
return self.dataset_management.create_dataset(name=name, collection=collection, **kwargs)
|
|
475
|
+
|
|
476
|
+
def _update_dataset(self, name: str, append: bool = True, collection: str = "terrakio-datasets", **kwargs):
|
|
477
|
+
if not self.dataset_management:
|
|
478
|
+
from terrakio_core.dataset_management import DatasetManagement
|
|
479
|
+
if not self.url or not self.key:
|
|
480
|
+
raise ConfigurationError("Dataset management client not initialized. Make sure API URL and key are set.")
|
|
481
|
+
self.dataset_management = DatasetManagement(
|
|
482
|
+
api_url=self.url,
|
|
483
|
+
api_key=self.key,
|
|
484
|
+
verify=self.verify,
|
|
485
|
+
timeout=self.timeout
|
|
486
|
+
)
|
|
487
|
+
return self.dataset_management.update_dataset(name=name, append=append, collection=collection, **kwargs)
|
|
488
|
+
|
|
489
|
+
def _overwrite_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs):
|
|
490
|
+
if not self.dataset_management:
|
|
491
|
+
from terrakio_core.dataset_management import DatasetManagement
|
|
492
|
+
if not self.url or not self.key:
|
|
493
|
+
raise ConfigurationError("Dataset management client not initialized. Make sure API URL and key are set.")
|
|
494
|
+
self.dataset_management = DatasetManagement(
|
|
495
|
+
api_url=self.url,
|
|
496
|
+
api_key=self.key,
|
|
497
|
+
verify=self.verify,
|
|
498
|
+
timeout=self.timeout
|
|
499
|
+
)
|
|
500
|
+
return self.dataset_management.overwrite_dataset(name=name, collection=collection, **kwargs)
|
|
501
|
+
|
|
502
|
+
def _delete_dataset(self, name: str, collection: str = "terrakio-datasets"):
|
|
503
|
+
if not self.dataset_management:
|
|
504
|
+
from terrakio_core.dataset_management import DatasetManagement
|
|
505
|
+
if not self.url or not self.key:
|
|
506
|
+
raise ConfigurationError("Dataset management client not initialized. Make sure API URL and key are set.")
|
|
507
|
+
self.dataset_management = DatasetManagement(
|
|
508
|
+
api_url=self.url,
|
|
509
|
+
api_key=self.key,
|
|
510
|
+
verify=self.verify,
|
|
511
|
+
timeout=self.timeout
|
|
512
|
+
)
|
|
513
|
+
return self.dataset_management.delete_dataset(name=name, collection=collection)
|
|
514
|
+
|
|
515
|
+
def close(self):
|
|
516
|
+
"""Close all client sessions"""
|
|
517
|
+
self.session.close()
|
|
518
|
+
if self.auth_client:
|
|
519
|
+
self.auth_client.session.close()
|
|
520
|
+
# Close aiohttp session if it exists
|
|
521
|
+
if self._aiohttp_session and not self._aiohttp_session.closed:
|
|
522
|
+
asyncio.run(self.close_async())
|
|
523
|
+
|
|
524
|
+
def __enter__(self):
|
|
525
|
+
return self
|
|
526
|
+
|
|
527
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
528
|
+
self.close()
|
|
529
|
+
|
|
530
|
+
# Mass Stats methods
|
|
531
|
+
def upload_mass_stats(self, name, size, bucket, output, location=None, **kwargs):
|
|
532
|
+
if not self.mass_stats:
|
|
533
|
+
from terrakio_core.mass_stats import MassStats
|
|
534
|
+
if not self.url or not self.key:
|
|
535
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
536
|
+
self.mass_stats = MassStats(
|
|
537
|
+
base_url=self.url,
|
|
538
|
+
api_key=self.key,
|
|
539
|
+
verify=self.verify,
|
|
540
|
+
timeout=self.timeout
|
|
541
|
+
)
|
|
542
|
+
return self.mass_stats.upload_request(name, size, bucket, output, location, **kwargs)
|
|
543
|
+
|
|
544
|
+
def start_mass_stats_job(self, task_id):
|
|
545
|
+
if not self.mass_stats:
|
|
546
|
+
from terrakio_core.mass_stats import MassStats
|
|
547
|
+
if not self.url or not self.key:
|
|
548
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
549
|
+
self.mass_stats = MassStats(
|
|
550
|
+
base_url=self.url,
|
|
551
|
+
api_key=self.key,
|
|
552
|
+
verify=self.verify,
|
|
553
|
+
timeout=self.timeout
|
|
554
|
+
)
|
|
555
|
+
return self.mass_stats.start_job(task_id)
|
|
556
|
+
|
|
557
|
+
def get_mass_stats_task_id(self, name, stage, uid=None):
|
|
558
|
+
if not self.mass_stats:
|
|
559
|
+
from terrakio_core.mass_stats import MassStats
|
|
560
|
+
if not self.url or not self.key:
|
|
561
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
562
|
+
self.mass_stats = MassStats(
|
|
563
|
+
base_url=self.url,
|
|
564
|
+
api_key=self.key,
|
|
565
|
+
verify=self.verify,
|
|
566
|
+
timeout=self.timeout
|
|
567
|
+
)
|
|
568
|
+
return self.mass_stats.get_task_id(name, stage, uid)
|
|
569
|
+
|
|
570
|
+
def track_mass_stats_job(self, ids=None):
|
|
571
|
+
if not self.mass_stats:
|
|
572
|
+
from terrakio_core.mass_stats import MassStats
|
|
573
|
+
if not self.url or not self.key:
|
|
574
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
575
|
+
self.mass_stats = MassStats(
|
|
576
|
+
base_url=self.url,
|
|
577
|
+
api_key=self.key,
|
|
578
|
+
verify=self.verify,
|
|
579
|
+
timeout=self.timeout
|
|
580
|
+
)
|
|
581
|
+
return self.mass_stats.track_job(ids)
|
|
582
|
+
|
|
583
|
+
def get_mass_stats_history(self, limit=100):
|
|
584
|
+
if not self.mass_stats:
|
|
585
|
+
from terrakio_core.mass_stats import MassStats
|
|
586
|
+
if not self.url or not self.key:
|
|
587
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
588
|
+
self.mass_stats = MassStats(
|
|
589
|
+
base_url=self.url,
|
|
590
|
+
api_key=self.key,
|
|
591
|
+
verify=self.verify,
|
|
592
|
+
timeout=self.timeout
|
|
593
|
+
)
|
|
594
|
+
return self.mass_stats.get_history(limit)
|
|
595
|
+
|
|
596
|
+
def start_mass_stats_post_processing(self, process_name, data_name, output, consumer_path, overwrite=False):
|
|
597
|
+
if not self.mass_stats:
|
|
598
|
+
from terrakio_core.mass_stats import MassStats
|
|
599
|
+
if not self.url or not self.key:
|
|
600
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
601
|
+
self.mass_stats = MassStats(
|
|
602
|
+
base_url=self.url,
|
|
603
|
+
api_key=self.key,
|
|
604
|
+
verify=self.verify,
|
|
605
|
+
timeout=self.timeout
|
|
606
|
+
)
|
|
607
|
+
return self.mass_stats.start_post_processing(process_name, data_name, output, consumer_path, overwrite)
|
|
608
|
+
|
|
609
|
+
def download_mass_stats_results(self, id=None, force_loc=False, **kwargs):
|
|
610
|
+
if not self.mass_stats:
|
|
611
|
+
from terrakio_core.mass_stats import MassStats
|
|
612
|
+
if not self.url or not self.key:
|
|
613
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
614
|
+
self.mass_stats = MassStats(
|
|
615
|
+
base_url=self.url,
|
|
616
|
+
api_key=self.key,
|
|
617
|
+
verify=self.verify,
|
|
618
|
+
timeout=self.timeout
|
|
619
|
+
)
|
|
620
|
+
return self.mass_stats.download_results(id, force_loc, **kwargs)
|
|
621
|
+
|
|
622
|
+
def cancel_mass_stats_job(self, id):
|
|
623
|
+
if not self.mass_stats:
|
|
624
|
+
from terrakio_core.mass_stats import MassStats
|
|
625
|
+
if not self.url or not self.key:
|
|
626
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
627
|
+
self.mass_stats = MassStats(
|
|
628
|
+
base_url=self.url,
|
|
629
|
+
api_key=self.key,
|
|
630
|
+
verify=self.verify,
|
|
631
|
+
timeout=self.timeout
|
|
632
|
+
)
|
|
633
|
+
return self.mass_stats.cancel_job(id)
|
|
634
|
+
|
|
635
|
+
def cancel_all_mass_stats_jobs(self):
|
|
636
|
+
if not self.mass_stats:
|
|
637
|
+
from terrakio_core.mass_stats import MassStats
|
|
638
|
+
if not self.url or not self.key:
|
|
639
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
640
|
+
self.mass_stats = MassStats(
|
|
641
|
+
base_url=self.url,
|
|
642
|
+
api_key=self.key,
|
|
643
|
+
verify=self.verify,
|
|
644
|
+
timeout=self.timeout
|
|
645
|
+
)
|
|
646
|
+
return self.mass_stats.cancel_all_jobs()
|
|
647
|
+
|
|
648
|
+
def _create_pyramids(self, name, levels, config):
|
|
649
|
+
if not self.mass_stats:
|
|
650
|
+
from terrakio_core.mass_stats import MassStats
|
|
651
|
+
if not self.url or not self.key:
|
|
652
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
653
|
+
self.mass_stats = MassStats(
|
|
654
|
+
base_url=self.url,
|
|
655
|
+
api_key=self.key,
|
|
656
|
+
verify=self.verify,
|
|
657
|
+
timeout=self.timeout
|
|
658
|
+
)
|
|
659
|
+
return self.mass_stats.create_pyramids(name, levels, config)
|
|
660
|
+
|
|
661
|
+
def random_sample(self, name, **kwargs):
|
|
662
|
+
if not self.mass_stats:
|
|
663
|
+
from terrakio_core.mass_stats import MassStats
|
|
664
|
+
if not self.url or not self.key:
|
|
665
|
+
raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
|
|
666
|
+
self.mass_stats = MassStats(
|
|
667
|
+
base_url=self.url,
|
|
668
|
+
api_key=self.key,
|
|
669
|
+
verify=self.verify,
|
|
670
|
+
timeout=self.timeout
|
|
671
|
+
)
|
|
672
|
+
return self.mass_stats.random_sample(name, **kwargs)
|
|
673
|
+
|
|
674
|
+
async def zonal_stats_async(self, gdb, expr, conc=20, inplace=False, output="csv"):
|
|
675
|
+
"""
|
|
676
|
+
Compute zonal statistics for all geometries in a GeoDataFrame using asyncio for concurrency.
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
# Process geometries in batches
|
|
680
|
+
all_results = []
|
|
681
|
+
row_indices = []
|
|
682
|
+
|
|
683
|
+
async def process_geometry(geom, index):
|
|
684
|
+
"""Process a single geometry"""
|
|
685
|
+
try:
|
|
686
|
+
feature = {
|
|
687
|
+
"type": "Feature",
|
|
688
|
+
"geometry": mapping(geom),
|
|
689
|
+
"properties": {"index": index}
|
|
690
|
+
}
|
|
691
|
+
result = await self.wcs_async(expr=expr, feature=feature, output=output)
|
|
692
|
+
# Add original index to track which geometry this result belongs to
|
|
693
|
+
if isinstance(result, pd.DataFrame):
|
|
694
|
+
result['_geometry_index'] = index
|
|
695
|
+
return result
|
|
696
|
+
except Exception as e:
|
|
697
|
+
raise
|
|
698
|
+
|
|
699
|
+
async def process_batch(batch_indices):
|
|
700
|
+
"""Process a batch of geometries concurrently using TaskGroup"""
|
|
701
|
+
try:
|
|
702
|
+
async with asyncio.TaskGroup() as tg:
|
|
703
|
+
tasks = []
|
|
704
|
+
for idx in batch_indices:
|
|
705
|
+
geom = gdb.geometry.iloc[idx]
|
|
706
|
+
task = tg.create_task(process_geometry(geom, idx))
|
|
707
|
+
tasks.append(task)
|
|
708
|
+
|
|
709
|
+
# Get results from completed tasks
|
|
710
|
+
results = []
|
|
711
|
+
for task in tasks:
|
|
712
|
+
try:
|
|
713
|
+
result = task.result()
|
|
714
|
+
results.append(result)
|
|
715
|
+
except Exception as e:
|
|
716
|
+
raise
|
|
717
|
+
|
|
718
|
+
return results
|
|
719
|
+
except* Exception as e:
|
|
720
|
+
# Get the actual exceptions from the tasks
|
|
721
|
+
for task in tasks:
|
|
722
|
+
if task.done() and task.exception():
|
|
723
|
+
raise task.exception()
|
|
724
|
+
raise
|
|
725
|
+
|
|
726
|
+
# Process in batches to control concurrency
|
|
727
|
+
for i in range(0, len(gdb), conc):
|
|
728
|
+
batch_indices = range(i, min(i + conc, len(gdb)))
|
|
729
|
+
try:
|
|
730
|
+
batch_results = await process_batch(batch_indices)
|
|
731
|
+
all_results.extend(batch_results)
|
|
732
|
+
row_indices.extend(batch_indices)
|
|
733
|
+
except Exception as e:
|
|
734
|
+
if hasattr(e, 'response'):
|
|
735
|
+
raise APIError(f"API request failed: {e.response.text}")
|
|
736
|
+
raise
|
|
737
|
+
|
|
738
|
+
if not all_results:
|
|
739
|
+
raise ValueError("No valid results were returned for any geometry")
|
|
740
|
+
|
|
741
|
+
# Combine all results
|
|
742
|
+
combined_df = pd.concat(all_results, ignore_index=True)
|
|
743
|
+
|
|
744
|
+
# Check if we have temporal results
|
|
745
|
+
has_time = 'time' in combined_df.columns
|
|
746
|
+
|
|
747
|
+
# Create a result GeoDataFrame
|
|
748
|
+
if has_time:
|
|
749
|
+
# For temporal data, we'll create a hierarchical index
|
|
750
|
+
# First make sure we have the geometry index and time columns
|
|
751
|
+
if '_geometry_index' not in combined_df.columns:
|
|
752
|
+
raise ValueError("Missing geometry index in results")
|
|
753
|
+
|
|
754
|
+
# Create hierarchical index on geometry_index and time
|
|
755
|
+
combined_df.set_index(['_geometry_index', 'time'], inplace=True)
|
|
756
|
+
|
|
757
|
+
# For each unique geometry index, we need the corresponding geometry
|
|
758
|
+
geometry_series = gdb.geometry.copy()
|
|
759
|
+
|
|
760
|
+
# Get columns that will become new attributes (exclude index/utility columns)
|
|
761
|
+
result_cols = combined_df.columns
|
|
762
|
+
|
|
763
|
+
# Create a new GeoDataFrame with multi-index
|
|
764
|
+
result_rows = []
|
|
765
|
+
geometries = []
|
|
766
|
+
|
|
767
|
+
# Iterate through the hierarchical index
|
|
768
|
+
for (geom_idx, time_val), row in combined_df.iterrows():
|
|
769
|
+
# Create a new row with geometry properties + result columns
|
|
770
|
+
new_row = {}
|
|
771
|
+
|
|
772
|
+
# Add original GeoDataFrame columns (except geometry)
|
|
773
|
+
for col in gdb.columns:
|
|
774
|
+
if col != 'geometry':
|
|
775
|
+
new_row[col] = gdb.loc[geom_idx, col]
|
|
776
|
+
|
|
777
|
+
# Add result columns
|
|
778
|
+
for col in result_cols:
|
|
779
|
+
new_row[col] = row[col]
|
|
780
|
+
|
|
781
|
+
result_rows.append(new_row)
|
|
782
|
+
geometries.append(gdb.geometry.iloc[geom_idx])
|
|
783
|
+
|
|
784
|
+
# Create a new GeoDataFrame with multi-index
|
|
785
|
+
multi_index = pd.MultiIndex.from_tuples(
|
|
786
|
+
combined_df.index.tolist(),
|
|
787
|
+
names=['geometry_index', 'time']
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
result_gdf = gpd.GeoDataFrame(
|
|
791
|
+
result_rows,
|
|
792
|
+
geometry=geometries,
|
|
793
|
+
index=multi_index
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
if inplace:
|
|
797
|
+
# Can't really do inplace with multi-temporal results as we're changing the structure
|
|
798
|
+
return result_gdf
|
|
799
|
+
else:
|
|
800
|
+
return result_gdf
|
|
801
|
+
else:
|
|
802
|
+
# Non-temporal data - just add new columns to the existing GeoDataFrame
|
|
803
|
+
result_gdf = gdb.copy() if not inplace else gdb
|
|
804
|
+
|
|
805
|
+
# Get column names from the results (excluding utility columns)
|
|
806
|
+
result_cols = [col for col in combined_df.columns if col not in ['_geometry_index']]
|
|
807
|
+
|
|
808
|
+
# Create a mapping from geometry index to result rows
|
|
809
|
+
geom_idx_to_row = {}
|
|
810
|
+
for idx, row in combined_df.iterrows():
|
|
811
|
+
geom_idx = int(row['_geometry_index'])
|
|
812
|
+
geom_idx_to_row[geom_idx] = row
|
|
813
|
+
|
|
814
|
+
# Add results as new columns to the GeoDataFrame
|
|
815
|
+
for col in result_cols:
|
|
816
|
+
# Initialize the column with None or appropriate default
|
|
817
|
+
if col not in result_gdf.columns:
|
|
818
|
+
result_gdf[col] = None
|
|
819
|
+
|
|
820
|
+
# Fill in values from results
|
|
821
|
+
for geom_idx, row in geom_idx_to_row.items():
|
|
822
|
+
result_gdf.loc[geom_idx, col] = row[col]
|
|
823
|
+
|
|
824
|
+
if inplace:
|
|
825
|
+
return None
|
|
826
|
+
else:
|
|
827
|
+
return result_gdf
|
|
828
|
+
|
|
829
|
+
def zonal_stats(self, gdb, expr, conc=20, inplace=False, output="csv"):
|
|
830
|
+
"""
|
|
831
|
+
Compute zonal statistics for all geometries in a GeoDataFrame.
|
|
832
|
+
|
|
833
|
+
Args:
|
|
834
|
+
gdb (geopandas.GeoDataFrame): GeoDataFrame containing geometries
|
|
835
|
+
expr (str): Terrakio expression to evaluate, can include spatial aggregations
|
|
836
|
+
conc (int): Number of concurrent requests to make
|
|
837
|
+
inplace (bool): Whether to modify the input GeoDataFrame in place
|
|
838
|
+
output (str): Output format (csv or netcdf)
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
geopandas.GeoDataFrame: GeoDataFrame with added columns for results, or None if inplace=True
|
|
842
|
+
"""
|
|
843
|
+
import asyncio
|
|
844
|
+
result = asyncio.run(self.zonal_stats_async(gdb, expr, conc, inplace, output))
|
|
845
|
+
# Ensure aiohttp session is closed after running async code
|
|
846
|
+
try:
|
|
847
|
+
if self._aiohttp_session and not self._aiohttp_session.closed:
|
|
848
|
+
asyncio.run(self.close_async())
|
|
849
|
+
except RuntimeError:
|
|
850
|
+
# Event loop may already be closed, ignore
|
|
851
|
+
pass
|
|
852
|
+
return result
|
|
853
|
+
|
|
854
|
+
# Group access management protected methods
|
|
855
|
+
def _get_group_users_and_datasets(self, group_name: str):
|
|
856
|
+
if not hasattr(self, "group_access_management") or self.group_access_management is None:
|
|
857
|
+
from terrakio_core.group_access_management import GroupAccessManagement
|
|
858
|
+
if not self.url or not self.key:
|
|
859
|
+
raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
|
|
860
|
+
self.group_access_management = GroupAccessManagement(
|
|
861
|
+
api_url=self.url,
|
|
862
|
+
api_key=self.key,
|
|
863
|
+
verify=self.verify,
|
|
864
|
+
timeout=self.timeout
|
|
865
|
+
)
|
|
866
|
+
return self.group_access_management.get_group_users_and_datasets(group_name)
|
|
867
|
+
|
|
868
|
+
def _add_group_to_dataset(self, dataset: str, group: str):
|
|
869
|
+
if not hasattr(self, "group_access_management") or self.group_access_management is None:
|
|
870
|
+
from terrakio_core.group_access_management import GroupAccessManagement
|
|
871
|
+
if not self.url or not self.key:
|
|
872
|
+
raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
|
|
873
|
+
self.group_access_management = GroupAccessManagement(
|
|
874
|
+
api_url=self.url,
|
|
875
|
+
api_key=self.key,
|
|
876
|
+
verify=self.verify,
|
|
877
|
+
timeout=self.timeout
|
|
878
|
+
)
|
|
879
|
+
return self.group_access_management.add_group_to_dataset(dataset, group)
|
|
880
|
+
|
|
881
|
+
def _add_group_to_user(self, uid: str, group: str):
|
|
882
|
+
if not hasattr(self, "group_access_management") or self.group_access_management is None:
|
|
883
|
+
from terrakio_core.group_access_management import GroupAccessManagement
|
|
884
|
+
if not self.url or not self.key:
|
|
885
|
+
raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
|
|
886
|
+
self.group_access_management = GroupAccessManagement(
|
|
887
|
+
api_url=self.url,
|
|
888
|
+
api_key=self.key,
|
|
889
|
+
verify=self.verify,
|
|
890
|
+
timeout=self.timeout
|
|
891
|
+
)
|
|
892
|
+
print("the uid is and the group is ", uid, group)
|
|
893
|
+
return self.group_access_management.add_group_to_user(uid, group)
|
|
894
|
+
|
|
895
|
+
def _delete_group_from_user(self, uid: str, group: str):
|
|
896
|
+
if not hasattr(self, "group_access_management") or self.group_access_management is None:
|
|
897
|
+
from terrakio_core.group_access_management import GroupAccessManagement
|
|
898
|
+
if not self.url or not self.key:
|
|
899
|
+
raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
|
|
900
|
+
self.group_access_management = GroupAccessManagement(
|
|
901
|
+
api_url=self.url,
|
|
902
|
+
api_key=self.key,
|
|
903
|
+
verify=self.verify,
|
|
904
|
+
timeout=self.timeout
|
|
905
|
+
)
|
|
906
|
+
return self.group_access_management.delete_group_from_user(uid, group)
|
|
907
|
+
|
|
908
|
+
def _delete_group_from_dataset(self, dataset: str, group: str):
|
|
909
|
+
if not hasattr(self, "group_access_management") or self.group_access_management is None:
|
|
910
|
+
from terrakio_core.group_access_management import GroupAccessManagement
|
|
911
|
+
if not self.url or not self.key:
|
|
912
|
+
raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
|
|
913
|
+
self.group_access_management = GroupAccessManagement(
|
|
914
|
+
api_url=self.url,
|
|
915
|
+
api_key=self.key,
|
|
916
|
+
verify=self.verify,
|
|
917
|
+
timeout=self.timeout
|
|
918
|
+
)
|
|
919
|
+
return self.group_access_management.delete_group_from_dataset(dataset, group)
|
|
920
|
+
|
|
921
|
+
# Space management protected methods
|
|
922
|
+
def _get_total_space_used(self):
|
|
923
|
+
if not hasattr(self, "space_management") or self.space_management is None:
|
|
924
|
+
from terrakio_core.space_management import SpaceManagement
|
|
925
|
+
if not self.url or not self.key:
|
|
926
|
+
raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
|
|
927
|
+
self.space_management = SpaceManagement(
|
|
928
|
+
api_url=self.url,
|
|
929
|
+
api_key=self.key,
|
|
930
|
+
verify=self.verify,
|
|
931
|
+
timeout=self.timeout
|
|
932
|
+
)
|
|
933
|
+
return self.space_management.get_total_space_used()
|
|
934
|
+
|
|
935
|
+
def _get_space_used_by_job(self, name: str, region: str = None):
|
|
936
|
+
if not hasattr(self, "space_management") or self.space_management is None:
|
|
937
|
+
from terrakio_core.space_management import SpaceManagement
|
|
938
|
+
if not self.url or not self.key:
|
|
939
|
+
raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
|
|
940
|
+
self.space_management = SpaceManagement(
|
|
941
|
+
api_url=self.url,
|
|
942
|
+
api_key=self.key,
|
|
943
|
+
verify=self.verify,
|
|
944
|
+
timeout=self.timeout
|
|
945
|
+
)
|
|
946
|
+
return self.space_management.get_space_used_by_job(name, region)
|
|
947
|
+
|
|
948
|
+
def _delete_user_job(self, name: str, region: str = None):
|
|
949
|
+
if not hasattr(self, "space_management") or self.space_management is None:
|
|
950
|
+
from terrakio_core.space_management import SpaceManagement
|
|
951
|
+
if not self.url or not self.key:
|
|
952
|
+
raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
|
|
953
|
+
self.space_management = SpaceManagement(
|
|
954
|
+
api_url=self.url,
|
|
955
|
+
api_key=self.key,
|
|
956
|
+
verify=self.verify,
|
|
957
|
+
timeout=self.timeout
|
|
958
|
+
)
|
|
959
|
+
return self.space_management.delete_user_job(name, region)
|
|
960
|
+
|
|
961
|
+
def _delete_data_in_path(self, path: str, region: str = None):
|
|
962
|
+
if not hasattr(self, "space_management") or self.space_management is None:
|
|
963
|
+
from terrakio_core.space_management import SpaceManagement
|
|
964
|
+
if not self.url or not self.key:
|
|
965
|
+
raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
|
|
966
|
+
self.space_management = SpaceManagement(
|
|
967
|
+
api_url=self.url,
|
|
968
|
+
api_key=self.key,
|
|
969
|
+
verify=self.verify,
|
|
970
|
+
timeout=self.timeout
|
|
971
|
+
)
|
|
972
|
+
return self.space_management.delete_data_in_path(path, region)
|
|
973
|
+
|
|
974
|
+
def train_model(self, model_name: str, training_data: dict) -> dict:
|
|
975
|
+
"""
|
|
976
|
+
Train a model using the external model training API.
|
|
977
|
+
|
|
978
|
+
Args:
|
|
979
|
+
model_name (str): The name of the model to train.
|
|
980
|
+
training_data (dict): Dictionary containing training data parameters.
|
|
981
|
+
|
|
982
|
+
Returns:
|
|
983
|
+
dict: The response from the model training API.
|
|
984
|
+
"""
|
|
985
|
+
endpoint = "https://modeltraining-573248941006.australia-southeast1.run.app/train_model"
|
|
986
|
+
payload = {
|
|
987
|
+
"model_name": model_name,
|
|
988
|
+
"training_data": training_data
|
|
989
|
+
}
|
|
990
|
+
try:
|
|
991
|
+
response = self.session.post(endpoint, json=payload, timeout=self.timeout, verify=self.verify)
|
|
992
|
+
if not response.ok:
|
|
993
|
+
error_msg = f"Model training request failed: {response.status_code} {response.reason}"
|
|
994
|
+
try:
|
|
995
|
+
error_data = response.json()
|
|
996
|
+
if "detail" in error_data:
|
|
997
|
+
error_msg += f" - {error_data['detail']}"
|
|
998
|
+
except Exception:
|
|
999
|
+
if response.text:
|
|
1000
|
+
error_msg += f" - {response.text}"
|
|
1001
|
+
raise APIError(error_msg)
|
|
1002
|
+
return response.json()
|
|
1003
|
+
except requests.RequestException as e:
|
|
1004
|
+
raise APIError(f"Model training request failed: {str(e)}")
|
|
1005
|
+
|