terrakio-core 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of terrakio-core might be problematic. Click here for more details.

Files changed (22) hide show
  1. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/PKG-INFO +2 -1
  2. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/pyproject.toml +2 -1
  3. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/__init__.py +1 -1
  4. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/client.py +291 -60
  5. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/dataset_management.py +62 -10
  6. terrakio_core-0.3.2/terrakio_core/decorators.py +18 -0
  7. terrakio_core-0.3.2/terrakio_core/generation/tiles.py +95 -0
  8. terrakio_core-0.3.2/terrakio_core/mass_stats.py +504 -0
  9. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core.egg-info/PKG-INFO +2 -1
  10. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core.egg-info/SOURCES.txt +3 -1
  11. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core.egg-info/requires.txt +1 -0
  12. terrakio_core-0.3.0/terrakio_core/mass_stats.py +0 -262
  13. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/README.md +0 -0
  14. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/setup.cfg +0 -0
  15. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/auth.py +0 -0
  16. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/config.py +0 -0
  17. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/exceptions.py +0 -0
  18. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/group_access_management.py +0 -0
  19. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/space_management.py +0 -0
  20. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core/user_management.py +0 -0
  21. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core.egg-info/dependency_links.txt +0 -0
  22. {terrakio_core-0.3.0 → terrakio_core-0.3.2}/terrakio_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: terrakio-core
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Core components for Terrakio API clients
5
5
  Author-email: Yupeng Chao <yupeng@haizea.com.au>
6
6
  Project-URL: Homepage, https://github.com/HaizeaAnalytics/terrakio-python-api
@@ -22,6 +22,7 @@ Requires-Dist: xarray>=2023.1.0
22
22
  Requires-Dist: shapely>=2.0.0
23
23
  Requires-Dist: geopandas>=0.13.0
24
24
  Requires-Dist: google-cloud-storage>=2.0.0
25
+ Requires-Dist: nest_asyncio
25
26
 
26
27
  # Terrakio Core
27
28
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "terrakio-core"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  authors = [
9
9
  {name = "Yupeng Chao", email = "yupeng@haizea.com.au"},
10
10
  ]
@@ -29,6 +29,7 @@ dependencies = [
29
29
  "shapely>=2.0.0",
30
30
  "geopandas>=0.13.0",
31
31
  "google-cloud-storage>=2.0.0",
32
+ "nest_asyncio",
32
33
  ]
33
34
 
34
35
  [project.urls]
@@ -4,4 +4,4 @@ Terrakio Core
4
4
  Core components for Terrakio API clients.
5
5
  """
6
6
 
7
- __version__ = "0.3.0"
7
+ __version__ = "0.3.2"
@@ -13,6 +13,7 @@ from shapely.geometry import shape, mapping
13
13
  from shapely.geometry.base import BaseGeometry as ShapelyGeometry
14
14
  from google.cloud import storage
15
15
  from .exceptions import APIError, ConfigurationError
16
+ from .decorators import admin_only_params
16
17
  import logging
17
18
  import textwrap
18
19
 
@@ -129,6 +130,7 @@ class BaseClient:
129
130
  "resolution": resolution,
130
131
  **kwargs
131
132
  }
133
+ print("the payload is ", payload)
132
134
  request_url = f"{self.url}/geoquery"
133
135
  for attempt in range(retry + 1):
134
136
  try:
@@ -536,8 +538,8 @@ class BaseClient:
536
538
  def __exit__(self, exc_type, exc_val, exc_tb):
537
539
  self.close()
538
540
 
539
- # Mass Stats methods
540
- def upload_mass_stats(self, name, size, bucket, output, location=None, **kwargs):
541
+ @admin_only_params('location', 'force_loc', 'server')
542
+ def execute_job(self, name, region, output, config, overwrite=False, skip_existing=False, request_json=None, manifest_json=None, location=None, force_loc=None, server="dev-au.terrak.io"):
541
543
  if not self.mass_stats:
542
544
  from terrakio_core.mass_stats import MassStats
543
545
  if not self.url or not self.key:
@@ -548,20 +550,8 @@ class BaseClient:
548
550
  verify=self.verify,
549
551
  timeout=self.timeout
550
552
  )
551
- return self.mass_stats.upload_request(name, size, bucket, output, location, **kwargs)
553
+ return self.mass_stats.execute_job(name, region, output, config, overwrite, skip_existing, request_json, manifest_json, location, force_loc, server)
552
554
 
553
- def start_mass_stats_job(self, task_id):
554
- if not self.mass_stats:
555
- from terrakio_core.mass_stats import MassStats
556
- if not self.url or not self.key:
557
- raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
558
- self.mass_stats = MassStats(
559
- base_url=self.url,
560
- api_key=self.key,
561
- verify=self.verify,
562
- timeout=self.timeout
563
- )
564
- return self.mass_stats.start_job(task_id)
565
555
 
566
556
  def get_mass_stats_task_id(self, name, stage, uid=None):
567
557
  if not self.mass_stats:
@@ -576,7 +566,7 @@ class BaseClient:
576
566
  )
577
567
  return self.mass_stats.get_task_id(name, stage, uid)
578
568
 
579
- def track_mass_stats_job(self, ids=None):
569
+ def track_mass_stats_job(self, ids: Optional[list] = None):
580
570
  if not self.mass_stats:
581
571
  from terrakio_core.mass_stats import MassStats
582
572
  if not self.url or not self.key:
@@ -1049,6 +1039,20 @@ class BaseClient:
1049
1039
  )
1050
1040
  return self.space_management.delete_data_in_path(path, region)
1051
1041
 
1042
+ def start_mass_stats_job(self, task_id):
1043
+ if not self.mass_stats:
1044
+ from terrakio_core.mass_stats import MassStats
1045
+ if not self.url or not self.key:
1046
+ raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1047
+ self.mass_stats = MassStats(
1048
+ base_url=self.url,
1049
+ api_key=self.key,
1050
+ verify=self.verify,
1051
+ timeout=self.timeout
1052
+ )
1053
+ return self.mass_stats.start_job(task_id)
1054
+
1055
+
1052
1056
  def generate_ai_dataset(
1053
1057
  self,
1054
1058
  name: str,
@@ -1118,27 +1122,58 @@ class BaseClient:
1118
1122
  overwrite=True
1119
1123
  )["task_id"]
1120
1124
  print("the task id is ", task_id)
1125
+
1126
+ # Wait for job completion
1127
+ import time
1128
+
1129
+ while True:
1130
+ result = self.track_mass_stats_job(ids=[task_id])
1131
+ status = result[task_id]['status']
1132
+ print(f"Job status: {status}")
1133
+
1134
+ if status == "Completed":
1135
+ break
1136
+ elif status == "Error":
1137
+ raise Exception(f"Job {task_id} encountered an error")
1138
+
1139
+ # Wait 30 seconds before checking again
1140
+ time.sleep(30)
1141
+
1142
+ # print("the result is ", result)
1143
+ # after all the random sample jos are done, we then start the mass stats job
1121
1144
  task_id = self.start_mass_stats_job(task_id)
1122
- print("the task id is ", task_id)
1123
- return task_id
1145
+ # now we hav ethe random sampel
1124
1146
 
1147
+ # print("the task id is ", task_id)
1148
+ return task_id
1125
1149
 
1126
- def train_model(self, model_name: str, training_data: dict) -> dict:
1150
+ def train_model(self, model_name: str, training_dataset: str, task_type: str, model_category: str, architecture: str, region: str, hyperparameters: dict = None) -> dict:
1127
1151
  """
1128
1152
  Train a model using the external model training API.
1129
-
1153
+
1130
1154
  Args:
1131
1155
  model_name (str): The name of the model to train.
1132
- training_data (dict): Dictionary containing training data parameters.
1133
-
1156
+ training_dataset (str): The training dataset identifier.
1157
+ task_type (str): The type of ML task (e.g., regression, classification).
1158
+ model_category (str): The category of model (e.g., random_forest).
1159
+ architecture (str): The model architecture.
1160
+ region (str): The region identifier.
1161
+ hyperparameters (dict, optional): Additional hyperparameters for training.
1162
+
1134
1163
  Returns:
1135
1164
  dict: The response from the model training API.
1136
1165
  """
1137
- endpoint = "https://modeltraining-573248941006.australia-southeast1.run.app/train_model"
1138
1166
  payload = {
1139
1167
  "model_name": model_name,
1140
- "training_data": training_data
1168
+ "training_dataset": training_dataset,
1169
+ "task_type": task_type,
1170
+ "model_category": model_category,
1171
+ "architecture": architecture,
1172
+ "region": region,
1173
+ "hyperparameters": hyperparameters
1141
1174
  }
1175
+ endpoint = f"{self.url.rstrip('/')}/train_model"
1176
+ print("the payload is ", payload)
1142
1177
  try:
1143
1178
  response = self.session.post(endpoint, json=payload, timeout=self.timeout, verify=self.verify)
1144
1179
  if not response.ok:
@@ -1155,35 +1190,163 @@ class BaseClient:
1155
1190
  except requests.RequestException as e:
1156
1191
  raise APIError(f"Model training request failed: {str(e)}")
1157
1192
 
1158
- def deploy_model(self, dataset: str, product:str, model_name:str, input_expression: str, model_training_job_name: str, uid: str, dates_iso8601: list):
1159
- # we have the dataset and we have the product, and we have the model name, we need to create a new json file and add that to the dataset as our virtual dataset
1160
- # upload the script to the bucket, the script should be able to download the model and do the inferencing
1161
- # we need to upload the the json to the to the dataset as our virtual dataset
1162
- # then we do nothing and wait for the user to make the request call to the explorer
1163
- # we should have a uniform script for the random forest deployment
1164
- # create a script for each model
1165
- # upload script to google bucket,
1166
- #
1193
+ # Mass Stats methods
1194
+ def combine_tiles(self,
1195
+ data_name: str,
1196
+ usezarr: bool,
1197
+ overwrite: bool,
1198
+ output : str) -> dict:
1199
+
1200
+ if not self.mass_stats:
1201
+ from terrakio_core.mass_stats import MassStats
1202
+ if not self.url or not self.key:
1203
+ raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1204
+ self.mass_stats = MassStats(
1205
+ base_url=self.url,
1206
+ api_key=self.key,
1207
+ verify=self.verify,
1208
+ timeout=self.timeout
1209
+ )
1210
+ return self.mass_stats.combine_tiles(data_name, usezarr, overwrite, output)
1211
+
1212
+
1213
+
1214
+ def create_dataset_file(
1215
+ self,
1216
+ name: str,
1217
+ aoi: str,
1218
+ expression: str,
1219
+ output: str,
1220
+ tile_size: float = 128.0,
1221
+ crs: str = "epsg:4326",
1222
+ res: float = 0.0001,
1223
+ region: str = "aus",
1224
+ to_crs: str = "epsg:4326",
1225
+ overwrite: bool = True,
1226
+ skip_existing: bool = False,
1227
+ non_interactive: bool = True,
1228
+ usezarr: bool = False,
1229
+ poll_interval: int = 30 # seconds between job status checks
1230
+ ) -> dict:
1231
+
1232
+ from terrakio_core.generation.tiles import tiles
1233
+ import tempfile
1234
+ import time
1235
+
1236
+ body, reqs, groups = tiles(
1237
+ name = name,
1238
+ aoi = aoi,
1239
+ expression = expression,
1240
+ output = output,
1241
+ tile_size = tile_size,
1242
+ crs = crs,
1243
+ res = res,
1244
+ region = region,
1245
+ to_crs = to_crs,
1246
+ fully_cover = True,
1247
+ overwrite = overwrite,
1248
+ skip_existing = skip_existing,
1249
+ non_interactive = non_interactive
1250
+ )
1251
+
1252
+ # Create temp json files before upload
1253
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tempreq:
1254
+ tempreq.write(reqs)
1255
+ tempreqname = tempreq.name
1256
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tempmanifest:
1257
+ tempmanifest.write(groups)
1258
+ tempmanifestname = tempmanifest.name
1259
+
1260
+ if not self.mass_stats:
1261
+ from terrakio_core.mass_stats import MassStats
1262
+ if not self.url or not self.key:
1263
+ raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1264
+ self.mass_stats = MassStats(
1265
+ base_url=self.url,
1266
+ api_key=self.key,
1267
+ verify=self.verify,
1268
+ timeout=self.timeout
1269
+ )
1167
1270
 
1271
+ task_id = self.mass_stats.execute_job(
1272
+ name=body["name"],
1273
+ region=body["region"],
1274
+ output=body["output"],
1275
+ config = {},
1276
+ overwrite=body["overwrite"],
1277
+ skip_existing=body["skip_existing"],
1278
+ request_json=tempreqname,
1279
+ manifest_json=tempmanifestname,
1280
+ )
1281
+
1282
+ ### Start combining tiles when generation-tiles job is done
1283
+ start_time = time.time()
1284
+ status = None
1285
+
1286
+ while True:
1287
+ try:
1288
+ taskid = task_id['task_id']
1289
+ trackinfo = self.mass_stats.track_job([taskid])
1290
+ status = trackinfo[taskid]['status']
1291
+
1292
+ # Check completion states
1293
+ if status == 'Completed':
1294
+ print('Tiles generated successfully!')
1295
+ break
1296
+ elif status in ['Failed', 'Cancelled', 'Error']:
1297
+ raise RuntimeError(f"Job {taskid} failed with status: {status}")
1298
+ else:
1299
+ # Job is still running
1300
+ elapsed_time = time.time() - start_time
1301
+ print(f"Job status: {status} - Elapsed time: {elapsed_time:.1f}s", end='\r')
1302
+
1303
+ # Sleep before next check
1304
+ time.sleep(poll_interval)
1305
+
1306
+
1307
+ except KeyboardInterrupt:
1308
+ print(f"\nInterrupted! Job {taskid} is still running in the background.")
1309
+ raise
1310
+ except Exception as e:
1311
+ print(f"\nError tracking job: {e}")
1312
+ raise
1313
+
1314
+ # Clean up temporary files
1315
+ import os
1316
+ os.unlink(tempreqname)
1317
+ os.unlink(tempmanifestname)
1318
+
1319
+
1320
+ # Start combining tiles
1321
+ if not self.mass_stats:
1322
+ from terrakio_core.mass_stats import MassStats
1323
+ if not self.url or not self.key:
1324
+ raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1325
+ self.mass_stats = MassStats(
1326
+ base_url=self.url,
1327
+ api_key=self.key,
1328
+ verify=self.verify,
1329
+ timeout=self.timeout
1330
+ )
1331
+
1332
+ return self.mass_stats.combine_tiles(body["name"], usezarr, body["overwrite"], body["output"])
1333
+
1334
+ def deploy_model(self, dataset: str, product:str, model_name:str, input_expression: str, model_training_job_name: str, uid: str, dates_iso8601: list):
1168
1335
  script_content = self._generate_script(model_name, product, model_training_job_name, uid)
1169
- # self.create_dataset(collection = "terrakio-datasets", input = input, )
1170
- # we have the script, we need to upload it to the bucket
1171
1336
  script_name = f"{product}.py"
1172
- print("the script content is ", script_content)
1173
- print("the script name is ", script_name)
1174
1337
  self._upload_script_to_bucket(script_content, script_name, model_training_job_name, uid)
1175
- # after uploading the script, we need to create a new virtual dataset
1176
1338
  self._create_dataset(name = dataset, collection = "terrakio-datasets", products = [product], path = f"gs://terrakio-mass-requests/{uid}/{model_training_job_name}/inference_scripts", input = input_expression, dates_iso8601 = dates_iso8601, padding = 0)
1177
1339
 
1178
1340
  def _generate_script(self, model_name: str, product: str, model_training_job_name: str, uid: str) -> str:
1179
1341
  return textwrap.dedent(f'''
1180
1342
  import logging
1181
1343
  from io import BytesIO
1182
- from google.cloud import storage
1183
- from onnxruntime import InferenceSession
1344
+
1184
1345
  import numpy as np
1346
+ import pandas as pd
1185
1347
  import xarray as xr
1186
- import datetime
1348
+ from google.cloud import storage
1349
+ from onnxruntime import InferenceSession
1187
1350
 
1188
1351
  logging.basicConfig(
1189
1352
  level=logging.INFO
@@ -1191,54 +1354,122 @@ class BaseClient:
1191
1354
 
1192
1355
  def get_model():
1193
1356
  logging.info("Loading model for {model_name}...")
1194
-
1357
+
1195
1358
  client = storage.Client()
1196
1359
  bucket = client.get_bucket('terrakio-mass-requests')
1197
1360
  blob = bucket.blob('{uid}/{model_training_job_name}/models/{model_name}.onnx')
1198
-
1361
+
1199
1362
  model = BytesIO()
1200
1363
  blob.download_to_file(model)
1201
1364
  model.seek(0)
1202
-
1365
+
1203
1366
  session = InferenceSession(model.read(), providers=["CPUExecutionProvider"])
1204
1367
  return session
1205
1368
 
1206
1369
  def {product}(*bands, model):
1207
1370
  logging.info("start preparing data")
1371
+ print("the bands are ", bands)
1208
1372
 
1209
- original_shape = bands[0].shape
1210
- logging.info(f"Original shape: {{original_shape}}")
1373
+ data_arrays = list(bands)
1211
1374
 
1212
- transformed_bands = []
1213
- for band in bands:
1214
- transformed_band = band.values.reshape(-1,1)
1215
- transformed_bands.append(transformed_band)
1375
+ print("the data arrays are ", [da.name for da in data_arrays])
1216
1376
 
1217
- input_data = np.hstack(transformed_bands)
1377
+ reference_array = data_arrays[0]
1378
+ original_shape = reference_array.shape
1379
+ logging.info(f"Original shape: {{original_shape}}")
1218
1380
 
1381
+ if 'time' in reference_array.dims:
1382
+ time_coords = reference_array.coords['time']
1383
+ if len(time_coords) == 1:
1384
+ output_timestamp = time_coords[0]
1385
+ else:
1386
+ years = [pd.to_datetime(t).year for t in time_coords.values]
1387
+ unique_years = set(years)
1388
+
1389
+ if len(unique_years) == 1:
1390
+ year = list(unique_years)[0]
1391
+ output_timestamp = pd.Timestamp(f"{{year}}-01-01")
1392
+ else:
1393
+ latest_year = max(unique_years)
1394
+ output_timestamp = pd.Timestamp(f"{{latest_year}}-01-01")
1395
+ else:
1396
+ output_timestamp = pd.Timestamp("1970-01-01")
1397
+
1398
+ averaged_bands = []
1399
+ for data_array in data_arrays:
1400
+ if 'time' in data_array.dims:
1401
+ averaged_band = np.mean(data_array.values, axis=0)
1402
+ logging.info(f"Averaged band from {{data_array.shape}} to {{averaged_band.shape}}")
1403
+ else:
1404
+ averaged_band = data_array.values
1405
+ logging.info(f"No time dimension, shape: {{averaged_band.shape}}")
1406
+
1407
+ flattened_band = averaged_band.reshape(-1, 1)
1408
+ averaged_bands.append(flattened_band)
1409
+
1410
+ input_data = np.hstack(averaged_bands)
1411
+
1219
1412
  logging.info(f"Final input shape: {{input_data.shape}}")
1220
-
1413
+
1221
1414
  output = model.run(None, {{"float_input": input_data.astype(np.float32)}})[0]
1222
-
1415
+
1223
1416
  logging.info(f"Model output shape: {{output.shape}}")
1224
1417
 
1225
- output_reshaped = output.reshape(original_shape)
1418
+ if len(original_shape) >= 3:
1419
+ spatial_shape = original_shape[1:]
1420
+ else:
1421
+ spatial_shape = original_shape
1422
+
1423
+ output_reshaped = output.reshape(spatial_shape)
1424
+
1425
+ output_with_time = np.expand_dims(output_reshaped, axis=0)
1426
+
1427
+ if 'time' in reference_array.dims:
1428
+ spatial_dims = [dim for dim in reference_array.dims if dim != 'time']
1429
+ spatial_coords = {{dim: reference_array.coords[dim] for dim in spatial_dims if dim in reference_array.coords}}
1430
+ else:
1431
+ spatial_dims = list(reference_array.dims)
1432
+ spatial_coords = dict(reference_array.coords)
1433
+
1226
1434
  result = xr.DataArray(
1227
- data=output_reshaped,
1228
- dims=bands[0].dims,
1229
- coords=bands[0].coords
1435
+ data=output_with_time.astype(np.float32),
1436
+ dims=['time'] + list(spatial_dims),
1437
+ coords={
1438
+ 'time': [output_timestamp.values],
1439
+ 'y': spatial_coords['y'].values,
1440
+ 'x': spatial_coords['x'].values
1441
+ }
1230
1442
  )
1231
-
1232
1443
  return result
1233
1444
  ''').strip()
1234
-
1445
+
1235
1446
  def _upload_script_to_bucket(self, script_content: str, script_name: str, model_training_job_name: str, uid: str):
1236
1447
  """Upload the generated script to Google Cloud Storage"""
1237
1448
 
1238
1449
  client = storage.Client()
1239
1450
  bucket = client.get_bucket('terrakio-mass-requests')
1240
1451
  blob = bucket.blob(f'{uid}/{model_training_job_name}/inference_scripts/{script_name}')
1241
- # the first layer is the uid, the second layer is the model training job name
1242
1452
  blob.upload_from_string(script_content, content_type='text/plain')
1243
1453
  logging.info(f"Script uploaded successfully to {uid}/{model_training_job_name}/inference_scripts/{script_name}")
1244
1454
 
1455
+
1456
+
1457
+
1458
+ def download_file_to_path(self, job_name, stage, file_name, output_path):
1459
+ if not self.mass_stats:
1460
+ from terrakio_core.mass_stats import MassStats
1461
+ if not self.url or not self.key:
1462
+ raise ConfigurationError("Mass Stats client not initialized. Make sure API URL and key are set.")
1463
+ self.mass_stats = MassStats(
1464
+ base_url=self.url,
1465
+ api_key=self.key,
1466
+ verify=self.verify,
1467
+ timeout=self.timeout
1468
+ )
1469
+
1470
+ # fetch bucket info based on job name and stage
1471
+
1472
+ taskid = self.mass_stats.get_task_id(job_name, stage).get('task_id')
1473
+ trackinfo = self.mass_stats.track_job([taskid])
1474
+ bucket = trackinfo[taskid]['bucket']
1475
+ return self.mass_stats.download_file(job_name, bucket, file_name, output_path)
@@ -83,10 +83,63 @@ class DatasetManagement:
83
83
  except requests.RequestException as e:
84
84
  raise APIError(f"Request failed: {str(e)}")
85
85
 
86
+ # def create_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs) -> Dict[str, Any]:
87
+ # """
88
+ # Create a new dataset.
89
+
90
+ # Args:
91
+ # name: Name of the dataset (required)
92
+ # collection: Dataset collection (default: 'terrakio-datasets')
93
+ # **kwargs: Additional dataset parameters including:
94
+ # - products: List of products
95
+ # - dates_iso8601: List of dates
96
+ # - bucket: Storage bucket
97
+ # - path: Storage path
98
+ # - data_type: Data type
99
+ # - no_data: No data value
100
+ # - l_max: Maximum level
101
+ # - y_size: Y size
102
+ # - x_size: X size
103
+ # - proj4: Projection string
104
+ # - abstract: Dataset abstract
105
+ # - geotransform: Geotransform parameters
106
+
107
+ # Returns:
108
+ # Created dataset information
109
+
110
+ # Raises:
111
+ # APIError: If the API request fails
112
+ # """
113
+ # endpoint = f"{self.api_url}/datasets"
114
+ # params = {"collection": collection}
115
+ # # Create payload with required name parameter
116
+ # payload = {"name": name}
117
+
118
+ # # Add optional parameters if provided
119
+ # for param in ["products", "dates_iso8601", "bucket", "path", "data_type",
120
+ # "no_data", "l_max", "y_size", "x_size", "proj4", "abstract", "geotransform", "input"]:
121
+ # if param in kwargs:
122
+ # payload[param] = kwargs[param]
123
+
124
+ # try:
125
+ # response = self.session.post(
126
+ # endpoint,
127
+ # params=params,
128
+ # json=payload,
129
+ # timeout=self.timeout,
130
+ # verify=self.verify
131
+ # )
132
+
133
+ # if not response.ok:
134
+ # raise APIError(f"API request failed: {response.status_code} {response.reason}")
135
+ # return response.json()
136
+ # except requests.RequestException as e:
137
+ # raise APIError(f"Request failed: {str(e)}")
138
+
86
139
  def create_dataset(self, name: str, collection: str = "terrakio-datasets", **kwargs) -> Dict[str, Any]:
87
140
  """
88
141
  Create a new dataset.
89
-
142
+
90
143
  Args:
91
144
  name: Name of the dataset (required)
92
145
  collection: Dataset collection (default: 'terrakio-datasets')
@@ -103,24 +156,23 @@ class DatasetManagement:
103
156
  - proj4: Projection string
104
157
  - abstract: Dataset abstract
105
158
  - geotransform: Geotransform parameters
106
-
159
+ - padding: Padding value
160
+
107
161
  Returns:
108
162
  Created dataset information
109
-
163
+
110
164
  Raises:
111
165
  APIError: If the API request fails
112
166
  """
113
167
  endpoint = f"{self.api_url}/datasets"
114
168
  params = {"collection": collection}
115
- # Create payload with required name parameter
116
169
  payload = {"name": name}
117
-
118
- # Add optional parameters if provided
119
- for param in ["products", "dates_iso8601", "bucket", "path", "data_type",
120
- "no_data", "l_max", "y_size", "x_size", "proj4", "abstract", "geotransform", "input"]:
170
+
171
+ for param in ["products", "dates_iso8601", "bucket", "path", "data_type",
172
+ "no_data", "l_max", "y_size", "x_size", "proj4", "abstract", "geotransform", "input", "padding"]:
121
173
  if param in kwargs:
122
174
  payload[param] = kwargs[param]
123
-
175
+
124
176
  try:
125
177
  response = self.session.post(
126
178
  endpoint,
@@ -129,7 +181,7 @@ class DatasetManagement:
129
181
  timeout=self.timeout,
130
182
  verify=self.verify
131
183
  )
132
-
184
+
133
185
  if not response.ok:
134
186
  raise APIError(f"API request failed: {response.status_code} {response.reason}")
135
187
  return response.json()
@@ -0,0 +1,18 @@
1
+ # terrakio_core/decorators.py
2
+ def admin_only_params(*restricted_params):
3
+ """
4
+ Decorator factory for restricting method parameters to admin users only.
5
+ """
6
+ def decorator(func):
7
+ def wrapper(self, *args, **kwargs):
8
+ if hasattr(self, '_is_admin') and self._is_admin:
9
+ return func(self, *args, **kwargs)
10
+
11
+ admin_params_used = set(kwargs.keys()) & set(restricted_params)
12
+ if admin_params_used:
13
+ raise PermissionError(f"Parameters {admin_params_used} are only available to admin users")
14
+
15
+ filtered_kwargs = {k: v for k, v in kwargs.items() if k not in restricted_params}
16
+ return func(self, *args, **filtered_kwargs)
17
+ return wrapper
18
+ return decorator