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,370 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
from typing import Optional, Dict, Any, Union
|
|
4
|
+
from geopandas import GeoDataFrame
|
|
5
|
+
from shapely.geometry.base import BaseGeometry as ShapelyGeometry
|
|
6
|
+
from .async_client import AsyncClient
|
|
7
|
+
from typing import Dict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def sync_wrapper(async_func):
|
|
11
|
+
"""
|
|
12
|
+
Decorator to convert async functions to sync functions.
|
|
13
|
+
"""
|
|
14
|
+
@functools.wraps(async_func)
|
|
15
|
+
def wrapper(*args, **kwargs):
|
|
16
|
+
# Get or create event loop
|
|
17
|
+
try:
|
|
18
|
+
loop = asyncio.get_running_loop()
|
|
19
|
+
# If we're in an async context, we need to run in a new thread
|
|
20
|
+
import concurrent.futures
|
|
21
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
22
|
+
future = executor.submit(asyncio.run, async_func(*args, **kwargs))
|
|
23
|
+
return future.result()
|
|
24
|
+
except RuntimeError:
|
|
25
|
+
# No running loop, we can create a new one
|
|
26
|
+
return asyncio.run(async_func(*args, **kwargs))
|
|
27
|
+
return wrapper
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SyncWrapper:
|
|
31
|
+
"""
|
|
32
|
+
Generic synchronous wrapper for any async object.
|
|
33
|
+
Automatically converts all async methods to sync using __getattr__.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, async_obj, sync_client):
|
|
37
|
+
self._async_obj = async_obj
|
|
38
|
+
self._sync_client = sync_client
|
|
39
|
+
|
|
40
|
+
def __getattr__(self, name):
|
|
41
|
+
"""
|
|
42
|
+
Dynamically wrap any method call to convert async to sync.
|
|
43
|
+
"""
|
|
44
|
+
attr = getattr(self._async_obj, name)
|
|
45
|
+
|
|
46
|
+
# If it's a callable (method), wrap it
|
|
47
|
+
if callable(attr):
|
|
48
|
+
def sync_wrapper(*args, **kwargs):
|
|
49
|
+
result = attr(*args, **kwargs)
|
|
50
|
+
# If the result is a coroutine, run it synchronously
|
|
51
|
+
if hasattr(result, '__await__'):
|
|
52
|
+
return self._sync_client._run_async(result)
|
|
53
|
+
return result
|
|
54
|
+
return sync_wrapper
|
|
55
|
+
|
|
56
|
+
# If it's not a callable (like a property), return as-is
|
|
57
|
+
return attr
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SyncClient:
|
|
61
|
+
"""
|
|
62
|
+
Synchronous wrapper around AsyncClient that converts all async methods to sync.
|
|
63
|
+
Uses the AsyncClient as a context manager to properly handle session lifecycle.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, url: Optional[str] = None, api_key: Optional[str] = None, verbose: bool = False):
|
|
67
|
+
"""
|
|
68
|
+
Initialize the synchronous client.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
url (Optional[str]): The API base URL
|
|
72
|
+
api_key (Optional[str]): The API key for authentication
|
|
73
|
+
verbose (bool): Whether to enable verbose logging
|
|
74
|
+
"""
|
|
75
|
+
self._async_client = AsyncClient(url=url, api_key=api_key, verbose=verbose)
|
|
76
|
+
self._context_entered = False
|
|
77
|
+
self._closed = False
|
|
78
|
+
|
|
79
|
+
# Initialize endpoint managers
|
|
80
|
+
self._init_endpoints()
|
|
81
|
+
|
|
82
|
+
# Register cleanup on exit
|
|
83
|
+
import atexit
|
|
84
|
+
atexit.register(self._cleanup)
|
|
85
|
+
|
|
86
|
+
def __getattr__(self, name):
|
|
87
|
+
"""
|
|
88
|
+
Delegate attribute access to the underlying async client.
|
|
89
|
+
"""
|
|
90
|
+
if hasattr(self._async_client, name):
|
|
91
|
+
attr = getattr(self._async_client, name)
|
|
92
|
+
|
|
93
|
+
# If it's a callable (method), wrap it to run synchronously
|
|
94
|
+
if callable(attr):
|
|
95
|
+
def sync_method(*args, **kwargs):
|
|
96
|
+
result = attr(*args, **kwargs)
|
|
97
|
+
# If the result is a coroutine, run it synchronously
|
|
98
|
+
if hasattr(result, '__await__'):
|
|
99
|
+
return self._run_async(result)
|
|
100
|
+
return result
|
|
101
|
+
return sync_method
|
|
102
|
+
|
|
103
|
+
# If it's not a callable (like a property), return as-is
|
|
104
|
+
return attr
|
|
105
|
+
|
|
106
|
+
# If the attribute doesn't exist, raise AttributeError
|
|
107
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
|
108
|
+
|
|
109
|
+
async def _ensure_context(self):
|
|
110
|
+
"""Ensure the async client context is entered."""
|
|
111
|
+
if not self._context_entered and not self._closed:
|
|
112
|
+
await self._async_client.__aenter__()
|
|
113
|
+
self._context_entered = True
|
|
114
|
+
|
|
115
|
+
async def _exit_context(self):
|
|
116
|
+
"""Exit the async client context."""
|
|
117
|
+
if self._context_entered and not self._closed:
|
|
118
|
+
await self._async_client.__aexit__(None, None, None)
|
|
119
|
+
self._context_entered = False
|
|
120
|
+
|
|
121
|
+
def _run_async(self, coro):
|
|
122
|
+
"""
|
|
123
|
+
Run an async coroutine and return the result synchronously.
|
|
124
|
+
Ensures the AsyncClient context is properly managed.
|
|
125
|
+
"""
|
|
126
|
+
async def run_with_context():
|
|
127
|
+
await self._ensure_context()
|
|
128
|
+
return await coro
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
loop = asyncio.get_running_loop()
|
|
132
|
+
# If we're already in an async context, run in a new thread
|
|
133
|
+
import concurrent.futures
|
|
134
|
+
|
|
135
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
136
|
+
future = executor.submit(asyncio.run, run_with_context())
|
|
137
|
+
return future.result()
|
|
138
|
+
except RuntimeError:
|
|
139
|
+
# No running loop, create a new one
|
|
140
|
+
return asyncio.run(run_with_context())
|
|
141
|
+
|
|
142
|
+
def _run_async_single_use(self, coro):
|
|
143
|
+
"""
|
|
144
|
+
Run an async coroutine with its own context manager for one-off operations.
|
|
145
|
+
This is useful when you don't want to maintain a persistent session.
|
|
146
|
+
"""
|
|
147
|
+
async def run_with_own_context():
|
|
148
|
+
async with self._async_client:
|
|
149
|
+
return await coro
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
loop = asyncio.get_running_loop()
|
|
153
|
+
# If we're already in an async context, run in a new thread
|
|
154
|
+
import concurrent.futures
|
|
155
|
+
|
|
156
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
157
|
+
future = executor.submit(asyncio.run, run_with_own_context())
|
|
158
|
+
return future.result()
|
|
159
|
+
except RuntimeError:
|
|
160
|
+
# No running loop, create a new one
|
|
161
|
+
return asyncio.run(run_with_own_context())
|
|
162
|
+
|
|
163
|
+
def geoquery(
|
|
164
|
+
self,
|
|
165
|
+
expr: str,
|
|
166
|
+
feature: Union[Dict[str, Any], ShapelyGeometry],
|
|
167
|
+
in_crs: str = "epsg:4326",
|
|
168
|
+
out_crs: str = "epsg:4326",
|
|
169
|
+
output: str = "csv",
|
|
170
|
+
resolution: int = -1,
|
|
171
|
+
geom_fix: bool = False,
|
|
172
|
+
**kwargs
|
|
173
|
+
):
|
|
174
|
+
"""
|
|
175
|
+
Compute WCS query for a single geometry (synchronous version).
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
expr (str): The WCS expression to evaluate
|
|
179
|
+
feature (Union[Dict[str, Any], ShapelyGeometry]): The geographic feature
|
|
180
|
+
in_crs (str): Input coordinate reference system
|
|
181
|
+
out_crs (str): Output coordinate reference system
|
|
182
|
+
output (str): Output format ('csv' or 'netcdf')
|
|
183
|
+
resolution (int): Resolution parameter
|
|
184
|
+
geom_fix (bool): Whether to fix the geometry (default False)
|
|
185
|
+
**kwargs: Additional parameters to pass to the WCS request
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Union[pd.DataFrame, xr.Dataset, bytes]: The response data in the requested format
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
APIError: If the API request fails
|
|
192
|
+
"""
|
|
193
|
+
coro = self._async_client.geoquery(
|
|
194
|
+
expr=expr,
|
|
195
|
+
feature=feature,
|
|
196
|
+
in_crs=in_crs,
|
|
197
|
+
out_crs=out_crs,
|
|
198
|
+
output=output,
|
|
199
|
+
resolution=resolution,
|
|
200
|
+
geom_fix=geom_fix,
|
|
201
|
+
**kwargs
|
|
202
|
+
)
|
|
203
|
+
return self._run_async(coro)
|
|
204
|
+
|
|
205
|
+
def zonal_stats(
|
|
206
|
+
self,
|
|
207
|
+
gdf: GeoDataFrame,
|
|
208
|
+
expr: str,
|
|
209
|
+
conc: int = 20,
|
|
210
|
+
inplace: bool = False,
|
|
211
|
+
in_crs: str = "epsg:4326",
|
|
212
|
+
out_crs: str = "epsg:4326",
|
|
213
|
+
resolution: int = -1,
|
|
214
|
+
geom_fix: bool = False,
|
|
215
|
+
):
|
|
216
|
+
"""
|
|
217
|
+
Compute zonal statistics for all geometries in a GeoDataFrame (synchronous version).
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
gdf (GeoDataFrame): GeoDataFrame containing geometries
|
|
221
|
+
expr (str): Terrakio expression to evaluate, can include spatial aggregations
|
|
222
|
+
conc (int): Number of concurrent requests to make
|
|
223
|
+
inplace (bool): Whether to modify the input GeoDataFrame in place
|
|
224
|
+
in_crs (str): Input coordinate reference system
|
|
225
|
+
out_crs (str): Output coordinate reference system
|
|
226
|
+
resolution (int): Resolution parameter
|
|
227
|
+
geom_fix (bool): Whether to fix the geometry (default False)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
geopandas.GeoDataFrame: GeoDataFrame with added columns for results, or None if inplace=True
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
ValueError: If concurrency is too high
|
|
234
|
+
APIError: If the API request fails
|
|
235
|
+
"""
|
|
236
|
+
coro = self._async_client.zonal_stats(
|
|
237
|
+
gdf=gdf,
|
|
238
|
+
expr=expr,
|
|
239
|
+
conc=conc,
|
|
240
|
+
inplace=inplace,
|
|
241
|
+
in_crs=in_crs,
|
|
242
|
+
out_crs=out_crs,
|
|
243
|
+
resolution=resolution,
|
|
244
|
+
geom_fix=geom_fix
|
|
245
|
+
)
|
|
246
|
+
return self._run_async(coro)
|
|
247
|
+
|
|
248
|
+
def create_dataset_file(
|
|
249
|
+
self,
|
|
250
|
+
aoi: str,
|
|
251
|
+
expression: str,
|
|
252
|
+
output: str,
|
|
253
|
+
in_crs: str = "epsg:4326",
|
|
254
|
+
res: float = 0.0001,
|
|
255
|
+
region: str = "aus",
|
|
256
|
+
to_crs: str = "epsg:4326",
|
|
257
|
+
overwrite: bool = True,
|
|
258
|
+
skip_existing: bool = False,
|
|
259
|
+
non_interactive: bool = True,
|
|
260
|
+
poll_interval: int = 30,
|
|
261
|
+
download_path: str = "/home/user/Downloads",
|
|
262
|
+
) -> dict:
|
|
263
|
+
"""
|
|
264
|
+
Create a dataset file using mass stats operations (synchronous version).
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
aoi (str): Area of interest
|
|
268
|
+
expression (str): Terrakio expression to evaluate
|
|
269
|
+
output (str): Output format
|
|
270
|
+
in_crs (str): Input coordinate reference system (default "epsg:4326")
|
|
271
|
+
res (float): Resolution (default 0.0001)
|
|
272
|
+
region (str): Region (default "aus")
|
|
273
|
+
to_crs (str): Target coordinate reference system (default "epsg:4326")
|
|
274
|
+
overwrite (bool): Whether to overwrite existing files (default True)
|
|
275
|
+
skip_existing (bool): Whether to skip existing files (default False)
|
|
276
|
+
non_interactive (bool): Whether to run non-interactively (default True)
|
|
277
|
+
poll_interval (int): Polling interval in seconds (default 30)
|
|
278
|
+
download_path (str): Download path (default "/home/user/Downloads")
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
dict: Dictionary containing generation_task_id and combine_task_id
|
|
282
|
+
|
|
283
|
+
Raises:
|
|
284
|
+
ConfigurationError: If mass stats client is not properly configured
|
|
285
|
+
RuntimeError: If job fails
|
|
286
|
+
"""
|
|
287
|
+
coro = self._async_client.create_dataset_file(
|
|
288
|
+
aoi=aoi,
|
|
289
|
+
expression=expression,
|
|
290
|
+
output=output,
|
|
291
|
+
in_crs=in_crs,
|
|
292
|
+
res=res,
|
|
293
|
+
region=region,
|
|
294
|
+
to_crs=to_crs,
|
|
295
|
+
overwrite=overwrite,
|
|
296
|
+
skip_existing=skip_existing,
|
|
297
|
+
non_interactive=non_interactive,
|
|
298
|
+
poll_interval=poll_interval,
|
|
299
|
+
download_path=download_path,
|
|
300
|
+
)
|
|
301
|
+
return self._run_async(coro)
|
|
302
|
+
|
|
303
|
+
def close(self):
|
|
304
|
+
"""
|
|
305
|
+
Close the underlying async client session (synchronous version).
|
|
306
|
+
"""
|
|
307
|
+
if not self._closed:
|
|
308
|
+
async def close_async():
|
|
309
|
+
await self._exit_context()
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
loop = asyncio.get_running_loop()
|
|
313
|
+
import concurrent.futures
|
|
314
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
315
|
+
future = executor.submit(asyncio.run, close_async())
|
|
316
|
+
future.result()
|
|
317
|
+
except RuntimeError:
|
|
318
|
+
asyncio.run(close_async())
|
|
319
|
+
|
|
320
|
+
self._closed = True
|
|
321
|
+
|
|
322
|
+
def _cleanup(self):
|
|
323
|
+
"""Internal cleanup method called by atexit."""
|
|
324
|
+
if not self._closed:
|
|
325
|
+
try:
|
|
326
|
+
self.close()
|
|
327
|
+
except Exception:
|
|
328
|
+
# Ignore errors during cleanup
|
|
329
|
+
pass
|
|
330
|
+
|
|
331
|
+
def __enter__(self):
|
|
332
|
+
"""Context manager entry."""
|
|
333
|
+
# Ensure context is entered when used as context manager
|
|
334
|
+
async def enter_async():
|
|
335
|
+
await self._ensure_context()
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
loop = asyncio.get_running_loop()
|
|
339
|
+
import concurrent.futures
|
|
340
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
341
|
+
future = executor.submit(asyncio.run, enter_async())
|
|
342
|
+
future.result()
|
|
343
|
+
except RuntimeError:
|
|
344
|
+
asyncio.run(enter_async())
|
|
345
|
+
|
|
346
|
+
return self
|
|
347
|
+
|
|
348
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
349
|
+
"""Context manager exit."""
|
|
350
|
+
self.close()
|
|
351
|
+
|
|
352
|
+
def __del__(self):
|
|
353
|
+
"""Destructor to ensure session is closed when object is garbage collected."""
|
|
354
|
+
if not self._closed:
|
|
355
|
+
try:
|
|
356
|
+
self._cleanup()
|
|
357
|
+
except Exception:
|
|
358
|
+
# If we can't close gracefully, ignore the error during cleanup
|
|
359
|
+
pass
|
|
360
|
+
|
|
361
|
+
# Initialize endpoint managers (these can be overridden by subclasses)
|
|
362
|
+
def _init_endpoints(self):
|
|
363
|
+
"""Initialize endpoint managers. Can be overridden by subclasses."""
|
|
364
|
+
self.datasets = SyncWrapper(self._async_client.datasets, self)
|
|
365
|
+
self.users = SyncWrapper(self._async_client.users, self)
|
|
366
|
+
self.mass_stats = SyncWrapper(self._async_client.mass_stats, self)
|
|
367
|
+
self.groups = SyncWrapper(self._async_client.groups, self)
|
|
368
|
+
self.space = SyncWrapper(self._async_client.space, self)
|
|
369
|
+
self.model = SyncWrapper(self._async_client.model, self)
|
|
370
|
+
self.auth = SyncWrapper(self._async_client.auth, self)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: terrakio-core
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Core components for Terrakio API clients
|
|
5
5
|
Author-email: Yupeng Chao <yupeng@haizea.com.au>
|
|
6
6
|
Project-URL: Homepage, https://github.com/HaizeaAnalytics/terrakio-python-api
|
|
@@ -23,6 +23,12 @@ Requires-Dist: shapely>=2.0.0
|
|
|
23
23
|
Requires-Dist: geopandas>=0.13.0
|
|
24
24
|
Requires-Dist: google-cloud-storage>=2.0.0
|
|
25
25
|
Requires-Dist: nest_asyncio
|
|
26
|
+
Provides-Extra: ml
|
|
27
|
+
Requires-Dist: torch>=2.7.1; extra == "ml"
|
|
28
|
+
Requires-Dist: scikit-learn>=1.7.0; extra == "ml"
|
|
29
|
+
Requires-Dist: skl2onnx>=1.19.1; extra == "ml"
|
|
30
|
+
Requires-Dist: onnx>=1.18.0; extra == "ml"
|
|
31
|
+
Requires-Dist: onnxruntime>=1.10.0; extra == "ml"
|
|
26
32
|
|
|
27
33
|
# Terrakio Core
|
|
28
34
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
terrakio_core/__init__.py,sha256=AhgqIBhUwFipuLFRbhkgYJ_LmAUEmDDsSjSXSagn2aE,242
|
|
2
|
+
terrakio_core/async_client.py,sha256=Lcj_PjhefyWuqUfJkJwgJyP-LmSWCOdv1t3QvnYnefg,12587
|
|
3
|
+
terrakio_core/client.py,sha256=h8GW88g6RlGwNFW6MW48c_3BnaeT9nSd19LI1jCn1GU,1008
|
|
4
|
+
terrakio_core/config.py,sha256=r8NARVYOca4AuM88VP_j-8wQxOk1s7VcRdyEdseBlLE,4193
|
|
5
|
+
terrakio_core/exceptions.py,sha256=4qnpOM1gOxsNIXDXY4qwY1d3I4Myhp7HBh7b2D0SVrU,529
|
|
6
|
+
terrakio_core/sync_client.py,sha256=v1mcBtUaKWACqZgw8dTTVPMxUfKfiY0kjtBKzDwtGTU,13634
|
|
7
|
+
terrakio_core/convenience_functions/convenience_functions.py,sha256=U7bLGwfBF-FUYc0nv49pAViPsBQ6LgPlV6c6b-zeKo8,10616
|
|
8
|
+
terrakio_core/endpoints/auth.py,sha256=e_hdNE6JOGhRVlQMFdEoOmoMHp5EzK6CclOEnc_AmZw,5863
|
|
9
|
+
terrakio_core/endpoints/dataset_management.py,sha256=BUm8IIlW_Q45vDiQp16CiJGeSLheI8uWRVRQtMdhaNk,13161
|
|
10
|
+
terrakio_core/endpoints/group_management.py,sha256=VFl3jakjQa9OPi351D3DZvLU9M7fHdfjCzGhmyJsx3U,6309
|
|
11
|
+
terrakio_core/endpoints/mass_stats.py,sha256=y1w3QLkDD0sKP1tBcFDqgLYLNxX94I-LYbNotaKhLYM,21356
|
|
12
|
+
terrakio_core/endpoints/model_management.py,sha256=Q2bqsVfBILu-hZVw1tr5WjOR68qoYF6m326YJXgAOeo,33886
|
|
13
|
+
terrakio_core/endpoints/space_management.py,sha256=YWb55nkJnFJGlALJ520DvurxDqVqwYtsvqQPWzxzhDs,2266
|
|
14
|
+
terrakio_core/endpoints/user_management.py,sha256=x0JW6VET7eokngmkhZPukegxoJNR1X09BVehJt2nIdI,3781
|
|
15
|
+
terrakio_core/helper/bounded_taskgroup.py,sha256=wiTH10jhKZgrsgrFUNG6gig8bFkUEPHkGRT2XY7Rgmo,677
|
|
16
|
+
terrakio_core/helper/decorators.py,sha256=L6om7wmWNgCei3Wy5U0aZ-70OzsCwclkjIf7SfQuhCg,2289
|
|
17
|
+
terrakio_core/helper/tiles.py,sha256=xNtp3oDD912PN_FQV5fb6uQYhwfHANuXyIcxoVCCfZU,2632
|
|
18
|
+
terrakio_core-0.3.7.dist-info/METADATA,sha256=duExFAn2EiG-5A4UMkaz_M5Piofh3vSLhuFVqoo7Nfk,1728
|
|
19
|
+
terrakio_core-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
terrakio_core-0.3.7.dist-info/top_level.txt,sha256=5cBj6O7rNWyn97ND4YuvvXm0Crv4RxttT4JZvNdOG6Q,14
|
|
21
|
+
terrakio_core-0.3.7.dist-info/RECORD,,
|
terrakio_core/auth.py
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from typing import Optional, Dict, Any
|
|
3
|
-
from .exceptions import APIError, ConfigurationError
|
|
4
|
-
|
|
5
|
-
class AuthClient:
|
|
6
|
-
def __init__(self, base_url: str = "https://dev-au.terrak.io",
|
|
7
|
-
verify: bool = True, timeout: int = 60):
|
|
8
|
-
"""
|
|
9
|
-
Initialize the Authentication Client for Terrakio API.
|
|
10
|
-
|
|
11
|
-
Args:
|
|
12
|
-
base_url: Authentication API base URL
|
|
13
|
-
verify: Verify SSL certificates
|
|
14
|
-
timeout: Request timeout in seconds
|
|
15
|
-
"""
|
|
16
|
-
self.base_url = base_url.rstrip('/')
|
|
17
|
-
self.verify = verify
|
|
18
|
-
self.timeout = timeout
|
|
19
|
-
self.session = requests.Session()
|
|
20
|
-
self.session.headers.update({
|
|
21
|
-
'Content-Type': 'application/json'
|
|
22
|
-
})
|
|
23
|
-
self.token = None
|
|
24
|
-
self.api_key = None
|
|
25
|
-
|
|
26
|
-
def signup(self, email: str, password: str) -> Dict[str, Any]:
|
|
27
|
-
"""
|
|
28
|
-
Register a new user account.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
email: User email address
|
|
32
|
-
password: User password
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
API response data
|
|
36
|
-
|
|
37
|
-
Raises:
|
|
38
|
-
APIError: If signup fails
|
|
39
|
-
"""
|
|
40
|
-
endpoint = f"{self.base_url}/users/signup"
|
|
41
|
-
|
|
42
|
-
payload = {
|
|
43
|
-
"email": email,
|
|
44
|
-
"password": password
|
|
45
|
-
}
|
|
46
|
-
print("the payload is ", payload)
|
|
47
|
-
print("the endpoint is ", endpoint)
|
|
48
|
-
try:
|
|
49
|
-
response = self.session.post(
|
|
50
|
-
endpoint,
|
|
51
|
-
json=payload,
|
|
52
|
-
verify=self.verify,
|
|
53
|
-
timeout=self.timeout
|
|
54
|
-
)
|
|
55
|
-
print("the response is ", response)
|
|
56
|
-
if not response.ok:
|
|
57
|
-
error_msg = f"Signup failed: {response.status_code} {response.reason}"
|
|
58
|
-
try:
|
|
59
|
-
error_data = response.json()
|
|
60
|
-
if "detail" in error_data:
|
|
61
|
-
error_msg += f" - {error_data['detail']}"
|
|
62
|
-
except:
|
|
63
|
-
pass
|
|
64
|
-
raise APIError(error_msg)
|
|
65
|
-
|
|
66
|
-
return response.json()
|
|
67
|
-
except requests.RequestException as e:
|
|
68
|
-
raise APIError(f"Signup request failed: {str(e)}")
|
|
69
|
-
|
|
70
|
-
def login(self, email: str, password: str) -> str:
|
|
71
|
-
"""
|
|
72
|
-
Log in and obtain authentication token.
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
email: User email address
|
|
76
|
-
password: User password
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
Authentication token
|
|
80
|
-
|
|
81
|
-
Raises:
|
|
82
|
-
APIError: If login fails
|
|
83
|
-
"""
|
|
84
|
-
endpoint = f"{self.base_url}/users/login"
|
|
85
|
-
|
|
86
|
-
payload = {
|
|
87
|
-
"email": email,
|
|
88
|
-
"password": password
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try:
|
|
92
|
-
response = self.session.post(
|
|
93
|
-
endpoint,
|
|
94
|
-
json=payload,
|
|
95
|
-
verify=self.verify,
|
|
96
|
-
timeout=self.timeout
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
if not response.ok:
|
|
100
|
-
error_msg = f"Login failed: {response.status_code} {response.reason}"
|
|
101
|
-
try:
|
|
102
|
-
error_data = response.json()
|
|
103
|
-
if "detail" in error_data:
|
|
104
|
-
error_msg += f" - {error_data['detail']}"
|
|
105
|
-
except:
|
|
106
|
-
pass
|
|
107
|
-
raise APIError(error_msg)
|
|
108
|
-
|
|
109
|
-
result = response.json()
|
|
110
|
-
self.token = result.get("token")
|
|
111
|
-
|
|
112
|
-
# Update session with authorization header
|
|
113
|
-
if self.token:
|
|
114
|
-
self.session.headers.update({
|
|
115
|
-
"Authorization": self.token
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
return self.token
|
|
119
|
-
except requests.RequestException as e:
|
|
120
|
-
raise APIError(f"Login request failed: {str(e)}")
|
|
121
|
-
|
|
122
|
-
def refresh_api_key(self) -> str:
|
|
123
|
-
"""
|
|
124
|
-
Generate or refresh API key.
|
|
125
|
-
|
|
126
|
-
Returns:
|
|
127
|
-
API key
|
|
128
|
-
|
|
129
|
-
Raises:
|
|
130
|
-
APIError: If refresh fails
|
|
131
|
-
"""
|
|
132
|
-
endpoint = f"{self.base_url}/users/refresh_key"
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
# Use session with updated headers from login
|
|
136
|
-
response = self.session.post(
|
|
137
|
-
endpoint,
|
|
138
|
-
verify=self.verify,
|
|
139
|
-
timeout=self.timeout
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
if not response.ok:
|
|
143
|
-
error_msg = f"API key generation failed: {response.status_code} {response.reason}"
|
|
144
|
-
try:
|
|
145
|
-
error_data = response.json()
|
|
146
|
-
if "detail" in error_data:
|
|
147
|
-
error_msg += f" - {error_data['detail']}"
|
|
148
|
-
except:
|
|
149
|
-
pass
|
|
150
|
-
raise APIError(error_msg)
|
|
151
|
-
|
|
152
|
-
result = response.json()
|
|
153
|
-
self.api_key = result.get("apiKey")
|
|
154
|
-
return self.api_key
|
|
155
|
-
except requests.RequestException as e:
|
|
156
|
-
raise APIError(f"API key refresh request failed: {str(e)}")
|
|
157
|
-
|
|
158
|
-
def view_api_key(self) -> str:
|
|
159
|
-
"""
|
|
160
|
-
Retrieve current API key.
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
API key
|
|
164
|
-
|
|
165
|
-
Raises:
|
|
166
|
-
APIError: If retrieval fails
|
|
167
|
-
"""
|
|
168
|
-
endpoint = f"{self.base_url}/users/key"
|
|
169
|
-
try:
|
|
170
|
-
# Use session with updated headers from login
|
|
171
|
-
response = self.session.get(
|
|
172
|
-
endpoint,
|
|
173
|
-
verify=self.verify,
|
|
174
|
-
timeout=self.timeout
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
if not response.ok:
|
|
178
|
-
error_msg = f"Failed to retrieve API key: {response.status_code} {response.reason}"
|
|
179
|
-
try:
|
|
180
|
-
error_data = response.json()
|
|
181
|
-
if "detail" in error_data:
|
|
182
|
-
error_msg += f" - {error_data['detail']}"
|
|
183
|
-
except:
|
|
184
|
-
pass
|
|
185
|
-
raise APIError(error_msg)
|
|
186
|
-
|
|
187
|
-
result = response.json()
|
|
188
|
-
self.api_key = result.get("apiKey")
|
|
189
|
-
return self.api_key
|
|
190
|
-
except requests.RequestException as e:
|
|
191
|
-
raise APIError(f"API key retrieval request failed: {str(e)}")
|
|
192
|
-
|
|
193
|
-
def get_user_info(self) -> Dict[str, Any]:
|
|
194
|
-
"""
|
|
195
|
-
Retrieve the current user's information.
|
|
196
|
-
|
|
197
|
-
Returns:
|
|
198
|
-
User information data
|
|
199
|
-
|
|
200
|
-
Raises:
|
|
201
|
-
APIError: If retrieval fails
|
|
202
|
-
"""
|
|
203
|
-
endpoint = f"{self.base_url}/users/info"
|
|
204
|
-
try:
|
|
205
|
-
# Use session with updated headers from login
|
|
206
|
-
response = self.session.get(
|
|
207
|
-
endpoint,
|
|
208
|
-
verify=self.verify,
|
|
209
|
-
timeout=self.timeout
|
|
210
|
-
)
|
|
211
|
-
if not response.ok:
|
|
212
|
-
error_msg = f"Failed to retrieve user info: {response.status_code} {response.reason}"
|
|
213
|
-
try:
|
|
214
|
-
error_data = response.json()
|
|
215
|
-
if "detail" in error_data:
|
|
216
|
-
error_msg += f" - {error_data['detail']}"
|
|
217
|
-
except:
|
|
218
|
-
pass
|
|
219
|
-
raise APIError(error_msg)
|
|
220
|
-
|
|
221
|
-
return response.json()
|
|
222
|
-
except requests.RequestException as e:
|
|
223
|
-
raise APIError(f"User info retrieval request failed: {str(e)}")
|