terrakio-core 0.2.1__tar.gz → 0.2.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 (20) hide show
  1. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/PKG-INFO +2 -1
  2. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/pyproject.toml +2 -1
  3. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/setup.py +2 -1
  4. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/auth.py +3 -2
  5. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/client.py +180 -36
  6. terrakio_core-0.2.2/terrakio_core/group_access_management.py +232 -0
  7. terrakio_core-0.2.2/terrakio_core/space_management.py +101 -0
  8. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core.egg-info/PKG-INFO +2 -1
  9. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core.egg-info/SOURCES.txt +2 -0
  10. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core.egg-info/requires.txt +1 -0
  11. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/README.md +0 -0
  12. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/setup.cfg +0 -0
  13. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/__init__.py +0 -0
  14. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/config.py +0 -0
  15. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/dataset_management.py +0 -0
  16. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/exceptions.py +0 -0
  17. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/mass_stats.py +0 -0
  18. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core/user_management.py +0 -0
  19. {terrakio_core-0.2.1 → terrakio_core-0.2.2}/terrakio_core.egg-info/dependency_links.txt +0 -0
  20. {terrakio_core-0.2.1 → terrakio_core-0.2.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.2.1
3
+ Version: 0.2.2
4
4
  Summary: Core components for Terrakio API clients
5
5
  Home-page: https://github.com/HaizeaAnalytics/terrakio-python-api
6
6
  Author: Yupeng Chao
@@ -22,6 +22,7 @@ Requires-Dist: aiohttp>=3.8.0
22
22
  Requires-Dist: pyyaml>=5.1
23
23
  Requires-Dist: xarray>=2023.1.0
24
24
  Requires-Dist: shapely>=2.0.0
25
+ Requires-Dist: geopandas>=0.13.0
25
26
  Dynamic: author
26
27
  Dynamic: home-page
27
28
  Dynamic: requires-python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "terrakio-core"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  authors = [
9
9
  {name = "Yupeng Chao", email = "yupeng@haizea.com.au"},
10
10
  ]
@@ -27,6 +27,7 @@ dependencies = [
27
27
  "pyyaml>=5.1",
28
28
  "xarray>=2023.1.0",
29
29
  "shapely>=2.0.0",
30
+ "geopandas>=0.13.0",
30
31
  ]
31
32
 
32
33
  [project.urls]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages # Make sure to import find_packages
2
2
 
3
3
  setup(
4
4
  name="terrakio_core",
5
- version="0.2.1",
5
+ version="0.2.2",
6
6
  author="Yupeng Chao",
7
7
  author_email="yupeng@haizea.com.au",
8
8
  description="Core components for Terrakio API clients",
@@ -24,6 +24,7 @@ setup(
24
24
  "pyyaml>=5.1",
25
25
  "xarray>=2023.1.0",
26
26
  "shapely>=2.0.0",
27
+ "geopandas>=0.13.0",
27
28
  ],
28
29
  metadata_version='2.2'
29
30
  )
@@ -43,7 +43,8 @@ class AuthClient:
43
43
  "email": email,
44
44
  "password": password
45
45
  }
46
-
46
+ print("the payload is ", payload)
47
+ print("the endpoint is ", endpoint)
47
48
  try:
48
49
  response = self.session.post(
49
50
  endpoint,
@@ -51,7 +52,7 @@ class AuthClient:
51
52
  verify=self.verify,
52
53
  timeout=self.timeout
53
54
  )
54
-
55
+ print("the response is ", response)
55
56
  if not response.ok:
56
57
  error_msg = f"Signup failed: {response.status_code} {response.reason}"
57
58
  try:
@@ -117,12 +117,7 @@ class BaseClient:
117
117
  **kwargs
118
118
  }
119
119
 
120
- if not self.quiet:
121
- print(f"Requesting data with expression: {expr}")
122
-
123
- request_url = f"{self.url}/wcs"
124
- print("the payload is ", payload)
125
- print("the request url is ", request_url)
120
+ request_url = f"{self.url}/geoquery"
126
121
 
127
122
  try:
128
123
  # Get the shared aiohttp session
@@ -139,12 +134,10 @@ class BaseClient:
139
134
  raise APIError(error_msg)
140
135
 
141
136
  content = await response.read()
142
- print("the content is ", content)
143
137
 
144
138
  if output.lower() == "csv":
145
139
  import pandas as pd
146
140
  df = pd.read_csv(BytesIO(content))
147
- print("the content is ", df)
148
141
  return df
149
142
  elif output.lower() == "netcdf":
150
143
  return xr.open_dataset(BytesIO(content))
@@ -159,10 +152,8 @@ class BaseClient:
159
152
  return content
160
153
 
161
154
  except aiohttp.ClientError as e:
162
- print(f"Client error in wcs_async: {str(e)}")
163
155
  raise APIError(f"Request failed: {str(e)}")
164
156
  except Exception as e:
165
- print(f"Unexpected error in wcs_async: {str(e)}")
166
157
  raise
167
158
 
168
159
  async def close_async(self):
@@ -217,13 +208,49 @@ class BaseClient:
217
208
  raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
218
209
  return self.auth_client.signup(email, password)
219
210
 
220
- def login(self, email: str, password: str) -> str:
211
+ def login(self, email: str, password: str) -> Dict[str, str]:
221
212
  if not self.auth_client:
222
213
  raise ConfigurationError("Authentication client not initialized. Please provide auth_url during client initialization.")
223
- token = self.auth_client.login(email, password)
224
- if not self.quiet:
225
- print(f"Successfully authenticated as: {email}")
226
- return token
214
+
215
+ try:
216
+ # First attempt to login
217
+ token_response = self.auth_client.login(email, password)
218
+
219
+ print("the token response is ", token_response)
220
+ # Only proceed with API key retrieval if login was successful
221
+ if token_response:
222
+ # After successful login, get the API key
223
+ api_key_response = self.view_api_key()
224
+ self.key = api_key_response
225
+
226
+ # Save email and API key to config file
227
+ import os
228
+ import json
229
+ config_path = os.path.join(os.environ.get("HOME", ""), ".tkio_config.json")
230
+ try:
231
+ config = {"EMAIL": email, "TERRAKIO_API_KEY": self.key}
232
+ if os.path.exists(config_path):
233
+ with open(config_path, 'r') as f:
234
+ config = json.load(f)
235
+ config["EMAIL"] = email
236
+ config["TERRAKIO_API_KEY"] = self.key
237
+
238
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
239
+ with open(config_path, 'w') as f:
240
+ json.dump(config, f, indent=4)
241
+
242
+ if not self.quiet:
243
+ print(f"Successfully authenticated as: {email}")
244
+ print(f"API key saved to {config_path}")
245
+ except Exception as e:
246
+ if not self.quiet:
247
+ print(f"Warning: Failed to update config file: {e}")
248
+
249
+ return {"token": token_response} if token_response else {"error": "Login failed"}
250
+ except Exception as e:
251
+ if not self.quiet:
252
+ print(f"Login failed: {str(e)}")
253
+ raise
227
254
 
228
255
  def refresh_api_key(self) -> str:
229
256
  if not self.auth_client:
@@ -286,11 +313,7 @@ class BaseClient:
286
313
  "expr": expr,
287
314
  **kwargs
288
315
  }
289
- if not self.quiet:
290
- print(f"Requesting data with expression: {expr}")
291
- request_url = f"{self.url}/wcs"
292
- print("the payload is ", payload)
293
- print("the request url is ", request_url)
316
+ request_url = f"{self.url}/geoquery"
294
317
  try:
295
318
  response = self.session.post(request_url, json=payload, timeout=self.timeout, verify=self.verify)
296
319
  if not response.ok:
@@ -486,11 +509,17 @@ class BaseClient:
486
509
  return self.dataset_management.delete_dataset(name=name, collection=collection)
487
510
 
488
511
  def close(self):
512
+ """Close all client sessions"""
489
513
  self.session.close()
490
514
  if self.auth_client:
491
515
  self.auth_client.session.close()
516
+ # Close aiohttp session if it exists
517
+ if self._aiohttp_session and not self._aiohttp_session.closed:
518
+ asyncio.run(self.close_async())
519
+
492
520
  def __enter__(self):
493
521
  return self
522
+
494
523
  def __exit__(self, exc_type, exc_val, exc_tb):
495
524
  self.close()
496
525
 
@@ -647,8 +676,6 @@ class BaseClient:
647
676
  import geopandas as gpd
648
677
  from shapely.geometry import mapping
649
678
 
650
- print(f"Starting zonal_stats_async with {len(gdb)} geometries")
651
-
652
679
  # Process geometries in batches
653
680
  all_results = []
654
681
  row_indices = []
@@ -661,20 +688,16 @@ class BaseClient:
661
688
  "geometry": mapping(geom),
662
689
  "properties": {"index": index}
663
690
  }
664
- print(f"Processing geometry {index}")
665
691
  result = await self.wcs_async(expr=expr, feature=feature, output=output)
666
- print(f"Got result for geometry {index}: {type(result)}")
667
692
  # Add original index to track which geometry this result belongs to
668
693
  if isinstance(result, pd.DataFrame):
669
694
  result['_geometry_index'] = index
670
695
  return result
671
696
  except Exception as e:
672
- print(f"Error in process_geometry for index {index}: {str(e)}")
673
697
  raise
674
698
 
675
699
  async def process_batch(batch_indices):
676
700
  """Process a batch of geometries concurrently using TaskGroup"""
677
- print(f"Processing batch with indices: {list(batch_indices)}")
678
701
  try:
679
702
  async with asyncio.TaskGroup() as tg:
680
703
  tasks = []
@@ -688,34 +711,28 @@ class BaseClient:
688
711
  for task in tasks:
689
712
  try:
690
713
  result = task.result()
691
- print(f"Task completed successfully: {type(result)}")
692
714
  results.append(result)
693
715
  except Exception as e:
694
- print(f"Error getting task result: {str(e)}")
695
716
  raise
696
717
 
697
718
  return results
698
719
  except* Exception as e:
699
- print(f"TaskGroup error: {str(e)}")
700
720
  # Get the actual exceptions from the tasks
701
721
  for task in tasks:
702
722
  if task.done() and task.exception():
703
- print(f"Task exception: {str(task.exception())}")
723
+ raise task.exception()
704
724
  raise
705
725
 
706
726
  # Process in batches to control concurrency
707
727
  for i in range(0, len(gdb), conc):
708
728
  batch_indices = range(i, min(i + conc, len(gdb)))
709
729
  try:
710
- print(f"Starting batch {i//conc + 1}")
711
730
  batch_results = await process_batch(batch_indices)
712
- print(f"Batch {i//conc + 1} completed successfully")
713
731
  all_results.extend(batch_results)
714
732
  row_indices.extend(batch_indices)
715
733
  except Exception as e:
716
- print(f"Error processing batch starting at index {i}: {str(e)}")
717
734
  if hasattr(e, 'response'):
718
- print(f"API Response: {e.response.text}")
735
+ raise APIError(f"API request failed: {e.response.text}")
719
736
  raise
720
737
 
721
738
  if not all_results:
@@ -778,7 +795,6 @@ class BaseClient:
778
795
 
779
796
  if inplace:
780
797
  # Can't really do inplace with multi-temporal results as we're changing the structure
781
- print("Warning: inplace=True ignored for temporal results, returning new GeoDataFrame")
782
798
  return result_gdf
783
799
  else:
784
800
  return result_gdf
@@ -825,5 +841,133 @@ class BaseClient:
825
841
  geopandas.GeoDataFrame: GeoDataFrame with added columns for results, or None if inplace=True
826
842
  """
827
843
  import asyncio
828
- return asyncio.run(self.zonal_stats_async(gdb, expr, conc, inplace, output))
844
+ result = asyncio.run(self.zonal_stats_async(gdb, expr, conc, inplace, output))
845
+ # Ensure aiohttp session is closed after running async code
846
+ try:
847
+ if self._aiohttp_session and not self._aiohttp_session.closed:
848
+ asyncio.run(self.close_async())
849
+ except RuntimeError:
850
+ # Event loop may already be closed, ignore
851
+ pass
852
+ return result
853
+
854
+ # Group access management protected methods
855
+ def _get_group_users_and_datasets(self, group_name: str):
856
+ if not hasattr(self, "group_access_management") or self.group_access_management is None:
857
+ from terrakio_core.group_access_management import GroupAccessManagement
858
+ if not self.url or not self.key:
859
+ raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
860
+ self.group_access_management = GroupAccessManagement(
861
+ api_url=self.url,
862
+ api_key=self.key,
863
+ verify=self.verify,
864
+ timeout=self.timeout
865
+ )
866
+ return self.group_access_management.get_group_users_and_datasets(group_name)
867
+
868
+ def _add_group_to_dataset(self, dataset: str, group: str):
869
+ if not hasattr(self, "group_access_management") or self.group_access_management is None:
870
+ from terrakio_core.group_access_management import GroupAccessManagement
871
+ if not self.url or not self.key:
872
+ raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
873
+ self.group_access_management = GroupAccessManagement(
874
+ api_url=self.url,
875
+ api_key=self.key,
876
+ verify=self.verify,
877
+ timeout=self.timeout
878
+ )
879
+ return self.group_access_management.add_group_to_dataset(dataset, group)
880
+
881
+ def _add_group_to_user(self, uid: str, group: str):
882
+ if not hasattr(self, "group_access_management") or self.group_access_management is None:
883
+ from terrakio_core.group_access_management import GroupAccessManagement
884
+ if not self.url or not self.key:
885
+ raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
886
+ self.group_access_management = GroupAccessManagement(
887
+ api_url=self.url,
888
+ api_key=self.key,
889
+ verify=self.verify,
890
+ timeout=self.timeout
891
+ )
892
+ print("the uid is and the group is ", uid, group)
893
+ return self.group_access_management.add_group_to_user(uid, group)
894
+
895
+ def _delete_group_from_user(self, uid: str, group: str):
896
+ if not hasattr(self, "group_access_management") or self.group_access_management is None:
897
+ from terrakio_core.group_access_management import GroupAccessManagement
898
+ if not self.url or not self.key:
899
+ raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
900
+ self.group_access_management = GroupAccessManagement(
901
+ api_url=self.url,
902
+ api_key=self.key,
903
+ verify=self.verify,
904
+ timeout=self.timeout
905
+ )
906
+ return self.group_access_management.delete_group_from_user(uid, group)
907
+
908
+ def _delete_group_from_dataset(self, dataset: str, group: str):
909
+ if not hasattr(self, "group_access_management") or self.group_access_management is None:
910
+ from terrakio_core.group_access_management import GroupAccessManagement
911
+ if not self.url or not self.key:
912
+ raise ConfigurationError("Group access management client not initialized. Make sure API URL and key are set.")
913
+ self.group_access_management = GroupAccessManagement(
914
+ api_url=self.url,
915
+ api_key=self.key,
916
+ verify=self.verify,
917
+ timeout=self.timeout
918
+ )
919
+ return self.group_access_management.delete_group_from_dataset(dataset, group)
920
+
921
+ # Space management protected methods
922
+ def _get_total_space_used(self):
923
+ if not hasattr(self, "space_management") or self.space_management is None:
924
+ from terrakio_core.space_management import SpaceManagement
925
+ if not self.url or not self.key:
926
+ raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
927
+ self.space_management = SpaceManagement(
928
+ api_url=self.url,
929
+ api_key=self.key,
930
+ verify=self.verify,
931
+ timeout=self.timeout
932
+ )
933
+ return self.space_management.get_total_space_used()
934
+
935
+ def _get_space_used_by_job(self, name: str, region: str = None):
936
+ if not hasattr(self, "space_management") or self.space_management is None:
937
+ from terrakio_core.space_management import SpaceManagement
938
+ if not self.url or not self.key:
939
+ raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
940
+ self.space_management = SpaceManagement(
941
+ api_url=self.url,
942
+ api_key=self.key,
943
+ verify=self.verify,
944
+ timeout=self.timeout
945
+ )
946
+ return self.space_management.get_space_used_by_job(name, region)
947
+
948
+ def _delete_user_job(self, name: str, region: str = None):
949
+ if not hasattr(self, "space_management") or self.space_management is None:
950
+ from terrakio_core.space_management import SpaceManagement
951
+ if not self.url or not self.key:
952
+ raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
953
+ self.space_management = SpaceManagement(
954
+ api_url=self.url,
955
+ api_key=self.key,
956
+ verify=self.verify,
957
+ timeout=self.timeout
958
+ )
959
+ return self.space_management.delete_user_job(name, region)
960
+
961
+ def _delete_data_in_path(self, path: str, region: str = None):
962
+ if not hasattr(self, "space_management") or self.space_management is None:
963
+ from terrakio_core.space_management import SpaceManagement
964
+ if not self.url or not self.key:
965
+ raise ConfigurationError("Space management client not initialized. Make sure API URL and key are set.")
966
+ self.space_management = SpaceManagement(
967
+ api_url=self.url,
968
+ api_key=self.key,
969
+ verify=self.verify,
970
+ timeout=self.timeout
971
+ )
972
+ return self.space_management.delete_data_in_path(path, region)
829
973
 
@@ -0,0 +1,232 @@
1
+ import requests
2
+ from typing import Dict, Any, List
3
+ from .exceptions import APIError
4
+
5
+ class GroupAccessManagement:
6
+ def __init__(self, api_url: str, api_key: str, verify: bool = True, timeout: int = 60):
7
+ """
8
+ Initialize the Group Access Management client.
9
+
10
+ Args:
11
+ api_url: API base URL
12
+ api_key: API key for authentication
13
+ verify: Verify SSL certificates
14
+ timeout: Request timeout in seconds
15
+ """
16
+ self.api_url = api_url.rstrip('/')
17
+ self.api_key = api_key
18
+ self.verify = verify
19
+ self.timeout = timeout
20
+ self.session = requests.Session()
21
+ self.session.headers.update({
22
+ 'x-api-key': self.api_key,
23
+ 'Content-Type': 'application/json'
24
+ })
25
+
26
+ def get_group_users_and_datasets(self, group_name: str) -> Dict[str, Any]:
27
+ """
28
+ Get users and datasets of a group.
29
+
30
+ Args:
31
+ group_name: Name of the group
32
+
33
+ Returns:
34
+ Dictionary containing lists of users and datasets associated with the group
35
+ {
36
+ "users": [UID, ...],
37
+ "datasets": [DATASET_NAME, ...]
38
+ }
39
+
40
+ Raises:
41
+ APIError: If the API request fails
42
+ """
43
+ endpoint = f"https://dev-au.terrak.io/groups/{group_name}"
44
+ print("the endpoint is ", endpoint)
45
+ print
46
+ try:
47
+ response = self.session.get(
48
+ endpoint,
49
+ timeout=self.timeout,
50
+ verify=self.verify
51
+ )
52
+ print("the response is ", response.text)
53
+ if not response.ok:
54
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
55
+ try:
56
+ error_data = response.json()
57
+ if "detail" in error_data:
58
+ error_msg += f" - {error_data['detail']}"
59
+ except:
60
+ pass
61
+ raise APIError(error_msg)
62
+
63
+ return response.json()
64
+
65
+ except requests.RequestException as e:
66
+ raise APIError(f"Request failed: {str(e)}")
67
+
68
+ def add_group_to_dataset(self, dataset: str, group: str) -> Dict[str, Any]:
69
+ """
70
+ Add a group to a dataset.
71
+
72
+ Args:
73
+ dataset: Name of the dataset
74
+ group: Name of the group to add to the dataset
75
+
76
+ Returns:
77
+ API response data
78
+
79
+ Raises:
80
+ APIError: If the API request fails
81
+ """
82
+ endpoint = f"{self.api_url}/groups/dataset/{dataset}"
83
+ print("hello")
84
+ print("the endpoint is ", endpoint)
85
+ params = {"group": group}
86
+ print("the endpoint is ", endpoint)
87
+ print("!!!!!!!!!!!")
88
+ print("the params are ", params)
89
+ try:
90
+ response = self.session.post(
91
+ endpoint,
92
+ params=params,
93
+ timeout=self.timeout,
94
+ verify=self.verify
95
+ )
96
+
97
+ if not response.ok:
98
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
99
+ try:
100
+ error_data = response.json()
101
+ if "detail" in error_data:
102
+ error_msg += f" - {error_data['detail']}"
103
+ except:
104
+ pass
105
+ raise APIError(error_msg)
106
+
107
+ return response.json()
108
+
109
+ except requests.RequestException as e:
110
+ raise APIError(f"Request failed: {str(e)}")
111
+
112
+ def add_group_to_user(self, uid: str, group: str) -> Dict[str, Any]:
113
+ """
114
+ Add a group to a user.
115
+
116
+ Args:
117
+ uid: User UID
118
+ group: Name of the group to add to the user
119
+
120
+ Returns:
121
+ API response data
122
+
123
+ Raises:
124
+ APIError: If the API request fails
125
+ """
126
+ endpoint = f"{self.api_url}/groups/users/{uid}"
127
+ params = {"group": group}
128
+ print("the endpoint is ", endpoint)
129
+ print("the params are ", params)
130
+ try:
131
+ response = self.session.post(
132
+ endpoint,
133
+ params=params,
134
+ timeout=self.timeout,
135
+ verify=self.verify
136
+ )
137
+
138
+ if not response.ok:
139
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
140
+ try:
141
+ error_data = response.json()
142
+ if "detail" in error_data:
143
+ error_msg += f" - {error_data['detail']}"
144
+ except:
145
+ pass
146
+ raise APIError(error_msg)
147
+
148
+ return response.json()
149
+
150
+ except requests.RequestException as e:
151
+ raise APIError(f"Request failed: {str(e)}")
152
+
153
+ def delete_group_from_user(self, uid: str, group: str) -> Dict[str, Any]:
154
+ """
155
+ Delete a group from a user.
156
+
157
+ Args:
158
+ uid: User UID
159
+ group: Name of the group to remove from the user
160
+
161
+ Returns:
162
+ API response data
163
+
164
+ Raises:
165
+ APIError: If the API request fails
166
+ """
167
+ endpoint = f"{self.api_url}/groups/users/{uid}"
168
+ params = {"group": group}
169
+
170
+ try:
171
+ response = self.session.delete(
172
+ endpoint,
173
+ params=params,
174
+ timeout=self.timeout,
175
+ verify=self.verify
176
+ )
177
+
178
+ if not response.ok:
179
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
180
+ try:
181
+ error_data = response.json()
182
+ if "detail" in error_data:
183
+ error_msg += f" - {error_data['detail']}"
184
+ except:
185
+ pass
186
+ raise APIError(error_msg)
187
+
188
+ return response.json()
189
+
190
+ except requests.RequestException as e:
191
+ raise APIError(f"Request failed: {str(e)}")
192
+
193
+ def delete_group_from_dataset(self, dataset: str, group: str) -> Dict[str, Any]:
194
+ """
195
+ Delete a group from a dataset.
196
+
197
+ Args:
198
+ dataset: Name of the dataset
199
+ group: Name of the group to remove from the dataset
200
+
201
+ Returns:
202
+ API response data
203
+
204
+ Raises:
205
+ APIError: If the API request fails
206
+ """
207
+ endpoint = f"{self.api_url}/groups/datasets/{dataset}"
208
+ params = {"group": group}
209
+
210
+ try:
211
+ response = self.session.delete(
212
+ endpoint,
213
+ params=params,
214
+ timeout=self.timeout,
215
+ verify=self.verify
216
+ )
217
+
218
+ if not response.ok:
219
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
220
+ try:
221
+ error_data = response.json()
222
+ if "detail" in error_data:
223
+ error_msg += f" - {error_data['detail']}"
224
+ except:
225
+ pass
226
+ raise APIError(error_msg)
227
+
228
+ return response.json()
229
+
230
+ except requests.RequestException as e:
231
+ raise APIError(f"Request failed: {str(e)}")
232
+
@@ -0,0 +1,101 @@
1
+ import requests
2
+ from typing import Dict, Any, Optional
3
+ from .exceptions import APIError
4
+
5
+ class SpaceManagement:
6
+ def __init__(self, api_url: str, api_key: str, verify: bool = True, timeout: int = 60):
7
+ self.api_url = api_url.rstrip('/')
8
+ self.api_key = api_key
9
+ self.verify = verify
10
+ self.timeout = timeout
11
+ self.session = requests.Session()
12
+ self.session.headers.update({
13
+ 'x-api-key': self.api_key,
14
+ 'Content-Type': 'application/json'
15
+ })
16
+
17
+ def get_total_space_used(self) -> Dict[str, Any]:
18
+ """
19
+ Get total space used by the user.
20
+ Returns a dict with user, total, and jobs breakdown.
21
+ """
22
+ endpoint = f"{self.api_url}/users/jobs"
23
+ try:
24
+ response = self.session.get(endpoint, timeout=self.timeout, verify=self.verify)
25
+ if not response.ok:
26
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
27
+ try:
28
+ error_data = response.json()
29
+ if "detail" in error_data:
30
+ error_msg += f" - {error_data['detail']}"
31
+ except:
32
+ pass
33
+ raise APIError(error_msg)
34
+ return response.json()
35
+ except requests.RequestException as e:
36
+ raise APIError(f"Request failed: {str(e)}")
37
+
38
+ def get_space_used_by_job(self, name: str, region: Optional[str] = None) -> Dict[str, Any]:
39
+ """
40
+ Get space used by a specific job.
41
+ """
42
+ endpoint = f"{self.api_url}/users/jobs/{name}"
43
+ params = {"region": region} if region else {}
44
+ try:
45
+ response = self.session.get(endpoint, params=params, timeout=self.timeout, verify=self.verify)
46
+ if not response.ok:
47
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
48
+ try:
49
+ error_data = response.json()
50
+ if "detail" in error_data:
51
+ error_msg += f" - {error_data['detail']}"
52
+ except:
53
+ pass
54
+ raise APIError(error_msg)
55
+ return response.json()
56
+ except requests.RequestException as e:
57
+ raise APIError(f"Request failed: {str(e)}")
58
+
59
+ def delete_user_job(self, name: str, region: Optional[str] = None) -> Dict[str, Any]:
60
+ """
61
+ Delete a user job by name and region.
62
+ """
63
+ endpoint = f"{self.api_url}/users/job/{name}"
64
+ params = {"region": region} if region else {}
65
+ try:
66
+ response = self.session.delete(endpoint, params=params, timeout=self.timeout, verify=self.verify)
67
+ if not response.ok:
68
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
69
+ try:
70
+ error_data = response.json()
71
+ if "detail" in error_data:
72
+ error_msg += f" - {error_data['detail']}"
73
+ except:
74
+ pass
75
+ raise APIError(error_msg)
76
+ return response.json()
77
+ except requests.RequestException as e:
78
+ raise APIError(f"Request failed: {str(e)}")
79
+
80
+ def delete_data_in_path(self, path: str, region: Optional[str] = None) -> Dict[str, Any]:
81
+ """
82
+ Delete data in a GCS path for a given region.
83
+ """
84
+ endpoint = f"{self.api_url}/users/jobs"
85
+ params = {"path": path}
86
+ if region:
87
+ params["region"] = region
88
+ try:
89
+ response = self.session.delete(endpoint, params=params, timeout=self.timeout, verify=self.verify)
90
+ if not response.ok:
91
+ error_msg = f"API request failed: {response.status_code} {response.reason}"
92
+ try:
93
+ error_data = response.json()
94
+ if "detail" in error_data:
95
+ error_msg += f" - {error_data['detail']}"
96
+ except:
97
+ pass
98
+ raise APIError(error_msg)
99
+ return response.json()
100
+ except requests.RequestException as e:
101
+ raise APIError(f"Request failed: {str(e)}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: terrakio-core
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Core components for Terrakio API clients
5
5
  Home-page: https://github.com/HaizeaAnalytics/terrakio-python-api
6
6
  Author: Yupeng Chao
@@ -22,6 +22,7 @@ Requires-Dist: aiohttp>=3.8.0
22
22
  Requires-Dist: pyyaml>=5.1
23
23
  Requires-Dist: xarray>=2023.1.0
24
24
  Requires-Dist: shapely>=2.0.0
25
+ Requires-Dist: geopandas>=0.13.0
25
26
  Dynamic: author
26
27
  Dynamic: home-page
27
28
  Dynamic: requires-python
@@ -7,7 +7,9 @@ terrakio_core/client.py
7
7
  terrakio_core/config.py
8
8
  terrakio_core/dataset_management.py
9
9
  terrakio_core/exceptions.py
10
+ terrakio_core/group_access_management.py
10
11
  terrakio_core/mass_stats.py
12
+ terrakio_core/space_management.py
11
13
  terrakio_core/user_management.py
12
14
  terrakio_core.egg-info/PKG-INFO
13
15
  terrakio_core.egg-info/SOURCES.txt
@@ -3,3 +3,4 @@ aiohttp>=3.8.0
3
3
  pyyaml>=5.1
4
4
  xarray>=2023.1.0
5
5
  shapely>=2.0.0
6
+ geopandas>=0.13.0
File without changes
File without changes