terrakio-core 0.3.4__py3-none-any.whl → 0.3.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of terrakio-core might be problematic. Click here for more details.
- terrakio_core/__init__.py +10 -1
- terrakio_core/async_client.py +304 -0
- terrakio_core/client.py +22 -1713
- terrakio_core/config.py +8 -15
- terrakio_core/convenience_functions/convenience_functions.py +296 -0
- terrakio_core/endpoints/auth.py +180 -0
- terrakio_core/endpoints/dataset_management.py +371 -0
- terrakio_core/endpoints/group_management.py +228 -0
- terrakio_core/endpoints/mass_stats.py +594 -0
- terrakio_core/endpoints/model_management.py +790 -0
- terrakio_core/endpoints/space_management.py +72 -0
- terrakio_core/endpoints/user_management.py +131 -0
- terrakio_core/exceptions.py +4 -2
- terrakio_core/helper/bounded_taskgroup.py +20 -0
- terrakio_core/helper/decorators.py +58 -0
- terrakio_core/{generation → helper}/tiles.py +1 -12
- terrakio_core/sync_client.py +370 -0
- {terrakio_core-0.3.4.dist-info → terrakio_core-0.3.7.dist-info}/METADATA +7 -1
- terrakio_core-0.3.7.dist-info/RECORD +21 -0
- terrakio_core/auth.py +0 -223
- terrakio_core/dataset_management.py +0 -287
- terrakio_core/decorators.py +0 -18
- terrakio_core/group_access_management.py +0 -232
- terrakio_core/mass_stats.py +0 -504
- terrakio_core/space_management.py +0 -101
- terrakio_core/user_management.py +0 -227
- terrakio_core-0.3.4.dist-info/RECORD +0 -16
- {terrakio_core-0.3.4.dist-info → terrakio_core-0.3.7.dist-info}/WHEEL +0 -0
- {terrakio_core-0.3.4.dist-info → terrakio_core-0.3.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from ..helper.decorators import require_token, require_api_key, require_auth
|
|
3
|
+
class SpaceManagement:
|
|
4
|
+
def __init__(self, client):
|
|
5
|
+
self._client = client
|
|
6
|
+
|
|
7
|
+
@require_api_key
|
|
8
|
+
def get_total_space_used(self) -> Dict[str, Any]:
|
|
9
|
+
"""
|
|
10
|
+
Get total space used by the user.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Dict[str, Any]: Total space used by the user.
|
|
14
|
+
|
|
15
|
+
Raises:
|
|
16
|
+
APIError: If the API request fails
|
|
17
|
+
"""
|
|
18
|
+
return self._client._terrakio_request("GET", "/users/jobs")
|
|
19
|
+
|
|
20
|
+
@require_api_key
|
|
21
|
+
def get_space_used_by_job(self, name: str, region: str) -> Dict[str, Any]:
|
|
22
|
+
"""
|
|
23
|
+
Get space used by a specific job.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
name: The name of the job
|
|
27
|
+
region: The region of the job
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dict[str, Any]: Space used by the job.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
APIError: If the API request fails
|
|
34
|
+
"""
|
|
35
|
+
params = {"region": region}
|
|
36
|
+
return self._client._terrakio_request("GET", f"/users/jobs/{name}", params=params)
|
|
37
|
+
|
|
38
|
+
@require_api_key
|
|
39
|
+
def delete_user_job(self, name: str, region: str) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Delete a user job by name and region.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
name: The name of the job
|
|
45
|
+
region: The region of the job
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dict[str, Any]: Response from the delete operation.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
APIError: If the API request fails
|
|
52
|
+
"""
|
|
53
|
+
params = {"region": region}
|
|
54
|
+
return self._client._terrakio_request("DELETE", f"/users/jobs/{name}", params=params)
|
|
55
|
+
|
|
56
|
+
@require_api_key
|
|
57
|
+
def delete_data_in_path(self, path: str, region: str) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Delete data in a GCS path for a given region.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
path: The GCS path to delete data from
|
|
63
|
+
region: The region where the data is located
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Dict[str, Any]: Response from the delete operation.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
APIError: If the API request fails
|
|
70
|
+
"""
|
|
71
|
+
params = {"path": path, "region": region}
|
|
72
|
+
return self._client._terrakio_request("DELETE", "/users/jobs", params=params)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional
|
|
2
|
+
from ..helper.decorators import require_token, require_api_key, require_auth
|
|
3
|
+
|
|
4
|
+
class UserManagement:
|
|
5
|
+
def __init__(self, client):
|
|
6
|
+
self._client = client
|
|
7
|
+
|
|
8
|
+
@require_api_key
|
|
9
|
+
def get_user_by_id(self, id: str) -> Dict[str, Any]:
|
|
10
|
+
"""
|
|
11
|
+
Get user by ID.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
user_id: User ID
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
User information
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
APIError: If the API request fails
|
|
21
|
+
"""
|
|
22
|
+
return self._client._terrakio_request("GET", f"admin/users/{id}")
|
|
23
|
+
|
|
24
|
+
@require_api_key
|
|
25
|
+
def get_user_by_email(self, email: str) -> Dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Get user by email.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
email: User email
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
User information
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
APIError: If the API request fails
|
|
37
|
+
"""
|
|
38
|
+
return self._client._terrakio_request("GET", f"admin/users/email/{email}")
|
|
39
|
+
|
|
40
|
+
@require_api_key
|
|
41
|
+
def list_users(self, substring: Optional[str] = None, uid: bool = False) -> List[Dict[str, Any]]:
|
|
42
|
+
"""
|
|
43
|
+
List users, optionally filtering by a substring.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
substring: Optional substring to filter users
|
|
47
|
+
uid: If True, includes the user ID in the response (default: False)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
List of users
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
APIError: If the API request fails
|
|
54
|
+
"""
|
|
55
|
+
params = {"uid": str(uid).lower()}
|
|
56
|
+
if substring:
|
|
57
|
+
params['substring'] = substring
|
|
58
|
+
return self._client._terrakio_request("GET", "admin/users", params=params)
|
|
59
|
+
|
|
60
|
+
@require_api_key
|
|
61
|
+
def edit_user(
|
|
62
|
+
self,
|
|
63
|
+
uid: str,
|
|
64
|
+
email: Optional[str],
|
|
65
|
+
role: Optional[str],
|
|
66
|
+
apiKey: Optional[str],
|
|
67
|
+
groups: Optional[List[str]],
|
|
68
|
+
quota: Optional[int]
|
|
69
|
+
) -> Dict[str, Any]:
|
|
70
|
+
"""
|
|
71
|
+
Edit user info. Only provided fields will be updated.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
uid: User ID
|
|
75
|
+
email: New user email
|
|
76
|
+
role: New user role
|
|
77
|
+
apiKey: New API key
|
|
78
|
+
groups: New list of groups
|
|
79
|
+
quota: New quota
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Updated user information
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
APIError: If the API request fails
|
|
86
|
+
"""
|
|
87
|
+
payload = {"uid": uid}
|
|
88
|
+
payload_mapping = {
|
|
89
|
+
"email": email,
|
|
90
|
+
"role": role,
|
|
91
|
+
"apiKey": apiKey,
|
|
92
|
+
"groups": groups,
|
|
93
|
+
"quota": quota
|
|
94
|
+
}
|
|
95
|
+
for key, value in payload_mapping.items():
|
|
96
|
+
if value is not None:
|
|
97
|
+
payload[key] = value
|
|
98
|
+
return self._client._terrakio_request("PATCH", "admin/users", json=payload)
|
|
99
|
+
|
|
100
|
+
@require_api_key
|
|
101
|
+
def reset_quota(self, email: str, quota: Optional[int] = None) -> Dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
Reset the quota for a user by email.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
email: The user's email (required)
|
|
107
|
+
quota: The new quota value (optional)
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
API response as a dictionary
|
|
111
|
+
"""
|
|
112
|
+
payload = {"email": email}
|
|
113
|
+
if quota is not None:
|
|
114
|
+
payload["quota"] = quota
|
|
115
|
+
return self._client._terrakio_request("PATCH", f"admin/users/reset_quota/{email}", json=payload)
|
|
116
|
+
|
|
117
|
+
@require_api_key
|
|
118
|
+
def delete_user(self, uid: str) -> Dict[str, Any]:
|
|
119
|
+
"""
|
|
120
|
+
Delete a user by UID.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
uid: The user's UID (required)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
API response as a dictionary
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
APIError: If the API request fails
|
|
130
|
+
"""
|
|
131
|
+
return self._client._terrakio_request("DELETE", f"admin/users/{uid}")
|
terrakio_core/exceptions.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
class APIError(Exception):
|
|
2
2
|
"""Exception raised for errors in the API responses."""
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
def __init__(self, message, status_code=None):
|
|
5
|
+
super().__init__(message)
|
|
6
|
+
self.status_code = status_code
|
|
5
7
|
|
|
6
8
|
class ConfigurationError(Exception):
|
|
7
9
|
"""Exception raised for errors in the configuration."""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
# Adapted from https://discuss.python.org/t/boundedtaskgroup-to-control-parallelism/27171
|
|
4
|
+
|
|
5
|
+
class BoundedTaskGroup(asyncio.TaskGroup):
|
|
6
|
+
def __init__(self, *args, max_concurrency = 0, **kwargs):
|
|
7
|
+
super().__init__(*args)
|
|
8
|
+
if max_concurrency:
|
|
9
|
+
self._sem = asyncio.Semaphore(max_concurrency)
|
|
10
|
+
else:
|
|
11
|
+
self._sem = None
|
|
12
|
+
|
|
13
|
+
def create_task(self, coro, *args, **kwargs):
|
|
14
|
+
if self._sem:
|
|
15
|
+
async def _wrapped_coro(sem, coro):
|
|
16
|
+
async with sem:
|
|
17
|
+
return await coro
|
|
18
|
+
coro = _wrapped_coro(self._sem, coro)
|
|
19
|
+
|
|
20
|
+
return super().create_task(coro, *args, **kwargs)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# terrakio_core/decorators.py
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from ..exceptions import ConfigurationError
|
|
4
|
+
|
|
5
|
+
def require_token(func):
|
|
6
|
+
"""Decorator to ensure a token is available before a method can be executed."""
|
|
7
|
+
@wraps(func)
|
|
8
|
+
def wrapper(self, *args, **kwargs):
|
|
9
|
+
# Check both direct token and client token
|
|
10
|
+
has_token = False
|
|
11
|
+
if hasattr(self, 'token') and self.token:
|
|
12
|
+
has_token = True
|
|
13
|
+
elif hasattr(self, '_client') and hasattr(self._client, 'token') and self._client.token:
|
|
14
|
+
has_token = True
|
|
15
|
+
|
|
16
|
+
if not has_token:
|
|
17
|
+
raise ConfigurationError("Authentication token required. Please login first.")
|
|
18
|
+
return func(self, *args, **kwargs)
|
|
19
|
+
|
|
20
|
+
wrapper._is_decorated = True
|
|
21
|
+
return wrapper
|
|
22
|
+
|
|
23
|
+
def require_api_key(func):
|
|
24
|
+
"""Decorator to ensure an API key is available before a method can be executed."""
|
|
25
|
+
@wraps(func)
|
|
26
|
+
def wrapper(self, *args, **kwargs):
|
|
27
|
+
# Check both direct key and client key
|
|
28
|
+
has_key = False
|
|
29
|
+
if hasattr(self, 'key') and self.key:
|
|
30
|
+
has_key = True
|
|
31
|
+
elif hasattr(self, '_client') and hasattr(self._client, 'key') and self._client.key:
|
|
32
|
+
has_key = True
|
|
33
|
+
|
|
34
|
+
if not has_key:
|
|
35
|
+
raise ConfigurationError("API key required. Please provide an API key or login first.")
|
|
36
|
+
return func(self, *args, **kwargs)
|
|
37
|
+
|
|
38
|
+
wrapper._is_decorated = True
|
|
39
|
+
return wrapper
|
|
40
|
+
|
|
41
|
+
def require_auth(func):
|
|
42
|
+
"""Decorator that requires either a token OR an API key"""
|
|
43
|
+
@wraps(func)
|
|
44
|
+
def wrapper(self, *args, **kwargs):
|
|
45
|
+
# Check both direct auth and client auth
|
|
46
|
+
has_token = (hasattr(self, 'token') and self.token) or \
|
|
47
|
+
(hasattr(self, '_client') and hasattr(self._client, 'token') and self._client.token)
|
|
48
|
+
has_api_key = (hasattr(self, 'key') and self.key) or \
|
|
49
|
+
(hasattr(self, '_client') and hasattr(self._client, 'key') and self._client.key)
|
|
50
|
+
|
|
51
|
+
if not has_token and not has_api_key:
|
|
52
|
+
raise ConfigurationError(
|
|
53
|
+
"Authentication required. Please provide either an API key or login to get a token."
|
|
54
|
+
)
|
|
55
|
+
return func(self, *args, **kwargs)
|
|
56
|
+
|
|
57
|
+
wrapper._is_decorated = True
|
|
58
|
+
return wrapper
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
### implementing generation-tiles in python api
|
|
2
|
-
### function should just generate the json file for mass_stats to pick up.
|
|
3
|
-
|
|
4
1
|
import geopandas as gpd
|
|
5
2
|
import shapely.geometry
|
|
6
3
|
import json
|
|
7
|
-
from rich import print
|
|
8
4
|
|
|
9
5
|
def escape_newline(string):
|
|
10
6
|
if isinstance(string, list):
|
|
@@ -28,7 +24,6 @@ def tile_generator(x_min, y_min, x_max, y_max, aoi, crs, res, tile_size, express
|
|
|
28
24
|
j_max += 1
|
|
29
25
|
for j in range(0, int(j_max)):
|
|
30
26
|
for i in range(0, int(i_max)):
|
|
31
|
-
#print(f"Processing tile {i} {j}")
|
|
32
27
|
x = x_min + i*(tile_size*res)
|
|
33
28
|
y = y_max - j*(tile_size*res)
|
|
34
29
|
bbox = shapely.geometry.box(x, y-(tile_size*res), x + (tile_size*res), y)
|
|
@@ -62,10 +57,8 @@ def tiles(
|
|
|
62
57
|
non_interactive: bool = False,
|
|
63
58
|
):
|
|
64
59
|
|
|
65
|
-
# Create requests for each tile
|
|
66
60
|
reqs = []
|
|
67
61
|
x_min, y_min, x_max, y_max, aoi = get_bounds(aoi, crs, to_crs)
|
|
68
|
-
#print(f"Bounds: {x_min}, {y_min}, {x_max}, {y_max}")
|
|
69
62
|
|
|
70
63
|
if to_crs is None:
|
|
71
64
|
to_crs = crs
|
|
@@ -73,9 +66,6 @@ def tiles(
|
|
|
73
66
|
req_name = f"{name}_{i:02d}_{j:02d}"
|
|
74
67
|
reqs.append({"group": "tiles", "file": req_name, "request": tile_req})
|
|
75
68
|
|
|
76
|
-
#print(f"Generated {len(reqs)} tile requests.")
|
|
77
|
-
|
|
78
|
-
|
|
79
69
|
count = len(reqs)
|
|
80
70
|
groups = list(set(dic["group"] for dic in reqs))
|
|
81
71
|
|
|
@@ -91,5 +81,4 @@ def tiles(
|
|
|
91
81
|
request_json = json.dumps(reqs)
|
|
92
82
|
manifest_json = json.dumps(groups)
|
|
93
83
|
|
|
94
|
-
return body, request_json, manifest_json
|
|
95
|
-
|
|
84
|
+
return body, request_json, manifest_json
|