terrakio-core 0.4.3__py3-none-any.whl → 0.4.5__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 +3 -1
- terrakio_core/accessors.py +477 -0
- terrakio_core/async_client.py +67 -39
- terrakio_core/client.py +83 -84
- terrakio_core/convenience_functions/convenience_functions.py +316 -324
- terrakio_core/endpoints/auth.py +8 -1
- terrakio_core/endpoints/mass_stats.py +13 -9
- terrakio_core/endpoints/model_management.py +604 -948
- terrakio_core/sync_client.py +436 -44
- {terrakio_core-0.4.3.dist-info → terrakio_core-0.4.5.dist-info}/METADATA +2 -1
- terrakio_core-0.4.5.dist-info/RECORD +22 -0
- terrakio_core-0.4.3.dist-info/RECORD +0 -21
- {terrakio_core-0.4.3.dist-info → terrakio_core-0.4.5.dist-info}/WHEEL +0 -0
- {terrakio_core-0.4.3.dist-info → terrakio_core-0.4.5.dist-info}/top_level.txt +0 -0
terrakio_core/async_client.py
CHANGED
|
@@ -17,7 +17,7 @@ from .endpoints.group_management import GroupManagement
|
|
|
17
17
|
from .endpoints.space_management import SpaceManagement
|
|
18
18
|
from .endpoints.model_management import ModelManagement
|
|
19
19
|
from .endpoints.auth import AuthClient
|
|
20
|
-
from .convenience_functions.convenience_functions import zonal_stats as _zonal_stats, create_dataset_file as _create_dataset_file
|
|
20
|
+
from .convenience_functions.convenience_functions import zonal_stats as _zonal_stats, create_dataset_file as _create_dataset_file, request_geoquery_list as _request_geoquery_list
|
|
21
21
|
|
|
22
22
|
class AsyncClient(BaseClient):
|
|
23
23
|
def __init__(self, url: Optional[str] = None, api_key: Optional[str] = None, verbose: bool = False, session: Optional[aiohttp.ClientSession] = None):
|
|
@@ -33,7 +33,6 @@ class AsyncClient(BaseClient):
|
|
|
33
33
|
self._owns_session = session is None
|
|
34
34
|
|
|
35
35
|
async def _terrakio_request(self, method: str, endpoint: str, **kwargs):
|
|
36
|
-
# if self.session is None:
|
|
37
36
|
if self.session is None:
|
|
38
37
|
headers = {
|
|
39
38
|
'Content-Type': 'application/json',
|
|
@@ -49,7 +48,6 @@ class AsyncClient(BaseClient):
|
|
|
49
48
|
async def _make_request_with_retry(self, session: aiohttp.ClientSession, method: str, endpoint: str, **kwargs) -> Dict[Any, Any]:
|
|
50
49
|
url = f"{self.url}/{endpoint.lstrip('/')}"
|
|
51
50
|
last_exception = None
|
|
52
|
-
|
|
53
51
|
for attempt in range(self.retry + 1):
|
|
54
52
|
try:
|
|
55
53
|
async with session.request(method, url, **kwargs) as response:
|
|
@@ -103,23 +101,38 @@ class AsyncClient(BaseClient):
|
|
|
103
101
|
return xr.open_dataset(BytesIO(content))
|
|
104
102
|
except:
|
|
105
103
|
raise APIError(f"Unknown response format: {content_type}", status_code=response.status)
|
|
106
|
-
|
|
104
|
+
|
|
107
105
|
async def _regular_request(self, method: str, endpoint: str, **kwargs):
|
|
108
106
|
url = endpoint.lstrip('/')
|
|
107
|
+
|
|
109
108
|
if self._session is None:
|
|
110
|
-
|
|
111
109
|
async with aiohttp.ClientSession() as session:
|
|
112
110
|
try:
|
|
113
111
|
async with session.request(method, url, **kwargs) as response:
|
|
114
112
|
response.raise_for_status()
|
|
115
|
-
|
|
113
|
+
|
|
114
|
+
content = await response.read()
|
|
115
|
+
|
|
116
|
+
return type('Response', (), {
|
|
117
|
+
'status': response.status,
|
|
118
|
+
'content': content,
|
|
119
|
+
'text': lambda: content.decode('utf-8'),
|
|
120
|
+
'json': lambda: json.loads(content.decode('utf-8'))
|
|
121
|
+
})()
|
|
116
122
|
except aiohttp.ClientError as e:
|
|
117
123
|
raise APIError(f"Request failed: {e}")
|
|
118
124
|
else:
|
|
119
125
|
try:
|
|
120
126
|
async with self._session.request(method, url, **kwargs) as response:
|
|
121
127
|
response.raise_for_status()
|
|
122
|
-
|
|
128
|
+
content = await response.read()
|
|
129
|
+
|
|
130
|
+
return type('Response', (), {
|
|
131
|
+
'status': response.status,
|
|
132
|
+
'content': content,
|
|
133
|
+
'text': lambda: content.decode('utf-8'),
|
|
134
|
+
'json': lambda: json.loads(content.decode('utf-8'))
|
|
135
|
+
})()
|
|
123
136
|
except aiohttp.ClientError as e:
|
|
124
137
|
raise APIError(f"Request failed: {e}")
|
|
125
138
|
|
|
@@ -170,23 +183,19 @@ class AsyncClient(BaseClient):
|
|
|
170
183
|
"validated": validated,
|
|
171
184
|
**kwargs
|
|
172
185
|
}
|
|
173
|
-
|
|
186
|
+
result = await self._terrakio_request("POST", "geoquery", json=payload)
|
|
187
|
+
|
|
188
|
+
return result
|
|
174
189
|
|
|
175
190
|
async def zonal_stats(
|
|
176
191
|
self,
|
|
177
192
|
gdf: GeoDataFrame,
|
|
178
193
|
expr: str,
|
|
179
194
|
conc: int = 20,
|
|
180
|
-
inplace: bool = False,
|
|
181
195
|
in_crs: str = "epsg:4326",
|
|
182
196
|
out_crs: str = "epsg:4326",
|
|
183
197
|
resolution: int = -1,
|
|
184
198
|
geom_fix: bool = False,
|
|
185
|
-
drop_nan: bool = False,
|
|
186
|
-
spatial_reduction: str = None,
|
|
187
|
-
temporal_reduction: str = None,
|
|
188
|
-
max_memory_mb: int = 500,
|
|
189
|
-
stream_to_disk: bool = False,
|
|
190
199
|
):
|
|
191
200
|
"""
|
|
192
201
|
Compute zonal statistics for all geometries in a GeoDataFrame.
|
|
@@ -195,22 +204,13 @@ class AsyncClient(BaseClient):
|
|
|
195
204
|
gdf (GeoDataFrame): GeoDataFrame containing geometries
|
|
196
205
|
expr (str): Terrakio expression to evaluate, can include spatial aggregations
|
|
197
206
|
conc (int): Number of concurrent requests to make
|
|
198
|
-
inplace (bool): Whether to modify the input GeoDataFrame in place
|
|
199
207
|
in_crs (str): Input coordinate reference system
|
|
200
208
|
out_crs (str): Output coordinate reference system
|
|
201
209
|
resolution (int): Resolution parameter
|
|
202
210
|
geom_fix (bool): Whether to fix the geometry (default False)
|
|
203
|
-
drop_nan (bool): Whether to drop NaN values from the results (default False)
|
|
204
|
-
spatial_reduction (str): Reduction operation for spatial dimensions (x, y).
|
|
205
|
-
Options: 'mean', 'median', 'min', 'max', 'std', 'var', 'sum', 'count'
|
|
206
|
-
temporal_reduction (str): Reduction operation for temporal dimension (time).
|
|
207
|
-
Options: 'mean', 'median', 'min', 'max', 'std', 'var', 'sum', 'count'
|
|
208
|
-
max_memory_mb (int): Maximum memory threshold in MB (default 500MB)
|
|
209
|
-
stream_to_disk (bool): Whether to stream datasets to disk as NetCDF files (default False)
|
|
210
211
|
|
|
211
212
|
Returns:
|
|
212
|
-
geopandas.GeoDataFrame: GeoDataFrame with added columns for results
|
|
213
|
-
If stream_to_disk=True, large datasets are saved as NetCDF files with file paths stored.
|
|
213
|
+
geopandas.GeoDataFrame: GeoDataFrame with added columns for results
|
|
214
214
|
|
|
215
215
|
Raises:
|
|
216
216
|
ValueError: If concurrency is too high or if data exceeds memory limit without streaming
|
|
@@ -221,16 +221,10 @@ class AsyncClient(BaseClient):
|
|
|
221
221
|
gdf=gdf,
|
|
222
222
|
expr=expr,
|
|
223
223
|
conc=conc,
|
|
224
|
-
inplace=inplace,
|
|
225
224
|
in_crs=in_crs,
|
|
226
225
|
out_crs=out_crs,
|
|
227
226
|
resolution=resolution,
|
|
228
227
|
geom_fix=geom_fix,
|
|
229
|
-
drop_nan=drop_nan,
|
|
230
|
-
spatial_reduction=spatial_reduction,
|
|
231
|
-
temporal_reduction=temporal_reduction,
|
|
232
|
-
max_memory_mb=max_memory_mb,
|
|
233
|
-
stream_to_disk=stream_to_disk
|
|
234
228
|
)
|
|
235
229
|
|
|
236
230
|
async def create_dataset_file(
|
|
@@ -288,33 +282,67 @@ class AsyncClient(BaseClient):
|
|
|
288
282
|
download_path=download_path,
|
|
289
283
|
)
|
|
290
284
|
|
|
285
|
+
async def geo_queries(
|
|
286
|
+
self,
|
|
287
|
+
queries: list[dict],
|
|
288
|
+
conc: int = 20,
|
|
289
|
+
):
|
|
290
|
+
"""
|
|
291
|
+
Execute multiple geo queries concurrently.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
queries (list[dict]): List of dictionaries containing query parameters.
|
|
295
|
+
Each query must have 'expr', 'feature', and 'in_crs' keys.
|
|
296
|
+
conc (int): Number of concurrent requests to make (default 20, max 100)
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Union[float, geopandas.GeoDataFrame]:
|
|
300
|
+
- float: Average of all results if results are integers
|
|
301
|
+
- GeoDataFrame: GeoDataFrame with geometry and dataset columns if results are xarray datasets
|
|
302
|
+
|
|
303
|
+
Raises:
|
|
304
|
+
ValueError: If queries list is empty, concurrency is too high, or queries are malformed
|
|
305
|
+
APIError: If the API request fails
|
|
306
|
+
|
|
307
|
+
Example:
|
|
308
|
+
queries = [
|
|
309
|
+
{
|
|
310
|
+
'expr': 'WCF.wcf',
|
|
311
|
+
'feature': {'type': 'Feature', 'geometry': {...}, 'properties': {}},
|
|
312
|
+
'in_crs': 'epsg:4326'
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
'expr': 'NDVI.ndvi',
|
|
316
|
+
'feature': {'type': 'Feature', 'geometry': {...}, 'properties': {}},
|
|
317
|
+
'in_crs': 'epsg:4326'
|
|
318
|
+
}
|
|
319
|
+
]
|
|
320
|
+
result = await client.geo_queries(queries)
|
|
321
|
+
"""
|
|
322
|
+
return await _request_geoquery_list(
|
|
323
|
+
client=self,
|
|
324
|
+
quries=queries, # Note: keeping original parameter name for compatibility
|
|
325
|
+
conc=conc,
|
|
326
|
+
)
|
|
327
|
+
|
|
291
328
|
async def __aenter__(self):
|
|
292
|
-
# if there is no session, we create a session
|
|
293
329
|
if self._session is None:
|
|
294
330
|
headers = {
|
|
295
331
|
'Content-Type': 'application/json',
|
|
296
332
|
'x-api-key': self.key,
|
|
297
333
|
'Authorization': self.token
|
|
298
334
|
}
|
|
299
|
-
# Remove None values from headers
|
|
300
335
|
clean_headers = {k: v for k, v in headers.items() if v is not None}
|
|
301
|
-
# we are creating the header and clean any value that is none
|
|
302
|
-
# now we create the session
|
|
303
336
|
self._session = aiohttp.ClientSession(
|
|
304
337
|
headers=clean_headers,
|
|
305
338
|
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
|
306
339
|
)
|
|
307
340
|
return self
|
|
308
|
-
# if there is no session, we create a session
|
|
309
341
|
|
|
310
|
-
# now lets create the aexit function, this function is used when a user uses with, and this function will be automatically called when the with statement is done
|
|
311
342
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
312
|
-
# so if the session is not being passed in, and we created it by ourselves, we are responsible for closing the session
|
|
313
|
-
# if the session is being passed in, we are not responsible for closing the session
|
|
314
343
|
if self._owns_session and self._session:
|
|
315
344
|
await self._session.close()
|
|
316
345
|
self._session = None
|
|
317
|
-
# we close the session and set the session value to none
|
|
318
346
|
|
|
319
347
|
async def close(self):
|
|
320
348
|
if self._owns_session and self._session:
|
terrakio_core/client.py
CHANGED
|
@@ -8,7 +8,6 @@ import xarray as xr
|
|
|
8
8
|
|
|
9
9
|
class BaseClient():
|
|
10
10
|
def __init__(self, url: Optional[str] = None, api_key: Optional[str] = None, verbose: bool = False):
|
|
11
|
-
|
|
12
11
|
self.verbose = verbose
|
|
13
12
|
self.logger = logging.getLogger("terrakio")
|
|
14
13
|
if verbose:
|
|
@@ -34,101 +33,101 @@ class BaseClient():
|
|
|
34
33
|
|
|
35
34
|
self.token = config.get('token')
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
# # Apply xarray printing fix to prevent crashes with GeoDataFrames
|
|
37
|
+
# self._apply_xarray_fix()
|
|
39
38
|
|
|
40
|
-
def _apply_xarray_fix(self):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
# def _apply_xarray_fix(self):
|
|
40
|
+
# """
|
|
41
|
+
# Apply xarray printing fix to prevent crashes when GeoDataFrames contain xarray objects.
|
|
42
|
+
# This fix is applied automatically when the client is initialized.
|
|
43
|
+
# """
|
|
44
|
+
# try:
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
# # Check if fix is already applied globally
|
|
47
|
+
# if hasattr(xr.DataArray, '_terrakio_fix_applied'):
|
|
48
|
+
# if self.verbose:
|
|
49
|
+
# self.logger.info("xarray printing fix already applied")
|
|
50
|
+
# return
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
# # Store original methods for potential restoration
|
|
53
|
+
# if not hasattr(xr.DataArray, '_original_iter'):
|
|
54
|
+
# xr.DataArray._original_iter = xr.DataArray.__iter__
|
|
55
|
+
# xr.Dataset._original_iter = xr.Dataset.__iter__
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
# # Define safe iteration methods that prevent pandas from iterating
|
|
58
|
+
# # but leave __repr__ and __str__ untouched for normal xarray printing
|
|
59
|
+
# def safe_dataarray_iter(self):
|
|
60
|
+
# # Return infinite iterator that always yields the same safe value
|
|
61
|
+
# name = getattr(self, 'name', None) or 'unnamed'
|
|
62
|
+
# shape_str = 'x'.join(map(str, self.shape)) if hasattr(self, 'shape') else 'unknown'
|
|
63
|
+
# placeholder = f"<DataArray '{name}' {shape_str}>"
|
|
64
|
+
# while True:
|
|
65
|
+
# yield placeholder
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
# def safe_dataset_iter(self):
|
|
68
|
+
# # Return infinite iterator that always yields the same safe value
|
|
69
|
+
# num_vars = len(self.data_vars) if hasattr(self, 'data_vars') else 0
|
|
70
|
+
# num_dims = len(self.dims) if hasattr(self, 'dims') else 0
|
|
71
|
+
# placeholder = f"<Dataset: {num_vars} vars, {num_dims} dims>"
|
|
72
|
+
# while True:
|
|
73
|
+
# yield placeholder
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
# # Apply only the iteration fix - leave __repr__ and __str__ untouched
|
|
76
|
+
# xr.DataArray.__iter__ = safe_dataarray_iter
|
|
77
|
+
# xr.Dataset.__iter__ = safe_dataset_iter
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
# # Mark as applied to avoid duplicate applications
|
|
80
|
+
# xr.DataArray._terrakio_fix_applied = True
|
|
81
|
+
# xr.Dataset._terrakio_fix_applied = True
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
# if self.verbose:
|
|
84
|
+
# self.logger.info("xarray iteration fix applied - GeoDataFrames with xarray objects will print safely, direct xarray printing unchanged")
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
# except ImportError:
|
|
87
|
+
# # xarray not installed, skip the fix
|
|
88
|
+
# if self.verbose:
|
|
89
|
+
# self.logger.info("xarray not installed, skipping printing fix")
|
|
90
|
+
# except Exception as e:
|
|
91
|
+
# # Log warning but don't fail initialization
|
|
92
|
+
# warning_msg = f"Failed to apply xarray printing fix: {e}"
|
|
93
|
+
# warnings.warn(warning_msg)
|
|
94
|
+
# if self.verbose:
|
|
95
|
+
# self.logger.warning(warning_msg)
|
|
97
96
|
|
|
98
|
-
def restore_xarray_printing(self):
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
# def restore_xarray_printing(self):
|
|
98
|
+
# """
|
|
99
|
+
# Restore original xarray printing behavior.
|
|
100
|
+
# Call this method if you want to see full xarray representations again.
|
|
101
|
+
# """
|
|
102
|
+
# try:
|
|
103
|
+
# import xarray as xr
|
|
105
104
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
# if hasattr(xr.DataArray, '_original_iter'):
|
|
106
|
+
# xr.DataArray.__iter__ = xr.DataArray._original_iter
|
|
107
|
+
# xr.Dataset.__iter__ = xr.Dataset._original_iter
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
# # Remove the fix markers
|
|
110
|
+
# if hasattr(xr.DataArray, '_terrakio_fix_applied'):
|
|
111
|
+
# delattr(xr.DataArray, '_terrakio_fix_applied')
|
|
112
|
+
# if hasattr(xr.Dataset, '_terrakio_fix_applied'):
|
|
113
|
+
# delattr(xr.Dataset, '_terrakio_fix_applied')
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
# if self.verbose:
|
|
116
|
+
# self.logger.info("Original xarray iteration behavior restored")
|
|
117
|
+
# else:
|
|
118
|
+
# if self.verbose:
|
|
119
|
+
# self.logger.info("No xarray fix to restore")
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
121
|
+
# except ImportError:
|
|
122
|
+
# if self.verbose:
|
|
123
|
+
# self.logger.info("xarray not available")
|
|
124
|
+
# except Exception as e:
|
|
125
|
+
# warning_msg = f"Failed to restore xarray printing: {e}"
|
|
126
|
+
# warnings.warn(warning_msg)
|
|
127
|
+
# if self.verbose:
|
|
128
|
+
# self.logger.warning(warning_msg)
|
|
130
129
|
|
|
131
|
-
@abstractmethod
|
|
132
|
-
def _setup_session(self):
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
# @abstractmethod
|
|
131
|
+
# def _setup_session(self):
|
|
132
|
+
# """Initialize the HTTP session - implemented by sync/async clients"""
|
|
133
|
+
# pass
|