terrakio-core 0.4.98.1b4__py3-none-any.whl → 0.4.98.1b6__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.

@@ -1,34 +1,34 @@
1
+ import json
1
2
  import os
2
3
  import time
3
- import typer
4
4
  from typing import Dict, Any, Optional, List
5
- from dateutil import parser
6
5
 
6
+ import aiohttp
7
+ import typer
8
+ from dateutil import parser
7
9
  from rich.console import Console
8
10
  from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
9
11
 
10
12
  from ..exceptions import (
13
+ CancelAllTasksError,
14
+ CancelCollectionTasksError,
15
+ CancelTaskError,
16
+ CollectionAlreadyExistsError,
17
+ CollectionNotFoundError,
11
18
  CreateCollectionError,
19
+ DeleteCollectionError,
20
+ DownloadFilesError,
12
21
  GetCollectionError,
13
- ListCollectionsError,
14
- CollectionNotFoundError,
15
- CollectionAlreadyExistsError,
22
+ GetTaskError,
16
23
  InvalidCollectionTypeError,
17
- DeleteCollectionError,
24
+ ListCollectionsError,
18
25
  ListTasksError,
19
- UploadRequestsError,
20
- UploadArtifactsError,
21
- GetTaskError,
22
26
  TaskNotFoundError,
23
- DownloadFilesError,
24
- CancelTaskError,
25
- CancelCollectionTasksError,
26
- CancelAllTasksError,
27
+ UploadArtifactsError,
28
+ UploadRequestsError,
27
29
  )
28
30
  from ..helper.decorators import require_api_key
29
31
 
30
- import aiohttp # Make sure this is imported at the top
31
-
32
32
 
33
33
  class MassStats:
34
34
  def __init__(self, client):
@@ -156,6 +156,7 @@ class MassStats:
156
156
 
157
157
  self.console.print(f"[bold green]All {number_of_jobs} jobs finished![/bold green]")
158
158
 
159
+ # below are functions related to collection
159
160
  @require_api_key
160
161
  async def create_collection(
161
162
  self,
@@ -202,45 +203,6 @@ class MassStats:
202
203
 
203
204
  return response
204
205
 
205
- @require_api_key
206
- async def delete_collection(
207
- self,
208
- collection: str,
209
- full: Optional[bool] = False,
210
- outputs: Optional[list] = [],
211
- data: Optional[bool] = False
212
- ) -> Dict[str, Any]:
213
- """
214
- Delete a collection by name.
215
-
216
- Args:
217
- collection: The name of the collection to delete (required)
218
- full: Delete the full collection (optional, defaults to False)
219
- outputs: Specific output folders to delete (optional, defaults to empty list)
220
- data: Whether to delete raw data (xdata folder) (optional, defaults to False)
221
-
222
- Returns:
223
- API response as a dictionary confirming deletion
224
-
225
- Raises:
226
- CollectionNotFoundError: If the collection is not found
227
- DeleteCollectionError: If the API request fails due to unknown reasons
228
- """
229
- payload = {
230
- "full": full,
231
- "outputs": outputs,
232
- "data": data
233
- }
234
-
235
- response, status = await self._client._terrakio_request("DELETE", f"collections/{collection}", json=payload)
236
-
237
- if status != 200:
238
- if status == 404:
239
- raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
240
- raise DeleteCollectionError(f"Delete collection failed with status {status}", status_code=status)
241
-
242
- return response
243
-
244
206
  @require_api_key
245
207
  async def get_collection(self, collection: str) -> Dict[str, Any]:
246
208
  """
@@ -304,95 +266,72 @@ class MassStats:
304
266
  return response
305
267
 
306
268
  @require_api_key
307
- async def list_tasks(
308
- self,
309
- limit: Optional[int] = 10,
310
- page: Optional[int] = 0
311
- ) -> List[Dict[str, Any]]:
269
+ async def delete_collection(
270
+ self,
271
+ collection: str,
272
+ full: Optional[bool] = False,
273
+ outputs: Optional[list] = [],
274
+ data: Optional[bool] = False
275
+ ) -> Dict[str, Any]:
312
276
  """
313
- List tasks for the current user.
277
+ Delete a collection by name.
314
278
 
315
279
  Args:
316
- limit: Number of tasks to return (optional, defaults to 10)
317
- page: Page number (optional, defaults to 0)
318
-
280
+ collection: The name of the collection to delete (required)
281
+ full: Delete the full collection (optional, defaults to False)
282
+ outputs: Specific output folders to delete (optional, defaults to empty list)
283
+ data: Whether to delete raw data (xdata folder) (optional, defaults to False)
284
+
319
285
  Returns:
320
- API response as a list of dictionaries containing task information
286
+ API response as a dictionary confirming deletion
321
287
 
322
288
  Raises:
323
- ListTasksError: If the API request fails due to unknown reasons
289
+ CollectionNotFoundError: If the collection is not found
290
+ DeleteCollectionError: If the API request fails due to unknown reasons
324
291
  """
325
- params = {
326
- "limit": limit,
327
- "page": page
292
+ payload = {
293
+ "full": full,
294
+ "outputs": outputs,
295
+ "data": data
328
296
  }
329
- response, status = await self._client._terrakio_request("GET", "tasks", params=params)
330
-
331
- if status != 200:
332
- raise ListTasksError(f"List tasks failed with status {status}", status_code=status)
333
-
334
- return response
335
-
336
- @require_api_key
337
- async def upload_requests(
338
- self,
339
- collection: str
340
- ) -> Dict[str, Any]:
341
- """
342
- Retrieve signed url to upload requests for a collection.
343
-
344
- Args:
345
- collection: Name of collection
346
297
 
347
- Returns:
348
- API response as a dictionary containing the upload URL
349
-
350
- Raises:
351
- CollectionNotFoundError: If the collection is not found
352
- UploadRequestsError: If the API request fails due to unknown reasons
353
- """
354
- response, status = await self._client._terrakio_request("GET", f"collections/{collection}/upload/requests")
298
+ response, status = await self._client._terrakio_request("DELETE", f"collections/{collection}", json=payload)
355
299
 
356
300
  if status != 200:
357
301
  if status == 404:
358
302
  raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
359
- raise UploadRequestsError(f"Upload requests failed with status {status}", status_code=status)
303
+ raise DeleteCollectionError(f"Delete collection failed with status {status}", status_code=status)
360
304
 
361
305
  return response
362
306
 
307
+ # below are functions related to tasks
363
308
  @require_api_key
364
- async def upload_artifacts(
309
+ async def list_tasks(
365
310
  self,
366
- collection: str,
367
- file_type: str,
368
- compressed: Optional[bool] = True
369
- ) -> Dict[str, Any]:
311
+ limit: Optional[int] = 10,
312
+ page: Optional[int] = 0
313
+ ) -> List[Dict[str, Any]]:
370
314
  """
371
- Retrieve signed url to upload artifact file to a collection.
315
+ List tasks for the current user.
372
316
 
373
317
  Args:
374
- collection: Name of collection
375
- file_type: The extension of the file
376
- compressed: Whether to compress the file using gzip or not (defaults to True)
318
+ limit: Number of tasks to return (optional, defaults to 10)
319
+ page: Page number (optional, defaults to 0)
377
320
 
378
321
  Returns:
379
- API response as a dictionary containing the upload URL
380
-
322
+ API response as a list of dictionaries containing task information
323
+
381
324
  Raises:
382
- CollectionNotFoundError: If the collection is not found
383
- UploadArtifactsError: If the API request fails due to unknown reasons
325
+ ListTasksError: If the API request fails due to unknown reasons
384
326
  """
385
327
  params = {
386
- "file_type": file_type,
387
- "compressed": str(compressed).lower(),
328
+ "limit": limit,
329
+ "page": page
388
330
  }
389
-
390
- response, status = await self._client._terrakio_request("GET", f"collections/{collection}/upload", params=params)
331
+ response, status = await self._client._terrakio_request("GET", "tasks", params=params)
391
332
 
392
333
  if status != 200:
393
- if status == 404:
394
- raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
395
- raise UploadArtifactsError(f"Upload artifacts failed with status {status}", status_code=status)
334
+ raise ListTasksError(f"List tasks failed with status {status}", status_code=status)
396
335
 
397
336
  return response
398
337
 
@@ -424,205 +363,114 @@ class MassStats:
424
363
  return response
425
364
 
426
365
  @require_api_key
427
- async def generate_data(
366
+ async def cancel_task(
428
367
  self,
429
- collection: str,
430
- output: str,
431
- skip_existing: Optional[bool] = True,
432
- force_loc: Optional[bool] = None,
433
- server: Optional[str] = None
368
+ task_id: str
434
369
  ) -> Dict[str, Any]:
435
370
  """
436
- Generate data for a collection.
371
+ Cancel a task by task ID.
437
372
 
438
373
  Args:
439
- collection: Name of collection
440
- output: Output type (str)
441
- force_loc: Write data directly to the cloud under this folder
442
- skip_existing: Skip existing data
443
- server: Server to use
444
-
374
+ task_id: ID of task to cancel
375
+
445
376
  Returns:
446
377
  API response as a dictionary containing task information
447
378
 
448
379
  Raises:
449
- CollectionNotFoundError: If the collection is not found
450
- GetTaskError: If the API request fails due to unknown reasons
380
+ TaskNotFoundError: If the task is not found
381
+ CancelTaskError: If the API request fails due to unknown reasons
451
382
  """
452
- payload = {"output": output, "skip_existing": skip_existing}
453
-
454
- if force_loc is not None:
455
- payload["force_loc"] = force_loc
456
- if server is not None:
457
- payload["server"] = server
383
+ response, status = await self._client._terrakio_request("POST", f"tasks/cancel/{task_id}")
458
384
 
459
- response, status = await self._client._terrakio_request("POST", f"collections/{collection}/generate_data", json=payload)
460
-
461
385
  if status != 200:
462
386
  if status == 404:
463
- raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
464
- raise GetTaskError(f"Generate data failed with status {status}", status_code=status)
465
-
387
+ raise TaskNotFoundError(f"Task {task_id} not found", status_code=status)
388
+ raise CancelTaskError(f"Cancel task failed with status {status}", status_code=status)
389
+
466
390
  return response
467
391
 
468
392
  @require_api_key
469
- async def training_samples(
393
+ async def cancel_collection_tasks(
470
394
  self,
471
- collection: str,
472
- expressions: list[str],
473
- filters: list[str],
474
- aoi: dict,
475
- samples: int,
476
- crs: str,
477
- tile_size: int,
478
- res: float,
479
- output: str,
480
- year_range: Optional[list[int]] = None,
481
- server: Optional[str] = None
395
+ collection: str
482
396
  ) -> Dict[str, Any]:
483
397
  """
484
- Generate training samples for a collection.
398
+ Cancel all tasks for a collection.
485
399
 
486
400
  Args:
487
401
  collection: Name of collection
488
- expressions: List of expressions for each sample
489
- filters: Expressions to filter sample areas
490
- aoi: AOI to sample from (geojson dict)
491
- samples: Number of samples to generate
492
- crs: CRS of AOI
493
- tile_size: Pixel width and height of samples
494
- res: Resolution of samples
495
- output: Sample output type
496
- year_range: Optional year range filter
497
- server: Server to use
498
402
 
499
403
  Returns:
500
- API response as a dictionary containing task information
404
+ API response as a dictionary containing task information for the collection
501
405
 
502
406
  Raises:
503
407
  CollectionNotFoundError: If the collection is not found
504
- GetTaskError: If the API request fails due to unknown reasons
408
+ CancelCollectionTasksError: If the API request fails due to unknown reasons
505
409
  """
506
- payload = {
507
- "expressions": expressions,
508
- "filters": filters,
509
- "aoi": aoi,
510
- "samples": samples,
511
- "crs": crs,
512
- "tile_size": tile_size,
513
- "res": res,
514
- "output": output
515
- }
516
-
517
- if year_range is not None:
518
- payload["year_range"] = year_range
519
- if server is not None:
520
- payload["server"] = server
410
+ response, status = await self._client._terrakio_request("POST", f"collections/{collection}/cancel")
521
411
 
522
- response, status = await self._client._terrakio_request("POST", f"collections/{collection}/training_samples", json=payload)
523
-
524
412
  if status != 200:
525
413
  if status == 404:
526
414
  raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
527
- raise GetTaskError(f"Training sample failed with status {status}", status_code=status)
528
-
415
+ raise CancelCollectionTasksError(f"Cancel collection tasks failed with status {status}", status_code=status)
416
+
529
417
  return response
530
418
 
531
- # @require_api_key
532
- # async def post_processing(
533
- # self,
534
- # collection: str,
535
- # folder: str,
536
- # consumer: str
537
- # ) -> Dict[str, Any]:
538
- # """
539
- # Run post processing for a collection.
540
-
541
- # Args:
542
- # collection: Name of collection
543
- # folder: Folder to store output
544
- # consumer: Post processing script
545
-
546
- # Returns:
547
- # API response as a dictionary containing task information
548
-
549
- # Raises:
550
- # CollectionNotFoundError: If the collection is not found
551
- # GetTaskError: If the API request fails due to unknown reasons
552
- # """
553
- # # payload = {
554
- # # "folder": folder,
555
- # # "consumer": consumer
556
- # # }
557
- # # we have the consumer as a string, we need to read in the file and then pass in the content
558
- # with open(consumer, 'rb') as f:
559
- # files = {
560
- # 'consumer': ('consumer.py', f.read(), 'text/plain')
561
- # }
562
- # data = {
563
- # 'folder': folder
564
- # }
565
-
566
- # # response, status = await self._client._terrakio_request("POST", f"collections/{collection}/post_process", json=payload)
567
- # response, status = await self._client._terrakio_request(
568
- # "POST",
569
- # f"collections/{collection}/post_process",
570
- # files=files,
571
- # data=data
572
- # )
573
- # if status != 200:
574
- # if status == 404:
575
- # raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
576
- # raise GetTaskError(f"Post processing failed with status {status}", status_code=status)
577
-
578
- # return response
419
+ @require_api_key
420
+ async def cancel_all_tasks(
421
+ self
422
+ ) -> Dict[str, Any]:
423
+ """
424
+ Cancel all tasks for the current user.
425
+
426
+ Returns:
427
+ API response as a dictionary containing task information for all tasks
428
+
429
+ Raises:
430
+ CancelAllTasksError: If the API request fails due to unknown reasons
431
+ """
432
+ response, status = await self._client._terrakio_request("POST", "tasks/cancel")
433
+
434
+ if status != 200:
435
+ raise CancelAllTasksError(f"Cancel all tasks failed with status {status}", status_code=status)
579
436
 
437
+ return response
580
438
 
439
+ # below are functions related to the web ui and needs to be deleted in the future
581
440
  @require_api_key
582
- async def post_processing(
441
+ async def upload_artifacts(
583
442
  self,
584
443
  collection: str,
585
- folder: str,
586
- consumer: str
444
+ file_type: str,
445
+ compressed: Optional[bool] = True
587
446
  ) -> Dict[str, Any]:
588
447
  """
589
- Run post processing for a collection.
448
+ Retrieve signed url to upload artifact file to a collection.
590
449
 
591
450
  Args:
592
451
  collection: Name of collection
593
- folder: Folder to store output
594
- consumer: Path to post processing script
595
-
452
+ file_type: The extension of the file
453
+ compressed: Whether to compress the file using gzip or not (defaults to True)
454
+
596
455
  Returns:
597
- API response as a dictionary containing task information
456
+ API response as a dictionary containing the upload URL
598
457
 
599
458
  Raises:
600
459
  CollectionNotFoundError: If the collection is not found
601
- GetTaskError: If the API request fails due to unknown reasons
460
+ UploadArtifactsError: If the API request fails due to unknown reasons
602
461
  """
603
- # Read file and build multipart form data
604
- with open(consumer, 'rb') as f:
605
- form = aiohttp.FormData()
606
- form.add_field('folder', folder) # Add text field
607
- form.add_field(
608
- 'consumer', # Field name
609
- f.read(), # File content
610
- filename='consumer.py', # Filename
611
- content_type='text/x-python' # MIME type
612
- )
613
-
614
- # Send using data= with FormData object (NOT files=)
615
- response, status = await self._client._terrakio_request(
616
- "POST",
617
- f"collections/{collection}/post_process",
618
- data=form # ✅ Pass FormData as data
619
- )
620
-
462
+ params = {
463
+ "file_type": file_type,
464
+ "compressed": str(compressed).lower(),
465
+ }
466
+
467
+ response, status = await self._client._terrakio_request("GET", f"collections/{collection}/upload", params=params)
468
+
621
469
  if status != 200:
622
470
  if status == 404:
623
471
  raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
624
- raise GetTaskError(f"Post processing failed with status {status}", status_code=status)
625
-
472
+ raise UploadArtifactsError(f"Upload artifacts failed with status {status}", status_code=status)
473
+
626
474
  return response
627
475
 
628
476
  @require_api_key
@@ -716,120 +564,425 @@ class MassStats:
716
564
 
717
565
  return response
718
566
 
567
+ async def _upload_requests(
568
+ self,
569
+ collection: str
570
+ ) -> Dict[str, Any]:
571
+ """
572
+ Retrieve signed url to upload requests for a collection.
573
+
574
+ Args:
575
+ collection: Name of collection
576
+
577
+ Returns:
578
+ API response as a dictionary containing the upload URL
579
+
580
+ Raises:
581
+ CollectionNotFoundError: If the collection is not found
582
+ UploadRequestsError: If the API request fails due to unknown reasons
583
+ """
584
+ response, status = await self._client._terrakio_request("GET", f"collections/{collection}/upload/requests")
585
+
586
+ if status != 200:
587
+ if status == 404:
588
+ raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
589
+ raise UploadRequestsError(f"Upload requests failed with status {status}", status_code=status)
590
+
591
+ return response
592
+
593
+ @require_api_key
594
+ async def _upload_file(self, file_path: str, url: str, use_gzip: bool = True):
595
+ """
596
+ Helper method to upload a JSON file to a signed URL.
597
+
598
+ Args:
599
+ file_path: Path to the JSON file
600
+ url: Signed URL to upload to
601
+ use_gzip: Whether to compress the file with gzip
602
+ """
603
+ try:
604
+ with open(file_path, 'r') as file:
605
+ json_data = json.load(file)
606
+ except FileNotFoundError:
607
+ raise FileNotFoundError(f"JSON file not found: {file_path}")
608
+ except json.JSONDecodeError as e:
609
+ raise ValueError(f"Invalid JSON in file {file_path}: {e}")
610
+
611
+ return await self._upload_json_data(json_data, url, use_gzip)
612
+
613
+ @require_api_key
614
+ async def _upload_json_data(self, json_data, url: str, use_gzip: bool = True):
615
+ """
616
+ Helper method to upload JSON data directly to a signed URL.
617
+
618
+ Args:
619
+ json_data: JSON data (dict or list) to upload
620
+ url: Signed URL to upload to
621
+ use_gzip: Whether to compress the data with gzip
622
+ """
623
+ if hasattr(json, 'dumps') and 'ignore_nan' in json.dumps.__code__.co_varnames:
624
+ dumps_kwargs = {'ignore_nan': True}
625
+ else:
626
+ dumps_kwargs = {}
627
+
628
+ if use_gzip:
629
+ import gzip
630
+ body = gzip.compress(json.dumps(json_data, **dumps_kwargs).encode('utf-8'))
631
+ headers = {
632
+ 'Content-Type': 'application/json',
633
+ 'Content-Encoding': 'gzip'
634
+ }
635
+ else:
636
+ body = json.dumps(json_data, **dumps_kwargs).encode('utf-8')
637
+ headers = {
638
+ 'Content-Type': 'application/json'
639
+ }
640
+
641
+ response = await self._client._regular_request("PUT", url, data=body, headers=headers)
642
+ return response
643
+
719
644
  @require_api_key
720
- async def download_files(
645
+ async def generate_data(
721
646
  self,
722
647
  collection: str,
723
- file_type: str,
724
- page: Optional[int] = 0,
725
- page_size: Optional[int] = 100,
726
- folder: Optional[str] = None
648
+ file_path: str,
649
+ output: str,
650
+ skip_existing: Optional[bool] = True,
651
+ force_loc: Optional[bool] = None,
652
+ server: Optional[str] = None
727
653
  ) -> Dict[str, Any]:
728
654
  """
729
- Get list of signed urls to download files in collection.
655
+ Generate data for a collection.
730
656
 
731
657
  Args:
732
658
  collection: Name of collection
733
- file_type: Whether to return raw or processed (after post processing) files
734
- page: Page number (optional, defaults to 0)
735
- page_size: Number of files to return per page (optional, defaults to 100)
736
- folder: If processed file type, which folder to download files from (optional)
737
-
659
+ file_path: Path to the file to upload
660
+ output: Output type (str)
661
+ force_loc: Write data directly to the cloud under this folder
662
+ skip_existing: Skip existing data
663
+ server: Server to use
664
+
738
665
  Returns:
739
- API response as a dictionary containing list of download URLs
666
+ API response as a dictionary containing task information
740
667
 
741
668
  Raises:
742
669
  CollectionNotFoundError: If the collection is not found
743
- DownloadFilesError: If the API request fails due to unknown reasons
670
+ GetTaskError: If the API request fails due to unknown reasons
744
671
  """
745
- params = {"file_type": file_type}
672
+ await self.create_collection(
673
+ collection = collection
674
+ )
675
+
676
+ upload_urls = await self._upload_requests(
677
+ collection = collection
678
+ )
746
679
 
747
- if page is not None:
748
- params["page"] = page
749
- if page_size is not None:
750
- params["page_size"] = page_size
751
- if folder is not None:
752
- params["folder"] = folder
680
+ url = upload_urls['url']
753
681
 
754
- response, status = await self._client._terrakio_request("GET", f"collections/{collection}/download", params=params)
682
+ await self._upload_file(file_path, url)
683
+
684
+ payload = {"output": output, "skip_existing": skip_existing}
685
+
686
+ if force_loc is not None:
687
+ payload["force_loc"] = force_loc
688
+ if server is not None:
689
+ payload["server"] = server
690
+
691
+ response, status = await self._client._terrakio_request("POST", f"collections/{collection}/generate_data", json=payload)
755
692
 
756
693
  if status != 200:
757
694
  if status == 404:
758
695
  raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
759
- raise DownloadFilesError(f"Download files failed with status {status}", status_code=status)
696
+ raise GetTaskError(f"Generate data failed with status {status}", status_code=status)
760
697
 
761
698
  return response
762
699
 
763
700
  @require_api_key
764
- async def cancel_task(
701
+ async def post_processing(
765
702
  self,
766
- task_id: str
767
- ):
703
+ collection: str,
704
+ folder: str,
705
+ consumer: str
706
+ ) -> Dict[str, Any]:
768
707
  """
769
- Cancel a task by task ID.
708
+ Run post processing for a collection.
770
709
 
771
710
  Args:
772
- task_id: ID of task to cancel
711
+ collection: Name of collection
712
+ folder: Folder to store output
713
+ consumer: Path to post processing script
773
714
 
774
715
  Returns:
775
716
  API response as a dictionary containing task information
776
717
 
777
718
  Raises:
778
- TaskNotFoundError: If the task is not found
779
- CancelTaskError: If the API request fails due to unknown reasons
719
+ CollectionNotFoundError: If the collection is not found
720
+ GetTaskError: If the API request fails due to unknown reasons
780
721
  """
781
- response, status = await self._client._terrakio_request("POST", f"tasks/cancel/{task_id}")
722
+ await self.create_collection(
723
+ collection = collection
724
+ )
725
+
726
+ with open(consumer, 'rb') as f:
727
+ form = aiohttp.FormData()
728
+ form.add_field('folder', folder)
729
+ form.add_field(
730
+ 'consumer',
731
+ f.read(),
732
+ filename='consumer.py',
733
+ content_type='text/x-python'
734
+ )
735
+
736
+ response, status = await self._client._terrakio_request(
737
+ "POST",
738
+ f"collections/{collection}/post_process",
739
+ data=form
740
+ )
741
+
782
742
  if status != 200:
783
743
  if status == 404:
784
- raise TaskNotFoundError(f"Task {task_id} not found", status_code=status)
785
- raise CancelTaskError(f"Cancel task failed with status {status}", status_code=status)
786
-
744
+ raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
745
+ raise GetTaskError(f"Post processing failed with status {status}", status_code=status)
746
+
787
747
  return response
788
748
 
789
749
  @require_api_key
790
- async def cancel_collection_tasks(
750
+ async def training_samples(
791
751
  self,
792
- collection: str
793
- ):
794
- """
795
- Cancel all tasks for a collection.
752
+ name: str,
753
+ aoi: str,
754
+ expression_x: str,
755
+ filter_x: str = "skip",
756
+ filter_x_rate: float = 1,
757
+ expression_y: str = "skip",
758
+ filter_y: str = "skip",
759
+ filter_y_rate: float = 1,
760
+ samples: int = 1000,
761
+ tile_size: float = 256,
762
+ crs: str = "epsg:3577",
763
+ res: float = 10,
764
+ res_y: float = None,
765
+ skip_test: bool = False,
766
+ start_year: int = None,
767
+ end_year: int = None,
768
+ bucket: str = None,
769
+ server: str = None,
770
+ extra_filters: list[str] = None,
771
+ extra_filters_rate: list[float] = None,
772
+ extra_filters_res: list[float] = None
773
+ ) -> dict:
774
+ """
775
+ Generate an AI dataset using specified parameters.
796
776
 
797
777
  Args:
798
- collection: Name of collection
778
+ name: Name of the collection to create
779
+ aoi: Path to GeoJSON file containing area of interest
780
+ expression_x: Expression for X data (features)
781
+ filter_x: Filter expression for X data (default: "skip")
782
+ filter_x_rate: Filter rate for X data (default: 1)
783
+ expression_y: Expression for Y data (labels) (default: "skip")
784
+ filter_y: Filter expression for Y data (default: "skip")
785
+ filter_y_rate: Filter rate for Y data (default: 1)
786
+ samples: Number of samples to generate (default: 1000)
787
+ tile_size: Size of tiles in pixels (default: 256)
788
+ crs: Coordinate reference system (default: "epsg:3577")
789
+ res: Resolution for X data (default: 10)
790
+ res_y: Resolution for Y data, defaults to res if None
791
+ skip_test: Skip expression validation test (default: False)
792
+ start_year: Start year for temporal filtering
793
+ end_year: End year for temporal filtering
794
+ bucket: Storage bucket name
795
+ server: Server to use for processing
796
+ extra_filters: Additional filter expressions
797
+ extra_filters_rate: Rates for additional filters
798
+ extra_filters_res: Resolutions for additional filters
799
799
 
800
800
  Returns:
801
- API response as a dictionary containing task information for the collection
801
+ Response containing task_id and collection name
802
802
 
803
803
  Raises:
804
804
  CollectionNotFoundError: If the collection is not found
805
- CancelCollectionTasksError: If the API request fails due to unknown reasons
805
+ GetTaskError: If the API request fails
806
+ TypeError: If extra filters have mismatched rate and resolution lists
806
807
  """
808
+ expressions = [{"expr": expression_x, "res": res, "prefix": "x"}]
809
+
810
+ res_y = res_y or res
811
+
812
+ if expression_y != "skip":
813
+ expressions.append({"expr": expression_y, "res": res_y, "prefix": "y"})
814
+
815
+ filters = []
816
+ if filter_x != "skip":
817
+ filters.append({"expr": filter_x, "res": res, "rate": filter_x_rate})
818
+
819
+ if filter_y != "skip":
820
+ filters.append({"expr": filter_y, "res": res_y, "rate": filter_y_rate})
821
+
822
+ if extra_filters:
823
+ try:
824
+ extra_filters_combined = zip(extra_filters, extra_filters_res, extra_filters_rate, strict=True)
825
+ except TypeError:
826
+ raise TypeError("Extra filters must have matching rate and resolution.")
827
+
828
+ for expr, filter_res, rate in extra_filters_combined:
829
+ filters.append({"expr": expr, "res": filter_res, "rate": rate})
830
+
831
+ if start_year is not None:
832
+ for expr_dict in expressions:
833
+ expr_dict["expr"] = expr_dict["expr"].replace("{year}", str(start_year))
834
+
835
+ for filter_dict in filters:
836
+ filter_dict["expr"] = filter_dict["expr"].replace("{year}", str(start_year))
837
+
838
+ if not skip_test:
839
+ for expr_dict in expressions:
840
+ test_request = self._client.model._generate_test_request(expr_dict["expr"], crs, -1)
841
+ await self._client._terrakio_request("POST", "geoquery", json=test_request)
842
+
843
+ for filter_dict in filters:
844
+ test_request = self._client.model._generate_test_request(filter_dict["expr"], crs, -1)
845
+ await self._client._terrakio_request("POST", "geoquery", json=test_request)
846
+
847
+ with open(aoi, 'r') as f:
848
+ aoi_data = json.load(f)
849
+
850
+ await self.create_collection(
851
+ collection = name,
852
+ bucket = bucket,
853
+ collection_type = "basic"
854
+ )
855
+
856
+ payload = {
857
+ "expressions": expressions,
858
+ "filters": filters,
859
+ "aoi": aoi_data,
860
+ "samples": samples,
861
+ "crs": crs,
862
+ "tile_size": tile_size,
863
+ "res": res,
864
+ "output": "nc",
865
+ "year_range": [start_year, end_year],
866
+ "server": server
867
+ }
868
+
869
+ task_id_dict, status = await self._client._terrakio_request("POST", f"collections/{name}/training_samples", json=payload)
807
870
 
808
- response, status = await self._client._terrakio_request("POST", f"collections/{collection}/cancel")
809
871
  if status != 200:
810
872
  if status == 404:
811
- raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
812
- raise CancelCollectionTasksError(f"Cancel collection tasks failed with status {status}", status_code=status)
813
-
814
- return response
873
+ raise CollectionNotFoundError(f"Collection {name} not found", status_code=status)
874
+ raise GetTaskError(f"Training sample failed with status {status}", status_code=status)
875
+
876
+ task_id = task_id_dict["task_id"]
877
+
878
+ await self._client.mass_stats.track_progress(task_id)
879
+
880
+ return {"task_id": task_id, "collection": name}
815
881
 
816
882
  @require_api_key
817
- async def cancel_all_tasks(
818
- self
819
- ):
883
+ async def download_files(
884
+ self,
885
+ collection: str,
886
+ file_type: str,
887
+ page: Optional[int] = 0,
888
+ page_size: Optional[int] = 100,
889
+ folder: Optional[str] = None,
890
+ url: Optional[bool] = True
891
+ ) -> Dict[str, Any]:
820
892
  """
821
- Cancel all tasks for the current user.
893
+ Get list of signed urls to download files in collection, or download the files directly.
894
+
895
+ Args:
896
+ collection: Name of collection
897
+ file_type: Type of files to download - must be either 'raw' or 'processed'
898
+ page: Page number (optional, defaults to 0)
899
+ page_size: Number of files to return per page (optional, defaults to 100)
900
+ folder: If processed file type, which folder to download files from (optional)
901
+ url: If True, return signed URLs; if False, download files directly (optional, defaults to True)
822
902
 
823
903
  Returns:
824
- API response as a dictionary containing task information for all tasks
904
+ API response as a dictionary containing list of download URLs (if url=True),
905
+ or a dictionary with downloaded file information (if url=False)
825
906
 
826
907
  Raises:
827
- CancelAllTasksError: If the API request fails due to unknown reasons
908
+ CollectionNotFoundError: If the collection is not found
909
+ DownloadFilesError: If the API request fails due to unknown reasons
910
+ ValueError: If file_type is not 'raw' or 'processed'
828
911
  """
912
+ if file_type not in ['raw', 'processed']:
913
+ raise ValueError(f"file_type must be either 'raw' or 'processed', got '{file_type}'")
914
+
915
+ params = {"file_type": file_type}
916
+
917
+ if page is not None:
918
+ params["page"] = page
919
+ if page_size is not None:
920
+ params["page_size"] = page_size
921
+ if folder is not None:
922
+ params["folder"] = folder
829
923
 
830
- response, status = await self._client._terrakio_request("POST", "tasks/cancel")
924
+ response, status = await self._client._terrakio_request("GET", f"collections/{collection}/download", params=params)
831
925
 
832
926
  if status != 200:
833
- raise CancelAllTasksError(f"Cancel all tasks failed with status {status}", status_code=status)
834
-
835
- return response
927
+ if status == 404:
928
+ raise CollectionNotFoundError(f"Collection {collection} not found", status_code=status)
929
+ raise DownloadFilesError(f"Download files failed with status {status}", status_code=status)
930
+
931
+ if url:
932
+ return response
933
+
934
+ downloaded_files = []
935
+ files_to_download = response.get('files', []) if isinstance(response, dict) else []
936
+
937
+ async with aiohttp.ClientSession() as session:
938
+ for file_info in files_to_download:
939
+ try:
940
+ file_url = file_info.get('url')
941
+ filename = file_info.get('file', '')
942
+ group = file_info.get('group', '')
943
+
944
+ if not file_url:
945
+ downloaded_files.append({
946
+ 'filename': filename,
947
+ 'group': group,
948
+ 'error': 'No URL provided'
949
+ })
950
+ continue
951
+
952
+ async with session.get(file_url) as file_response:
953
+ if file_response.status == 200:
954
+ content = await file_response.read()
955
+
956
+ output_dir = folder if folder else "downloads"
957
+ if group:
958
+ output_dir = os.path.join(output_dir, group)
959
+ os.makedirs(output_dir, exist_ok=True)
960
+ filepath = os.path.join(output_dir, filename)
961
+
962
+ with open(filepath, 'wb') as f:
963
+ f.write(content)
964
+
965
+ downloaded_files.append({
966
+ 'filename': filename,
967
+ 'group': group,
968
+ 'filepath': filepath,
969
+ 'size': len(content)
970
+ })
971
+ else:
972
+ downloaded_files.append({
973
+ 'filename': filename,
974
+ 'group': group,
975
+ 'error': f"Failed to download: HTTP {file_response.status}"
976
+ })
977
+ except Exception as e:
978
+ downloaded_files.append({
979
+ 'filename': file_info.get('file', 'unknown'),
980
+ 'group': file_info.get('group', ''),
981
+ 'error': str(e)
982
+ })
983
+
984
+ return {
985
+ 'collection': collection,
986
+ 'downloaded_files': downloaded_files,
987
+ 'total': len(downloaded_files)
988
+ }
@@ -59,131 +59,6 @@ class ModelManagement:
59
59
  }
60
60
  return req
61
61
 
62
- @require_api_key
63
- async def generate_ai_dataset(
64
- self,
65
- name: str,
66
- aoi: str,
67
- expression_x: str,
68
- filter_x: str = "skip",
69
- filter_x_rate: float = 1,
70
- expression_y: str = "skip",
71
- filter_y: str = "skip",
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,
79
- start_year: int = None,
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
86
- ) -> dict:
87
- """
88
- Generate an AI dataset using specified parameters.
89
-
90
- Args:
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
112
-
113
- Returns:
114
- dict: Response containing task_id and collection name
115
-
116
- Raises:
117
- APIError: If the API request fails
118
- TypeError: If extra filters have mismatched rate and resolution lists
119
- """
120
- expressions = [{"expr": expression_x, "res": res, "prefix": "x"}]
121
-
122
- res_y = res_y or res
123
-
124
- if expression_y != "skip":
125
- expressions.append({"expr": expression_y, "res": res_y, "prefix": "y"})
126
-
127
- filters = []
128
- if filter_x != "skip":
129
- filters.append({"expr": filter_x, "res": res, "rate": filter_x_rate})
130
-
131
- if filter_y != "skip":
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
-
143
- if start_year is not None:
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:
161
- aoi_data = json.load(f)
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,
173
- aoi=aoi_data,
174
- samples=samples,
175
- year_range=[start_year, end_year],
176
- crs=crs,
177
- tile_size=tile_size,
178
- res=res,
179
- output="nc",
180
- server=server
181
- )
182
-
183
- task_id = task_id_dict["task_id"]
184
-
185
- await self._client.mass_stats.track_progress(task_id)
186
-
187
62
  @require_api_key
188
63
  async def _get_url_for_upload_model_and_script(self, expression: str, model_name: str, script_name: str) -> str:
189
64
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: terrakio-core
3
- Version: 0.4.98.1b4
3
+ Version: 0.4.98.1b6
4
4
  Summary: Core package for the terrakio-python-api
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: aiofiles>=24.1.0
@@ -11,13 +11,13 @@ terrakio_core/convenience_functions/zonal_stats.py,sha256=7PI--RI0hiF1pzZ7_7hqty
11
11
  terrakio_core/endpoints/auth.py,sha256=5WvAO39aLsbJAVtxISwiOZseKr3B9I5tHjamPRehDBQ,8327
12
12
  terrakio_core/endpoints/dataset_management.py,sha256=jpwftiKOI59NhXXypqm6wtILIkfyjy9NfYifRIvhZS0,16791
13
13
  terrakio_core/endpoints/group_management.py,sha256=V0KOGTXwmePBFeym55G_m3ONR-jVj2IU4OVLiL5UKz4,15869
14
- terrakio_core/endpoints/mass_stats.py,sha256=YOtV1_yiTJwtJF-hXY5SrOZ7_tpHeXFk5HLoXcL1Yuc,30064
15
- terrakio_core/endpoints/model_management.py,sha256=AqQkFPIdPuApYC58VLRG0GxD-s6gic7Ffa9InG8lb3I,56505
14
+ terrakio_core/endpoints/mass_stats.py,sha256=Bvke7gmZpNCsbEDyskqV7ZlkztkoBWxxFUPP0DCXz7Y,36970
15
+ terrakio_core/endpoints/model_management.py,sha256=tvJ4BOBsyXKKH430byGH25CkLIzXWgxaPaL0CvL8_0Y,51341
16
16
  terrakio_core/endpoints/space_management.py,sha256=YWb55nkJnFJGlALJ520DvurxDqVqwYtsvqQPWzxzhDs,2266
17
17
  terrakio_core/endpoints/user_management.py,sha256=L_g4ysrh2xyz_JbObUU_tCxgHxisrDUPntWgQOs15GE,7709
18
18
  terrakio_core/helper/bounded_taskgroup.py,sha256=wiTH10jhKZgrsgrFUNG6gig8bFkUEPHkGRT2XY7Rgmo,677
19
19
  terrakio_core/helper/decorators.py,sha256=L6om7wmWNgCei3Wy5U0aZ-70OzsCwclkjIf7SfQuhCg,2289
20
20
  terrakio_core/helper/tiles.py,sha256=lcLCO6KiP05lCI9vngo3zCZJ6Z9C0pUxHSQS4H58EHc,2699
21
- terrakio_core-0.4.98.1b4.dist-info/METADATA,sha256=OWd41QUCndnXlcVN5tdYyQD_QleJ6ts6Fa-XVqxz0mg,1184
22
- terrakio_core-0.4.98.1b4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- terrakio_core-0.4.98.1b4.dist-info/RECORD,,
21
+ terrakio_core-0.4.98.1b6.dist-info/METADATA,sha256=zlli64p2DlufJj4q_xoIYbyuDPra2thNi5r2CtovLJA,1184
22
+ terrakio_core-0.4.98.1b6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ terrakio_core-0.4.98.1b6.dist-info/RECORD,,