terrakio-core 0.4.98__py3-none-any.whl → 0.4.98.1b1__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.1b1.dist-info}/METADATA +2 -1
- terrakio_core-0.4.98.1b1.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.1b1.dist-info}/WHEEL +0 -0
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
# Standard library imports
|
|
2
1
|
import ast
|
|
3
2
|
import json
|
|
4
3
|
import textwrap
|
|
5
|
-
import time
|
|
6
4
|
from io import BytesIO
|
|
7
5
|
from typing import Optional, Tuple
|
|
6
|
+
|
|
8
7
|
import onnxruntime as ort
|
|
9
8
|
|
|
10
|
-
# Internal imports
|
|
11
9
|
from ..helper.decorators import require_api_key
|
|
12
10
|
|
|
13
|
-
# Optional dependency flags
|
|
14
11
|
TORCH_AVAILABLE = False
|
|
15
12
|
SKL2ONNX_AVAILABLE = False
|
|
16
13
|
|
|
17
|
-
# PyTorch imports
|
|
18
14
|
try:
|
|
19
15
|
import torch
|
|
20
16
|
TORCH_AVAILABLE = True
|
|
21
17
|
except ImportError:
|
|
22
18
|
torch = None
|
|
23
19
|
|
|
24
|
-
# Scikit-learn and ONNX conversion imports
|
|
25
20
|
try:
|
|
26
21
|
from sklearn.base import BaseEstimator
|
|
27
22
|
from skl2onnx import convert_sklearn
|
|
@@ -36,124 +31,159 @@ class ModelManagement:
|
|
|
36
31
|
def __init__(self, client):
|
|
37
32
|
self._client = client
|
|
38
33
|
|
|
34
|
+
def _generate_test_request(self, expr: str, crs: str, resolution: float) -> dict:
|
|
35
|
+
"""Generate test request using set polygon (Australia)"""
|
|
36
|
+
req = {
|
|
37
|
+
"feature": {
|
|
38
|
+
"type": "Feature",
|
|
39
|
+
"properties": {},
|
|
40
|
+
"geometry": {
|
|
41
|
+
"coordinates": [
|
|
42
|
+
[
|
|
43
|
+
[150.57846438251084, -29.535000759011766],
|
|
44
|
+
[150.57846438251084, -29.539538891448665],
|
|
45
|
+
[150.5845432181327, -29.539538891448665],
|
|
46
|
+
[150.5845432181327, -29.535000759011766],
|
|
47
|
+
[150.57846438251084, -29.535000759011766]
|
|
48
|
+
]
|
|
49
|
+
],
|
|
50
|
+
"type": "Polygon"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"in_crs": "epsg:4326",
|
|
54
|
+
"out_crs": crs,
|
|
55
|
+
"output": "csv",
|
|
56
|
+
"resolution": resolution,
|
|
57
|
+
"expr": expr,
|
|
58
|
+
"debug": True
|
|
59
|
+
}
|
|
60
|
+
return req
|
|
61
|
+
|
|
39
62
|
@require_api_key
|
|
40
63
|
async def generate_ai_dataset(
|
|
41
64
|
self,
|
|
42
65
|
name: str,
|
|
43
|
-
|
|
66
|
+
aoi: str,
|
|
44
67
|
expression_x: str,
|
|
45
|
-
filter_x_rate: float,
|
|
46
|
-
filter_y_rate: float,
|
|
47
|
-
samples: int,
|
|
48
|
-
tile_size: int,
|
|
49
|
-
expression_y: str = "skip",
|
|
50
68
|
filter_x: str = "skip",
|
|
69
|
+
filter_x_rate: float = 1,
|
|
70
|
+
expression_y: str = "skip",
|
|
51
71
|
filter_y: str = "skip",
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
filter_y_rate: float = 1,
|
|
73
|
+
samples: int = 1000,
|
|
74
|
+
tile_size: float = 256,
|
|
75
|
+
crs: str = "epsg:3577",
|
|
76
|
+
res: float = 10,
|
|
77
|
+
res_y: float = None,
|
|
78
|
+
skip_test: bool = False,
|
|
56
79
|
start_year: int = None,
|
|
57
80
|
end_year: int = None,
|
|
81
|
+
bucket: str = None,
|
|
82
|
+
server: str = None,
|
|
83
|
+
extra_filters: list[str] = None,
|
|
84
|
+
extra_filters_rate: list[float] = None,
|
|
85
|
+
extra_filters_res: list[float] = None
|
|
58
86
|
) -> dict:
|
|
59
87
|
"""
|
|
60
88
|
Generate an AI dataset using specified parameters.
|
|
61
89
|
|
|
62
90
|
Args:
|
|
63
|
-
name (str): Name of the
|
|
64
|
-
|
|
65
|
-
expression_x (str): Expression for X
|
|
66
|
-
filter_x (str): Filter for X
|
|
67
|
-
filter_x_rate (float): Filter rate for X
|
|
68
|
-
expression_y (str): Expression for Y
|
|
69
|
-
filter_y (str): Filter for Y
|
|
70
|
-
filter_y_rate (float): Filter rate for Y
|
|
71
|
-
samples (int): Number of samples to generate
|
|
72
|
-
tile_size (
|
|
73
|
-
crs (str
|
|
74
|
-
res (float
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
start_year (int
|
|
78
|
-
end_year (int
|
|
91
|
+
name (str): Name of the collection to create
|
|
92
|
+
aoi (str): Path to GeoJSON file containing area of interest
|
|
93
|
+
expression_x (str): Expression for X data (features)
|
|
94
|
+
filter_x (str): Filter expression for X data (default: "skip")
|
|
95
|
+
filter_x_rate (float): Filter rate for X data (default: 1)
|
|
96
|
+
expression_y (str): Expression for Y data (labels) (default: "skip")
|
|
97
|
+
filter_y (str): Filter expression for Y data (default: "skip")
|
|
98
|
+
filter_y_rate (float): Filter rate for Y data (default: 1)
|
|
99
|
+
samples (int): Number of samples to generate (default: 1000)
|
|
100
|
+
tile_size (float): Size of tiles in pixels (default: 256)
|
|
101
|
+
crs (str): Coordinate reference system (default: "epsg:3577")
|
|
102
|
+
res (float): Resolution for X data (default: 10)
|
|
103
|
+
res_y (float): Resolution for Y data, defaults to res if None
|
|
104
|
+
skip_test (bool): Skip expression validation test (default: False)
|
|
105
|
+
start_year (int): Start year for temporal filtering
|
|
106
|
+
end_year (int): End year for temporal filtering
|
|
107
|
+
bucket (str): Storage bucket name
|
|
108
|
+
server (str): Server to use for processing
|
|
109
|
+
extra_filters (list[str]): Additional filter expressions
|
|
110
|
+
extra_filters_rate (list[float]): Rates for additional filters
|
|
111
|
+
extra_filters_res (list[float]): Resolutions for additional filters
|
|
79
112
|
|
|
80
113
|
Returns:
|
|
81
|
-
dict: Response
|
|
114
|
+
dict: Response containing task_id and collection name
|
|
82
115
|
|
|
83
116
|
Raises:
|
|
84
117
|
APIError: If the API request fails
|
|
118
|
+
TypeError: If extra filters have mismatched rate and resolution lists
|
|
85
119
|
"""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
120
|
+
expressions = [{"expr": expression_x, "res": res, "prefix": "x"}]
|
|
121
|
+
|
|
122
|
+
res_y = res_y or res
|
|
123
|
+
|
|
92
124
|
if expression_y != "skip":
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
expressions.append({"expr": expression_y, "res": res_y, "prefix": "y"})
|
|
126
|
+
|
|
127
|
+
filters = []
|
|
95
128
|
if filter_x != "skip":
|
|
96
|
-
|
|
129
|
+
filters.append({"expr": filter_x, "res": res, "rate": filter_x_rate})
|
|
130
|
+
|
|
97
131
|
if filter_y != "skip":
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
132
|
+
filters.append({"expr": filter_y, "res": res_y, "rate": filter_y_rate})
|
|
133
|
+
|
|
134
|
+
if extra_filters:
|
|
135
|
+
try:
|
|
136
|
+
extra_filters_combined = zip(extra_filters, extra_filters_res, extra_filters_rate, strict=True)
|
|
137
|
+
except TypeError:
|
|
138
|
+
raise TypeError("Extra filters must have matching rate and resolution.")
|
|
139
|
+
|
|
140
|
+
for expr, filter_res, rate in extra_filters_combined:
|
|
141
|
+
filters.append({"expr": expr, "res": filter_res, "rate": rate})
|
|
142
|
+
|
|
101
143
|
if start_year is not None:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
144
|
+
for expr_dict in expressions:
|
|
145
|
+
expr_dict["expr"] = expr_dict["expr"].replace("{year}", str(start_year))
|
|
146
|
+
|
|
147
|
+
for filter_dict in filters:
|
|
148
|
+
filter_dict["expr"] = filter_dict["expr"].replace("{year}", str(start_year))
|
|
149
|
+
|
|
150
|
+
# this is making request to the server that is being used when doing the initialization
|
|
151
|
+
if not skip_test:
|
|
152
|
+
for expr_dict in expressions:
|
|
153
|
+
test_request = self._generate_test_request(expr_dict["expr"], crs, -1)
|
|
154
|
+
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
155
|
+
|
|
156
|
+
for filter_dict in filters:
|
|
157
|
+
test_request = self._generate_test_request(filter_dict["expr"], crs, -1)
|
|
158
|
+
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
159
|
+
|
|
160
|
+
with open(aoi, 'r') as f:
|
|
112
161
|
aoi_data = json.load(f)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
162
|
+
|
|
163
|
+
await self._client.mass_stats.create_collection(
|
|
164
|
+
collection=name,
|
|
165
|
+
bucket=bucket,
|
|
166
|
+
collection_type="basic"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
task_id_dict = await self._client.mass_stats.training_samples(
|
|
170
|
+
collection=name,
|
|
171
|
+
expressions=expressions,
|
|
172
|
+
filters=filters,
|
|
117
173
|
aoi=aoi_data,
|
|
118
174
|
samples=samples,
|
|
119
175
|
year_range=[start_year, end_year],
|
|
120
176
|
crs=crs,
|
|
121
177
|
tile_size=tile_size,
|
|
122
178
|
res=res,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
server=self._client.url,
|
|
126
|
-
bucket=bucket,
|
|
127
|
-
overwrite=True
|
|
179
|
+
output="nc",
|
|
180
|
+
server=server
|
|
128
181
|
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
status = result[task_id]['status']
|
|
134
|
-
completed = result[task_id].get('completed', 0)
|
|
135
|
-
total = result[task_id].get('total', 1)
|
|
136
|
-
|
|
137
|
-
progress = completed / total if total > 0 else 0
|
|
138
|
-
bar_length = 50
|
|
139
|
-
filled_length = int(bar_length * progress)
|
|
140
|
-
bar = '█' * filled_length + '░' * (bar_length - filled_length)
|
|
141
|
-
percentage = progress * 100
|
|
142
|
-
|
|
143
|
-
self._client.logger.info(f"Job status: {status} [{bar}] {percentage:.1f}% ({completed}/{total})")
|
|
144
|
-
|
|
145
|
-
if status == "Completed":
|
|
146
|
-
self._client.logger.info("Job completed successfully!")
|
|
147
|
-
break
|
|
148
|
-
elif status == "Error":
|
|
149
|
-
self._client.logger.info("Job encountered an error")
|
|
150
|
-
raise Exception(f"Job {task_id} encountered an error")
|
|
151
|
-
|
|
152
|
-
time.sleep(5)
|
|
182
|
+
|
|
183
|
+
task_id = task_id_dict["task_id"]
|
|
184
|
+
|
|
185
|
+
await self._client.mass_stats.track_progress(task_id)
|
|
153
186
|
|
|
154
|
-
task_id = await self._client.mass_stats.start_job(task_id)
|
|
155
|
-
return task_id
|
|
156
|
-
|
|
157
187
|
@require_api_key
|
|
158
188
|
async def _get_url_for_upload_model_and_script(self, expression: str, model_name: str, script_name: str) -> str:
|
|
159
189
|
"""
|
|
@@ -252,7 +282,7 @@ class ModelManagement:
|
|
|
252
282
|
return bucket_name
|
|
253
283
|
|
|
254
284
|
@require_api_key
|
|
255
|
-
async def upload_and_deploy_model(self, model, virtual_dataset_name: str, virtual_product_name: str, input_expression: str, dates_iso8601: list, input_shape: Tuple[int, ...] = None, processing_script_path: Optional[str] = None, model_type: Optional[str] = None):
|
|
285
|
+
async def upload_and_deploy_model(self, model, virtual_dataset_name: str, virtual_product_name: str, input_expression: str, dates_iso8601: list, input_shape: Tuple[int, ...] = None, processing_script_path: Optional[str] = None, model_type: Optional[str] = None, padding: int = 0):
|
|
256
286
|
"""
|
|
257
287
|
Upload a model to the bucket and deploy it.
|
|
258
288
|
Args:
|
|
@@ -264,6 +294,7 @@ class ModelManagement:
|
|
|
264
294
|
input_shape: Shape of input data for ONNX conversion (required for PyTorch models)
|
|
265
295
|
processing_script_path: Path to the processing script, if not provided, no processing will be done
|
|
266
296
|
model_type: The type of the model we want to upload
|
|
297
|
+
padding: Padding value for the dataset (default: 0)
|
|
267
298
|
|
|
268
299
|
Raises:
|
|
269
300
|
APIError: If the API request fails
|
|
@@ -278,12 +309,11 @@ class ModelManagement:
|
|
|
278
309
|
uid = user_info["uid"]
|
|
279
310
|
await self._client.datasets.create_dataset(
|
|
280
311
|
name=virtual_dataset_name,
|
|
281
|
-
collection="terrakio-datasets",
|
|
282
312
|
products=[virtual_product_name],
|
|
283
|
-
path=f"gs://{bucket_name}/{uid}/
|
|
313
|
+
path=f"gs://{bucket_name}/{uid}/models/{virtual_dataset_name}/inference_scripts",
|
|
284
314
|
input=input_expression,
|
|
285
315
|
dates_iso8601=dates_iso8601,
|
|
286
|
-
padding=
|
|
316
|
+
padding=padding
|
|
287
317
|
)
|
|
288
318
|
|
|
289
319
|
@require_api_key
|
|
@@ -1088,15 +1118,32 @@ class ModelManagement:
|
|
|
1088
1118
|
raise ValueError(f"Failed to convert scikit-learn model to ONNX: {str(e)}")
|
|
1089
1119
|
|
|
1090
1120
|
@require_api_key
|
|
1091
|
-
def train_model(
|
|
1092
|
-
self,
|
|
1093
|
-
model_name: str,
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1121
|
+
async def train_model(
|
|
1122
|
+
self,
|
|
1123
|
+
model_name: str,
|
|
1124
|
+
task_type: str,
|
|
1125
|
+
model_category: str,
|
|
1126
|
+
architecture: str,
|
|
1127
|
+
hyperparameters: dict = None,
|
|
1128
|
+
aoi: str = None,
|
|
1129
|
+
expression_x: str = None,
|
|
1130
|
+
filter_x: str = "skip",
|
|
1131
|
+
filter_x_rate: float = 1,
|
|
1132
|
+
expression_y: str = "skip",
|
|
1133
|
+
filter_y: str = "skip",
|
|
1134
|
+
filter_y_rate: float = 1,
|
|
1135
|
+
samples: int = 1000,
|
|
1136
|
+
tile_size: float = 256,
|
|
1137
|
+
crs: str = "epsg:3577",
|
|
1138
|
+
res: float = 10,
|
|
1139
|
+
res_y: float = None,
|
|
1140
|
+
skip_test: bool = False,
|
|
1141
|
+
start_year: int = None,
|
|
1142
|
+
end_year: int = None,
|
|
1143
|
+
server: str = None,
|
|
1144
|
+
extra_filters: list[str] = None,
|
|
1145
|
+
extra_filters_rate: list[float] = None,
|
|
1146
|
+
extra_filters_res: list[float] = None
|
|
1100
1147
|
) -> dict:
|
|
1101
1148
|
"""
|
|
1102
1149
|
Train a model using the external model training API.
|
|
@@ -1107,7 +1154,6 @@ class ModelManagement:
|
|
|
1107
1154
|
task_type (str): The type of ML task (e.g., regression, classification).
|
|
1108
1155
|
model_category (str): The category of model (e.g., random_forest).
|
|
1109
1156
|
architecture (str): The model architecture.
|
|
1110
|
-
region (str): The region identifier.
|
|
1111
1157
|
hyperparameters (dict, optional): Additional hyperparameters for training.
|
|
1112
1158
|
|
|
1113
1159
|
Returns:
|
|
@@ -1116,13 +1162,71 @@ class ModelManagement:
|
|
|
1116
1162
|
Raises:
|
|
1117
1163
|
APIError: If the API request fails
|
|
1118
1164
|
"""
|
|
1165
|
+
expressions = [{"expr": expression_x, "res": res, "prefix": "x"}]
|
|
1166
|
+
|
|
1167
|
+
res_y = res_y or res
|
|
1168
|
+
|
|
1169
|
+
if expression_y != "skip":
|
|
1170
|
+
expressions.append({"expr": expression_y, "res": res_y, "prefix": "y"})
|
|
1171
|
+
|
|
1172
|
+
filters = []
|
|
1173
|
+
if filter_x != "skip":
|
|
1174
|
+
filters.append({"expr": filter_x, "res": res, "rate": filter_x_rate})
|
|
1175
|
+
|
|
1176
|
+
if filter_y != "skip":
|
|
1177
|
+
filters.append({"expr": filter_y, "res": res_y, "rate": filter_y_rate})
|
|
1178
|
+
|
|
1179
|
+
if extra_filters:
|
|
1180
|
+
try:
|
|
1181
|
+
extra_filters_combined = zip(extra_filters, extra_filters_res, extra_filters_rate, strict=True)
|
|
1182
|
+
except TypeError:
|
|
1183
|
+
raise TypeError("Extra filters must have matching rate and resolution.")
|
|
1184
|
+
|
|
1185
|
+
for expr, filter_res, rate in extra_filters_combined:
|
|
1186
|
+
filters.append({"expr": expr, "res": filter_res, "rate": rate})
|
|
1187
|
+
|
|
1188
|
+
if start_year is not None:
|
|
1189
|
+
for expr_dict in expressions:
|
|
1190
|
+
expr_dict["expr"] = expr_dict["expr"].replace("{year}", str(start_year))
|
|
1191
|
+
|
|
1192
|
+
for filter_dict in filters:
|
|
1193
|
+
filter_dict["expr"] = filter_dict["expr"].replace("{year}", str(start_year))
|
|
1194
|
+
|
|
1195
|
+
if not skip_test:
|
|
1196
|
+
for expr_dict in expressions:
|
|
1197
|
+
test_request = self._generate_test_request(expr_dict["expr"], crs, -1)
|
|
1198
|
+
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
1199
|
+
|
|
1200
|
+
for filter_dict in filters:
|
|
1201
|
+
test_request = self._generate_test_request(filter_dict["expr"], crs, -1)
|
|
1202
|
+
await self._client._terrakio_request("POST", "geoquery", json=test_request)
|
|
1203
|
+
|
|
1204
|
+
with open(aoi, 'r') as f:
|
|
1205
|
+
aoi_data = json.load(f)
|
|
1206
|
+
|
|
1207
|
+
await self._client.mass_stats.create_collection(
|
|
1208
|
+
collection=model_name,
|
|
1209
|
+
bucket="terrakio-mass-requests",
|
|
1210
|
+
collection_type="basic"
|
|
1211
|
+
)
|
|
1212
|
+
|
|
1119
1213
|
payload = {
|
|
1120
1214
|
"model_name": model_name,
|
|
1121
|
-
"training_dataset": training_dataset,
|
|
1122
1215
|
"task_type": task_type,
|
|
1123
1216
|
"model_category": model_category,
|
|
1124
1217
|
"architecture": architecture,
|
|
1125
|
-
"
|
|
1126
|
-
"
|
|
1218
|
+
"hyperparameters": hyperparameters,
|
|
1219
|
+
"expressions": expressions,
|
|
1220
|
+
"filters": filters,
|
|
1221
|
+
"aoi": aoi_data,
|
|
1222
|
+
"samples": samples,
|
|
1223
|
+
"year_range": [start_year, end_year],
|
|
1224
|
+
"crs": crs,
|
|
1225
|
+
"tile_size": tile_size,
|
|
1226
|
+
"res": res,
|
|
1227
|
+
"server": server
|
|
1127
1228
|
}
|
|
1128
|
-
|
|
1229
|
+
|
|
1230
|
+
task_id_dict, _ = await self._client._terrakio_request("POST", "models/train", json=payload)
|
|
1231
|
+
|
|
1232
|
+
await self._client.mass_stats.track_progress(task_id_dict["task_id"])
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from typing import Dict, Any, List, Optional
|
|
2
2
|
from ..helper.decorators import require_token, require_api_key, require_auth
|
|
3
|
+
from ..exceptions import UserNotFoundError, GetUserByIdError, GetUserByEmailError, ListUsersError, EditUserError, ResetQuotaError, DeleteUserError, GetUsersByRoleError, RoleDoNotExistError, ChangeRoleError
|
|
3
4
|
|
|
4
5
|
class UserManagement:
|
|
5
6
|
def __init__(self, client):
|
|
6
7
|
self._client = client
|
|
7
8
|
|
|
8
9
|
@require_api_key
|
|
9
|
-
def get_user_by_id(self, id: str) -> Dict[str, Any]:
|
|
10
|
+
async def get_user_by_id(self, id: str) -> Dict[str, Any]:
|
|
10
11
|
"""
|
|
11
12
|
Get user by ID.
|
|
12
13
|
|
|
@@ -17,12 +18,19 @@ class UserManagement:
|
|
|
17
18
|
User information
|
|
18
19
|
|
|
19
20
|
Raises:
|
|
20
|
-
|
|
21
|
+
GetUserByIdError: If the API request fails
|
|
22
|
+
UserNotFoundError: If the user is not found
|
|
21
23
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
response, status = await self._client._terrakio_request("GET", f"admin/users/{id}")
|
|
25
|
+
if status != 200:
|
|
26
|
+
if status == 404:
|
|
27
|
+
raise UserNotFoundError(f"User {id} not found.", status_code = status)
|
|
28
|
+
raise GetUserByIdError(f"Get user by id failed with status {status}", status_code = status)
|
|
29
|
+
else:
|
|
30
|
+
return response
|
|
31
|
+
|
|
24
32
|
@require_api_key
|
|
25
|
-
def get_user_by_email(self, email: str) -> Dict[str, Any]:
|
|
33
|
+
async def get_user_by_email(self, email: str) -> Dict[str, Any]:
|
|
26
34
|
"""
|
|
27
35
|
Get user by email.
|
|
28
36
|
|
|
@@ -33,12 +41,19 @@ class UserManagement:
|
|
|
33
41
|
User information
|
|
34
42
|
|
|
35
43
|
Raises:
|
|
36
|
-
|
|
44
|
+
GetUserByEmailError: If the API request fails
|
|
45
|
+
UserNotFoundError: If the user is not found
|
|
37
46
|
"""
|
|
38
|
-
|
|
47
|
+
response, status = await self._client._terrakio_request("GET", f"admin/users/email/{email}")
|
|
48
|
+
if status != 200:
|
|
49
|
+
if status == 404:
|
|
50
|
+
raise UserNotFoundError(f"User {email} not found.", status_code = status)
|
|
51
|
+
raise GetUserByEmailError(f"Get user by email failed with status {status}", status_code = status)
|
|
52
|
+
else:
|
|
53
|
+
return response
|
|
39
54
|
|
|
40
55
|
@require_api_key
|
|
41
|
-
def list_users(self, substring: Optional[str] = None, uid: bool = False) -> List[Dict[str, Any]]:
|
|
56
|
+
async def list_users(self, substring: Optional[str] = None, uid: bool = False) -> List[Dict[str, Any]]:
|
|
42
57
|
"""
|
|
43
58
|
List users, optionally filtering by a substring.
|
|
44
59
|
|
|
@@ -50,15 +65,19 @@ class UserManagement:
|
|
|
50
65
|
List of users
|
|
51
66
|
|
|
52
67
|
Raises:
|
|
53
|
-
|
|
68
|
+
ListUsersError: If the API request fails
|
|
54
69
|
"""
|
|
55
70
|
params = {"uid": str(uid).lower()}
|
|
56
71
|
if substring:
|
|
57
72
|
params['substring'] = substring
|
|
58
|
-
|
|
73
|
+
response, status = await self._client._terrakio_request("GET", "admin/users", params=params)
|
|
74
|
+
if status != 200:
|
|
75
|
+
raise ListUsersError(f"List users failed with status {status}", status_code = status)
|
|
76
|
+
else:
|
|
77
|
+
return response
|
|
59
78
|
|
|
60
79
|
@require_api_key
|
|
61
|
-
def edit_user(
|
|
80
|
+
async def edit_user(
|
|
62
81
|
self,
|
|
63
82
|
uid: str,
|
|
64
83
|
email: Optional[str] = None,
|
|
@@ -82,7 +101,7 @@ class UserManagement:
|
|
|
82
101
|
Updated user information
|
|
83
102
|
|
|
84
103
|
Raises:
|
|
85
|
-
|
|
104
|
+
EditUserError: If the API request fails
|
|
86
105
|
"""
|
|
87
106
|
payload = {"uid": uid}
|
|
88
107
|
payload_mapping = {
|
|
@@ -95,10 +114,14 @@ class UserManagement:
|
|
|
95
114
|
for key, value in payload_mapping.items():
|
|
96
115
|
if value is not None:
|
|
97
116
|
payload[key] = value
|
|
98
|
-
|
|
117
|
+
response, status = await self._client._terrakio_request("PATCH", "admin/users", json=payload)
|
|
118
|
+
if status != 200:
|
|
119
|
+
raise EditUserError(f"Edit user failed with status {status}", status_code = status)
|
|
120
|
+
else:
|
|
121
|
+
return response
|
|
99
122
|
|
|
100
123
|
@require_api_key
|
|
101
|
-
def reset_quota(self, email: str, quota: Optional[int] = None) -> Dict[str, Any]:
|
|
124
|
+
async def reset_quota(self, email: str, quota: Optional[int] = None) -> Dict[str, Any]:
|
|
102
125
|
"""
|
|
103
126
|
Reset the quota for a user by email.
|
|
104
127
|
|
|
@@ -106,16 +129,20 @@ class UserManagement:
|
|
|
106
129
|
email: The user's email (required)
|
|
107
130
|
quota: The new quota value (optional)
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
Raises:
|
|
133
|
+
ResetQuotaError: If the API request fails
|
|
111
134
|
"""
|
|
112
135
|
payload = {"email": email}
|
|
113
136
|
if quota is not None:
|
|
114
137
|
payload["quota"] = quota
|
|
115
|
-
|
|
138
|
+
response, status = await self._client._terrakio_request("PATCH", f"admin/users/reset_quota/{email}", json=payload)
|
|
139
|
+
if status != 200:
|
|
140
|
+
raise ResetQuotaError(f"Reset quota failed with status {status}", status_code = status)
|
|
141
|
+
else:
|
|
142
|
+
return response
|
|
116
143
|
|
|
117
144
|
@require_api_key
|
|
118
|
-
def delete_user(self, uid: str) -> Dict[str, Any]:
|
|
145
|
+
async def delete_user(self, uid: str) -> Dict[str, Any]:
|
|
119
146
|
"""
|
|
120
147
|
Delete a user by UID.
|
|
121
148
|
|
|
@@ -123,9 +150,67 @@ class UserManagement:
|
|
|
123
150
|
uid: The user's UID (required)
|
|
124
151
|
|
|
125
152
|
Returns:
|
|
126
|
-
|
|
153
|
+
Deleted user information
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
DeleteUserError: If the API request fails
|
|
157
|
+
"""
|
|
158
|
+
response, status = await self._client._terrakio_request("DELETE", f"admin/users/{uid}")
|
|
159
|
+
if status != 200:
|
|
160
|
+
raise DeleteUserError(f"Delete user failed with status {status}", status_code = status)
|
|
161
|
+
else:
|
|
162
|
+
return response
|
|
163
|
+
|
|
164
|
+
@require_api_key
|
|
165
|
+
async def get_users_by_role(self, role: str) -> Dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Get users by role.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
role: The user role to filter by (required)
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Users with the specified role
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
GetUsersByRoleError: If the API request fails
|
|
177
|
+
"""
|
|
178
|
+
response, status = await self._client._terrakio_request("GET", f"admin/users/role?role={role}")
|
|
179
|
+
if status != 200:
|
|
180
|
+
if status == 422:
|
|
181
|
+
raise RoleDoNotExistError(f"Role {role} does not exist", status_code = status)
|
|
182
|
+
raise GetUsersByRoleError(f"Get users by role failed with status {status}", status_code = status)
|
|
183
|
+
else:
|
|
184
|
+
return response
|
|
185
|
+
|
|
186
|
+
@require_api_key
|
|
187
|
+
async def change_role(self, uid: str, role: str, reset_quota: Optional[bool] = None, limit: Optional[int] = None) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Change user role.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
uid: The user's UID to change role for (required)
|
|
193
|
+
role: Role to apply (required)
|
|
194
|
+
reset_quota: Reset user's quota to new role limit (optional)
|
|
195
|
+
limit: Quota limit if role is custom (optional)
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Response from the role change operation
|
|
127
199
|
|
|
128
200
|
Raises:
|
|
129
|
-
|
|
201
|
+
ChangeRoleError: If the API request fails
|
|
130
202
|
"""
|
|
131
|
-
|
|
203
|
+
payload = {"uid": uid, "role": role}
|
|
204
|
+
if reset_quota is not None:
|
|
205
|
+
payload["reset_quota"] = reset_quota
|
|
206
|
+
if limit is not None:
|
|
207
|
+
payload["limit"] = limit
|
|
208
|
+
response, status = await self._client._terrakio_request("PATCH", "admin/users/change_role", json=payload)
|
|
209
|
+
if status != 200:
|
|
210
|
+
if status == 404:
|
|
211
|
+
raise UserNotFoundError(f"User {uid} not found", status_code = status)
|
|
212
|
+
elif status == 422:
|
|
213
|
+
raise RoleDoNotExistError(f"Role {role} does not exist", status_code = status)
|
|
214
|
+
raise ChangeRoleError(f"Change role failed with status {status}", status_code = status)
|
|
215
|
+
else:
|
|
216
|
+
return response
|