terrakio-core 0.2.0__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.
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/PKG-INFO +2 -1
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/pyproject.toml +2 -1
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/setup.py +2 -1
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/auth.py +3 -2
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/client.py +180 -36
- terrakio_core-0.2.2/terrakio_core/group_access_management.py +232 -0
- terrakio_core-0.2.2/terrakio_core/space_management.py +101 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/user_management.py +1 -1
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core.egg-info/PKG-INFO +2 -1
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core.egg-info/SOURCES.txt +2 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core.egg-info/requires.txt +1 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/README.md +0 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/setup.cfg +0 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/__init__.py +0 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/config.py +0 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/dataset_management.py +0 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/exceptions.py +0 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core/mass_stats.py +0 -0
- {terrakio_core-0.2.0 → terrakio_core-0.2.2}/terrakio_core.egg-info/dependency_links.txt +0 -0
- {terrakio_core-0.2.0 → 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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)}")
|
|
@@ -148,7 +148,7 @@ class UserManagement:
|
|
|
148
148
|
APIError: If the API request fails
|
|
149
149
|
"""
|
|
150
150
|
# Use the base API URL instead of hardcoding
|
|
151
|
-
endpoint =
|
|
151
|
+
endpoint = "https://terrakio-server-lark-573248941006.australia-southeast1.run.app/admin/users"
|
|
152
152
|
|
|
153
153
|
params = {}
|
|
154
154
|
if substring:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: terrakio-core
|
|
3
|
-
Version: 0.2.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|