terrakio-core 0.3.6__tar.gz → 0.3.8__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 (27) hide show
  1. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/PKG-INFO +7 -1
  2. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/pyproject.toml +10 -1
  3. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/__init__.py +1 -1
  4. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/async_client.py +50 -47
  5. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/endpoints/dataset_management.py +6 -4
  6. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/endpoints/mass_stats.py +5 -5
  7. terrakio_core-0.3.8/terrakio_core/endpoints/model_management.py +790 -0
  8. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/exceptions.py +4 -2
  9. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core.egg-info/PKG-INFO +7 -1
  10. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core.egg-info/requires.txt +7 -0
  11. terrakio_core-0.3.6/terrakio_core/endpoints/model_management.py +0 -385
  12. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/README.md +0 -0
  13. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/setup.cfg +0 -0
  14. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/client.py +0 -0
  15. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/config.py +0 -0
  16. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/convenience_functions/convenience_functions.py +0 -0
  17. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/endpoints/auth.py +0 -0
  18. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/endpoints/group_management.py +0 -0
  19. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/endpoints/space_management.py +0 -0
  20. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/endpoints/user_management.py +0 -0
  21. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/helper/bounded_taskgroup.py +0 -0
  22. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/helper/decorators.py +0 -0
  23. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/helper/tiles.py +0 -0
  24. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core/sync_client.py +0 -0
  25. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core.egg-info/SOURCES.txt +0 -0
  26. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/terrakio_core.egg-info/dependency_links.txt +0 -0
  27. {terrakio_core-0.3.6 → terrakio_core-0.3.8}/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.3.6
3
+ Version: 0.3.8
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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "terrakio-core"
7
- version = "0.3.6"
7
+ version = "0.3.8"
8
8
  authors = [
9
9
  {name = "Yupeng Chao", email = "yupeng@haizea.com.au"},
10
10
  ]
@@ -32,6 +32,15 @@ dependencies = [
32
32
  "nest_asyncio",
33
33
  ]
34
34
 
35
+ [project.optional-dependencies]
36
+ ml = [
37
+ "torch>=2.7.1",
38
+ "scikit-learn>=1.7.0",
39
+ "skl2onnx>=1.19.1",
40
+ "onnx>=1.18.0",
41
+ "onnxruntime>=1.10.0",
42
+ ]
43
+
35
44
  [project.urls]
36
45
  "Homepage" = "https://github.com/HaizeaAnalytics/terrakio-python-api"
37
46
  "Bug Tracker" = "https://github.com/HaizeaAnalytics/terrakio-python-api/issues"
@@ -5,7 +5,7 @@ Terrakio Core
5
5
  Core components for Terrakio API clients.
6
6
  """
7
7
 
8
- __version__ = "0.3.4"
8
+ __version__ = "0.3.8"
9
9
 
10
10
  from .async_client import AsyncClient
11
11
  from .sync_client import SyncClient
@@ -46,65 +46,65 @@ class AsyncClient(BaseClient):
46
46
  else:
47
47
  return await self._make_request_with_retry(self._session, method, endpoint, **kwargs)
48
48
 
49
-
50
49
  async def _make_request_with_retry(self, session: aiohttp.ClientSession, method: str, endpoint: str, **kwargs) -> Dict[Any, Any]:
51
50
  url = f"{self.url}/{endpoint.lstrip('/')}"
51
+ last_exception = None
52
+
52
53
  for attempt in range(self.retry + 1):
53
- try:
54
+ try:
54
55
  async with session.request(method, url, **kwargs) as response:
55
- response_text = await response.text()
56
+ if not response.ok and self._should_retry(response.status, attempt):
57
+ self.logger.info(f"Request failed (attempt {attempt+1}/{self.retry+1}): {response.status}. Retrying...")
58
+ continue
56
59
  if not response.ok:
57
- should_retry = False
58
-
59
- if response.status == 400:
60
- should_retry = False
61
- else:
62
- if response.status in [408, 502, 503, 504]:
63
- should_retry = True
64
- elif response.status == 500:
65
- try:
66
- response_text = await response.text()
67
- if "Internal server error" not in response_text:
68
- should_retry = True
69
- except:
70
- should_retry = True
71
-
72
- if should_retry and attempt < self.retry:
73
- self.logger.info(f"Request failed (attempt {attempt+1}/{self.retry+1}): {response.status} {response.reason}. Retrying...")
74
- continue
75
- else:
76
- error_msg = f"API request failed: {response.status} {response.reason}"
77
- try:
78
- error_data = await response.json()
79
- if "detail" in error_data:
80
- error_msg += f" - {error_data['detail']}"
81
- except:
82
- pass
83
- raise APIError(error_msg, status_code=response.status)
84
-
85
- content_type = response.headers.get('content-type', '').lower()
86
- content = await response.read()
87
- if 'json' in content_type:
88
- return json.loads(content.decode('utf-8'))
89
- elif 'csv' in content_type:
90
- return pd.read_csv(BytesIO(content))
91
- elif 'image/' in content_type:
92
- return content
93
- elif 'text' in content_type:
94
- return content.decode('utf-8')
95
- else:
60
+ error_msg = f"API request failed: {response.status} {response.reason}"
96
61
  try:
97
- return xr.open_dataset(BytesIO(content))
62
+ error_data = await response.json()
63
+ if "detail" in error_data:
64
+ error_msg += f" - {error_data['detail']}"
98
65
  except:
99
- raise APIError(f"Unknown response format. Content-Type: {response.headers.get('content-type', 'unknown')}", status_code=response.status)
66
+ pass
67
+ raise APIError(error_msg, status_code=response.status)
68
+ return await self._parse_response(response)
69
+
100
70
  except aiohttp.ClientError as e:
71
+ last_exception = e
101
72
  if attempt < self.retry:
102
- self.logger.info(f"Request failed (attempt {attempt+1}/{self.retry+1}): {e}. Retrying...")
73
+ self.logger.info(f"Networking error (attempt {attempt+1}/{self.retry+1}): {e}. Retrying...")
103
74
  continue
104
75
  else:
105
- raise APIError(f"Request failed after {self.retry+1} attempts: {e}", status_code=None)
106
-
76
+ break
77
+
78
+ raise APIError(f"Networking error, request failed after {self.retry+1} attempts: {last_exception}", status_code=None)
79
+
80
+ def _should_retry(self, status_code: int, attempt: int) -> bool:
81
+ """Determine if the request should be retried based on status code."""
82
+ if attempt >= self.retry:
83
+ return False
84
+ elif status_code in [408, 502, 503, 504]:
85
+ return True
86
+ else:
87
+ return False
88
+
89
+ async def _parse_response(self, response) -> Any:
90
+ """Parse response based on content type."""
91
+ content_type = response.headers.get('content-type', '').lower()
92
+ content = await response.read()
107
93
 
94
+ if 'json' in content_type:
95
+ return json.loads(content.decode('utf-8'))
96
+ elif 'csv' in content_type:
97
+ return pd.read_csv(BytesIO(content))
98
+ elif 'image/' in content_type:
99
+ return content
100
+ elif 'text' in content_type:
101
+ return content.decode('utf-8')
102
+ else:
103
+ try:
104
+ return xr.open_dataset(BytesIO(content))
105
+ except:
106
+ raise APIError(f"Unknown response format: {content_type}", status_code=response.status)
107
+
108
108
  async def _regular_request(self, method: str, endpoint: str, **kwargs):
109
109
  url = endpoint.lstrip('/')
110
110
  if self._session is None:
@@ -134,6 +134,7 @@ class AsyncClient(BaseClient):
134
134
  output: str = "csv",
135
135
  resolution: int = -1,
136
136
  geom_fix: bool = False,
137
+ validated: bool = True,
137
138
  **kwargs
138
139
  ):
139
140
  """
@@ -147,6 +148,7 @@ class AsyncClient(BaseClient):
147
148
  output (str): Output format ('csv' or 'netcdf')
148
149
  resolution (int): Resolution parameter
149
150
  geom_fix (bool): Whether to fix the geometry (default False)
151
+ validated (bool): Whether to use validated data (default True)
150
152
  **kwargs: Additional parameters to pass to the WCS request
151
153
 
152
154
  Returns:
@@ -169,6 +171,7 @@ class AsyncClient(BaseClient):
169
171
  "resolution": resolution,
170
172
  "expr": expr,
171
173
  "buffer": geom_fix,
174
+ "validated": validated,
172
175
  **kwargs
173
176
  }
174
177
  return await self._terrakio_request("POST", "geoquery", json=payload)
@@ -42,7 +42,7 @@ class DatasetManagement:
42
42
  return self._client._terrakio_request("GET", f"/datasets/{name}", params = params)
43
43
 
44
44
  @require_api_key
45
- def create_dataset(
45
+ async def create_dataset(
46
46
  self,
47
47
  name: str,
48
48
  collection: str = "terrakio-datasets",
@@ -59,7 +59,8 @@ class DatasetManagement:
59
59
  proj4: Optional[str] = None,
60
60
  abstract: Optional[str] = None,
61
61
  geotransform: Optional[List[float]] = None,
62
- padding: Optional[Any] = None
62
+ padding: Optional[Any] = None,
63
+ input: Optional[str] = None
63
64
  ) -> Dict[str, Any]:
64
65
  """
65
66
  Create a new dataset.
@@ -104,12 +105,13 @@ class DatasetManagement:
104
105
  "proj4": proj4,
105
106
  "abstract": abstract,
106
107
  "geotransform": geotransform,
107
- "padding": padding
108
+ "padding": padding,
109
+ "input": input
108
110
  }
109
111
  for param, value in param_mapping.items():
110
112
  if value is not None:
111
113
  payload[param] = value
112
- return self._client._terrakio_request("POST", "/datasets", params = params, json = payload)
114
+ return await self._client._terrakio_request("POST", "/datasets", params = params, json = payload)
113
115
 
114
116
  @require_api_key
115
117
  def update_dataset(
@@ -12,7 +12,7 @@ class MassStats:
12
12
  self._client = client
13
13
 
14
14
  @require_api_key
15
- async def upload_request(
15
+ async def _upload_request(
16
16
  self,
17
17
  name: str,
18
18
  size: int,
@@ -220,7 +220,7 @@ class MassStats:
220
220
  return self._client._terrakio_request("GET", "mass_stats/download", params=params)
221
221
 
222
222
  @require_api_key
223
- async def upload_file(self, file_path: str, url: str, use_gzip: bool = False):
223
+ async def _upload_file(self, file_path: str, url: str, use_gzip: bool = False):
224
224
  """
225
225
  Helper method to upload a JSON file to a signed URL.
226
226
 
@@ -427,7 +427,7 @@ class MassStats:
427
427
  return e
428
428
  except json.JSONDecodeError as e:
429
429
  return e
430
- upload_result = await self.upload_request(name = name, size = size, region = region, output = output, config = config, location = location, force_loc = force_loc, overwrite = overwrite, server = server, skip_existing = skip_existing)
430
+ upload_result = await self._upload_request(name = name, size = size, region = region, output = output, config = config, location = location, force_loc = force_loc, overwrite = overwrite, server = server, skip_existing = skip_existing)
431
431
  requests_url = upload_result.get('requests_url')
432
432
  manifest_url = upload_result.get('manifest_url')
433
433
  if not requests_url:
@@ -436,7 +436,7 @@ class MassStats:
436
436
  try:
437
437
  # in this place we are uploading the request json file, we need to check whether the json is in the correct format or not
438
438
  self.validate_request(request_json)
439
- requests_response = await self.upload_file(request_json, requests_url, use_gzip=True)
439
+ requests_response = await self._upload_file(request_json, requests_url, use_gzip=True)
440
440
  if requests_response.status not in [200, 201, 204]:
441
441
  self._client.logger.error(f"Requests upload error: {requests_response.text()}")
442
442
  raise Exception(f"Failed to upload request JSON: {requests_response.text()}")
@@ -447,7 +447,7 @@ class MassStats:
447
447
  raise ValueError("No manifest_url returned from server for manifest JSON upload")
448
448
 
449
449
  try:
450
- manifest_response = await self.upload_file(manifest_json, manifest_url, use_gzip=False)
450
+ manifest_response = await self._upload_file(manifest_json, manifest_url, use_gzip=False)
451
451
  if manifest_response.status not in [200, 201, 204]:
452
452
  self._client.logger.error(f"Manifest upload error: {manifest_response.text()}")
453
453
  raise Exception(f"Failed to upload manifest JSON: {manifest_response.text()}")