geobox 2.0.1__py3-none-any.whl → 2.2.0__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.
Files changed (70) hide show
  1. geobox/__init__.py +61 -63
  2. geobox/aio/__init__.py +61 -63
  3. geobox/aio/api.py +489 -473
  4. geobox/aio/apikey.py +263 -263
  5. geobox/aio/attachment.py +341 -339
  6. geobox/aio/base.py +261 -262
  7. geobox/aio/basemap.py +196 -196
  8. geobox/aio/dashboard.py +340 -342
  9. geobox/aio/feature.py +23 -33
  10. geobox/aio/field.py +315 -321
  11. geobox/aio/file.py +72 -72
  12. geobox/aio/layout.py +340 -341
  13. geobox/aio/log.py +23 -23
  14. geobox/aio/map.py +1033 -1034
  15. geobox/aio/model3d.py +415 -415
  16. geobox/aio/mosaic.py +696 -696
  17. geobox/aio/plan.py +314 -314
  18. geobox/aio/query.py +693 -693
  19. geobox/aio/raster.py +907 -869
  20. geobox/aio/raster_analysis.py +740 -0
  21. geobox/aio/route.py +4 -4
  22. geobox/aio/scene.py +340 -342
  23. geobox/aio/settings.py +18 -18
  24. geobox/aio/task.py +404 -402
  25. geobox/aio/tile3d.py +337 -339
  26. geobox/aio/tileset.py +102 -103
  27. geobox/aio/usage.py +52 -51
  28. geobox/aio/user.py +506 -507
  29. geobox/aio/vector_tool.py +1968 -0
  30. geobox/aio/vectorlayer.py +315 -306
  31. geobox/aio/version.py +272 -273
  32. geobox/aio/view.py +1019 -983
  33. geobox/aio/workflow.py +340 -341
  34. geobox/api.py +18 -2
  35. geobox/apikey.py +262 -262
  36. geobox/attachment.py +336 -337
  37. geobox/base.py +384 -384
  38. geobox/basemap.py +194 -194
  39. geobox/dashboard.py +339 -341
  40. geobox/enums.py +432 -348
  41. geobox/feature.py +5 -5
  42. geobox/field.py +320 -320
  43. geobox/file.py +4 -4
  44. geobox/layout.py +339 -340
  45. geobox/log.py +4 -4
  46. geobox/map.py +1031 -1032
  47. geobox/model3d.py +410 -410
  48. geobox/mosaic.py +696 -696
  49. geobox/plan.py +313 -313
  50. geobox/query.py +691 -691
  51. geobox/raster.py +907 -863
  52. geobox/raster_analysis.py +737 -0
  53. geobox/scene.py +341 -342
  54. geobox/settings.py +194 -194
  55. geobox/task.py +399 -400
  56. geobox/tile3d.py +337 -338
  57. geobox/tileset.py +4 -4
  58. geobox/usage.py +3 -3
  59. geobox/user.py +503 -503
  60. geobox/vector_tool.py +1968 -0
  61. geobox/vectorlayer.py +5 -5
  62. geobox/version.py +272 -272
  63. geobox/view.py +981 -981
  64. geobox/workflow.py +338 -339
  65. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/METADATA +15 -1
  66. geobox-2.2.0.dist-info/RECORD +72 -0
  67. geobox-2.0.1.dist-info/RECORD +0 -68
  68. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/WHEEL +0 -0
  69. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/licenses/LICENSE +0 -0
  70. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/top_level.txt +0 -0
geobox/aio/task.py CHANGED
@@ -1,402 +1,404 @@
1
- from urllib.parse import urljoin, urlencode
2
- from typing import Optional, Dict, List, Optional, Union, TYPE_CHECKING
3
- import time
4
-
5
- from .base import AsyncBase
6
- from ..enums import TaskStatus
7
-
8
- if TYPE_CHECKING:
9
- from . import AsyncGeoboxClient
10
- from .vectorlayer import VectorLayer
11
- from .raster import Raster
12
- from .model3d import Model
13
- from .file import File
14
- from .tile3d import Tile3d
15
- from ..api import GeoboxClient as SyncGeoboxClient
16
- from ..task import Task as SyncTask
17
-
18
-
19
- class Task(AsyncBase):
20
-
21
- BASE_ENDPOINT: str = 'tasks/'
22
-
23
- def __init__(self,
24
- api: 'AsyncGeoboxClient',
25
- uuid: str,
26
- data: Optional[Dict] = {}):
27
- """
28
- Constructs all the necessary attributes for the Task object.
29
-
30
- Args:
31
- api (AsyncGeoboxClient): The API instance.
32
- uuid (str): The UUID of the task.
33
- data (Dict, optional): The task data.
34
- """
35
- super().__init__(api, uuid=uuid, data=data)
36
- self._data = data if isinstance(data, dict) else {}
37
-
38
-
39
- async def refresh_data(self) -> None:
40
- """
41
- [async] Updates the task data.
42
- """
43
- task = await self.get_task(self.api, self.uuid)
44
- self._data = task.data
45
-
46
-
47
- @property
48
- async def output_asset(self) -> Union['VectorLayer', 'Raster', 'Model', 'File', 'Tile3d', None]:
49
- """
50
- [async] output asset property
51
-
52
- Returns:
53
- VectorLayer | Raster | Model | Tile3d | None: if task type is publish, it returns the published layer
54
-
55
- Example:
56
- >>> from geobox.aio import AsyncGeoboxClient
57
- >>> from geobox.aio.task import Task
58
- >>> async with AsyncGeoboxClient() as client:
59
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
60
- >>> await task.output_asset
61
- """
62
- if self.data.get('result', {}).get('layer_uuid'):
63
- return await self.api.get_vector(uuid=self.data['result']['layer_uuid'])
64
-
65
- elif self.data.get('result', {}).get('raster_uuid'):
66
- return await self.api.get_raster(uuid=self.data['result']['raster_uuid'])
67
-
68
- elif self.data.get('result', {}).get('model_uuid'):
69
- return await self.api.get_model(uuid=self.data['result']['model_uuid'])
70
-
71
- elif self.data.get('result', {}).get('file_uuid'):
72
- return await self.api.get_file(uuid=self.data['result']['file_uuid'])
73
-
74
- elif self.data.get('result', {}).get('3dtiles_uuid'):
75
- return await self.api.get_3dtile(uuid=self.data['result']['3dtiles_uuid'])
76
-
77
- else:
78
- return None
79
-
80
-
81
- @property
82
- def data(self) -> Dict:
83
- """
84
- Returns the task data.
85
-
86
- Returns:
87
- Dict: the task data as a dictionary
88
-
89
- Example:
90
- >>> from geobox.aio import AsyncGeoboxClient
91
- >>> from geobox.aio.task import Task
92
- >>> async with AsyncGeoboxClient() as client:
93
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
94
- >>> task.data
95
- """
96
- return self._data
97
-
98
-
99
- @data.setter
100
- def data(self, value: Dict) -> None:
101
- """
102
- Sets the task data.
103
-
104
- Example:
105
- >>> from geobox.aio import AsyncGeoboxClient
106
- >>> from geobox.aio.task import Task
107
- >>> async with AsyncGeoboxClient() as client:
108
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
109
- >>> task.data = {'name': 'test'}
110
- """
111
- self._data = value if isinstance(value, dict) else {}
112
-
113
-
114
- @property
115
- async def status(self) -> 'TaskStatus':
116
- """
117
- [async] Returns the status of the task. (auto refresh)
118
-
119
- Returns:
120
- TaskStatus: the status of the task(SUCCESS, FAILURE, ABORTED, PENDING, PROGRESS)
121
-
122
- Example:
123
- >>> from geobox.aio import AsyncGeoboxClient
124
- >>> from geobox.aio.task import Task
125
- >>> async with AsyncGeoboxClient() as client:
126
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
127
- >>> await task.status
128
- """
129
- await self.refresh_data()
130
- return TaskStatus(self._data.get('state'))
131
-
132
-
133
- @property
134
- def errors(self) -> Union[Dict, None]:
135
- """
136
- Get the task errors.
137
-
138
- Returns:
139
- Dict | None: if there are any errors
140
-
141
- Example:
142
- >>> from geobox.aio import AsyncGeoboxClient
143
- >>> from geobox.aio.task import Task
144
- >>> async with AsyncGeoboxClient() as client:
145
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
146
- >>> task.errors
147
- """
148
- result = self.data.get('result', {})
149
- if result.get('errors') or result.get('detail', {}).get('msg'):
150
- return result
151
- else:
152
- return None
153
-
154
-
155
- @property
156
- async def progress(self) -> Union[int, None]:
157
- """
158
- [async] Returns the progress of the task.
159
-
160
- Returns:
161
- int | None: the progress of the task in percentage or None if the task is not running
162
-
163
- Example:
164
- >>> from geobox.aio import AsyncGeoboxClient
165
- >>> from geobox.aio.task import Task
166
- >>> async with AsyncGeoboxClient() as client:
167
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
168
- >>> await task.progress
169
- """
170
- endpoint = urljoin(self.endpoint, 'status/')
171
- response = await self.api.get(endpoint)
172
-
173
- current = response.get('current')
174
- total = response.get('total')
175
- if not total or not current:
176
- return None
177
-
178
- return int((current / total) * 100)
179
-
180
-
181
- async def _wait(self, timeout: Union[int, None] = None, interval: int = 1, progress_bar: bool = True) -> 'TaskStatus':
182
- start_time = time.time()
183
- last_progress = 0
184
- pbar = self._create_progress_bar() if progress_bar else None
185
-
186
- try:
187
- while True:
188
- self._check_timeout(start_time, timeout)
189
- status = await self.status
190
-
191
- if self._is_final_state(status):
192
- await self._update_progress_bar(pbar, last_progress, status)
193
- return TaskStatus(status)
194
-
195
- if pbar:
196
- last_progress = await self._update_progress_bar(pbar, last_progress)
197
-
198
- time.sleep(interval)
199
- finally:
200
- if pbar:
201
- pbar.close()
202
-
203
-
204
- async def wait(self, timeout: Union[int, None] = None, interval: int = 1, progress_bar: bool = True, retry: int = 3) -> 'TaskStatus':
205
- """
206
- [async] Wait for the task to finish.
207
-
208
- Args:
209
- timeout (int, optional): Maximum time to wait in seconds.
210
- interval (int, optional): Time between status checks in seconds.
211
- progress_bar (bool, optional): Whether to show a progress bar. default: True
212
- retry (int, optional): Number of times to retry if waiting for the task fails. default is 3
213
-
214
- Returns:
215
- TaskStatus: the status of the task(SUCCESS, FAILURE, ABORTED, PENDING, PROGRESS)
216
-
217
- Raises:
218
- TimeoutError: If the task doesn't complete within timeout seconds.
219
-
220
- Example:
221
- >>> from geobox.aio import AsyncGeoboxClient
222
- >>> from geobox.aio.task import Task
223
- >>> async with AsyncGeoboxClient() as client:
224
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
225
- >>> await task.wait() # return the status of the task
226
- """
227
- last_exception = None
228
-
229
- for attempt in range(1, retry + 1):
230
- try:
231
- return await self._wait(timeout, interval, progress_bar)
232
- except Exception as e:
233
- last_exception = e
234
- print(f"[Retry {attempt}/{retry}] Task wait failed: {e}")
235
- time.sleep(interval)
236
- raise last_exception
237
-
238
-
239
- def _create_progress_bar(self) -> 'tqdm':
240
- """Creates a progress bar for the task."""
241
- try:
242
- from tqdm.auto import tqdm
243
- except ImportError:
244
- from .api import logger
245
- logger.warning("[tqdm] extra is required to show the progress bar. install with: pip insatll geobox[tqdm]")
246
- return None
247
-
248
- return tqdm(total=100, colour='green', desc=f"Task: {self.name}", unit="%", leave=True)
249
-
250
-
251
- def _check_timeout(self, start_time: float, timeout: Union[int, None]) -> None:
252
- """Checks if the task has exceeded the timeout period."""
253
- if timeout and time.time() - start_time > timeout:
254
- raise TimeoutError(f"Task {self.name} timed out after {timeout} seconds")
255
-
256
-
257
- def _is_final_state(self, status: 'TaskStatus') -> bool:
258
- """Checks if the task has reached a final state."""
259
- return status in [TaskStatus.FAILURE, TaskStatus.SUCCESS, TaskStatus.ABORTED]
260
-
261
-
262
- async def _update_progress_bar(self, pbar: Union['tqdm', None], last_progress: int, status: 'TaskStatus' = None) -> int:
263
- """
264
- [async] Updates the progress bar with current progress and returns the new last_progress.
265
-
266
- Args:
267
- pbar (tqdm | None): The progress bar to update
268
- last_progress (int): The last progress value
269
- status (TaskStatus, optional): The task status. If provided and SUCCESS, updates to 100%
270
-
271
- Returns:
272
- int: The new last_progress value
273
- """
274
- if not pbar:
275
- return last_progress
276
-
277
- if status == TaskStatus.SUCCESS:
278
- pbar.update(100 - last_progress)
279
- return 100
280
-
281
- current_progress = await self.progress
282
- if current_progress is not None:
283
- progress_diff = current_progress - last_progress
284
- if progress_diff > 0:
285
- pbar.update(progress_diff)
286
- return current_progress
287
- return last_progress
288
-
289
-
290
- async def abort(self) -> None:
291
- """
292
- [async] Aborts the task.
293
-
294
- Returns:
295
- None
296
-
297
- Example:
298
- >>> from geobox.aio import AsyncGeoboxClient
299
- >>> from geobox.aio.task import Task
300
- >>> async with AsyncGeoboxClient() as client:
301
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
302
- >>> await task.abort()
303
- """
304
- endpoint = urljoin(self.endpoint, 'abort/')
305
- await self.api.post(endpoint)
306
-
307
-
308
- @classmethod
309
- async def get_tasks(cls, api: 'AsyncGeoboxClient', **kwargs) -> Union[List['Task'], int]:
310
- """
311
- [async] Get a list of tasks
312
-
313
- Args:
314
- api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
315
-
316
- Keyword Args:
317
- state (TaskStatus): Available values : TaskStatus.PENDING, TaskStatus.PROGRESS, TaskStatus.SUCCESS, TaskStatus.FAILURE, TaskStatus.ABORTED
318
- q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
319
- search (str): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value. NOTE: if q param is defined this param will be ignored.
320
- search_fields (str): comma separated list of fields for searching.
321
- order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, type D. NOTE: "A" denotes ascending order and "D" denotes descending order.
322
- return_count (bool): The count of the tasks. default is False.
323
- skip (int): The skip of the task. default is 0.
324
- limit (int): The limit of the task. default is 10.
325
- user_id (int): Specific user. privileges required.
326
- shared (bool): Whether to return shared tasks. default is False.
327
-
328
- Returns:
329
- List[Task] | int: The list of task objects or the count of the tasks if return_count is True.
330
-
331
- Example:
332
- >>> from geobox.aio import AsyncGeoboxClient
333
- >>> from geobox.aio.task import Task
334
- >>> async with AsyncGeoboxClient() as client:
335
- >>> tasks = await Task.get_tasks(client)
336
- or
337
- >>> tasks = await client.get_tasks()
338
- """
339
- params = {
340
- 'f': 'json',
341
- 'state': kwargs.get('state').value if kwargs.get('state') else None,
342
- 'q': kwargs.get('q'),
343
- 'search': kwargs.get('search'),
344
- 'search_fields': kwargs.get('search_fields'),
345
- 'order_by': kwargs.get('order_by'),
346
- 'return_count': kwargs.get('return_count', False),
347
- 'skip': kwargs.get('skip'),
348
- 'limit': kwargs.get('limit'),
349
- 'user_id': kwargs.get('user_id'),
350
- 'shared': kwargs.get('shared', False)
351
- }
352
- return await super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: Task(api, item['uuid'], item))
353
-
354
-
355
-
356
- @classmethod
357
- async def get_task(cls, api: 'AsyncGeoboxClient', uuid: str) -> 'Task':
358
- """
359
- [async] Gets a task.
360
-
361
- Args:
362
- api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
363
- uuid (str): The UUID of the task.
364
-
365
- Returns:
366
- Task: The task object.
367
-
368
- Example:
369
- >>> from geobox.aio import AsyncGeoboxClient
370
- >>> from geobox.aio.task import Task
371
- >>> async with AsyncGeoboxClient() as client:
372
- >>> task = await Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
373
- or
374
- >>> task = await client.get_task(uuid="12345678-1234-5678-1234-567812345678")
375
- """
376
- return await super()._get_detail(api, cls.BASE_ENDPOINT, uuid, factory_func=lambda api, item: Task(api, item['uuid'], item))
377
-
378
-
379
- def to_sync(self, sync_client: 'SyncGeoboxClient') -> 'SyncTask':
380
- """
381
- Switch to sync version of the task instance to have access to the sync methods
382
-
383
- Args:
384
- sync_client (SyncGeoboxClient): The sync version of the GeoboxClient instance for making requests.
385
-
386
- Returns:
387
- geobox.task.Task: the sync instance of the task.
388
-
389
- Example:
390
- >>> from geobox import Geoboxclient
391
- >>> from geobox.aio import AsyncGeoboxClient
392
- >>> from geobox.aio.task import Task
393
- >>> client = GeoboxClient()
394
- >>> async with AsyncGeoboxClient() as async_client:
395
- >>> task = await Task.get_task(async_client, uuid="12345678-1234-5678-1234-567812345678")
396
- or
397
- >>> task = await async_client.get_task(uuid="12345678-1234-5678-1234-567812345678")
398
- >>> sync_task = task.to_sync(client)
399
- """
400
- from ..task import Task as SyncTask
401
-
402
- return SyncTask(api=sync_client, uuid=self.uuid, data=self.data)
1
+ import time
2
+ import asyncio
3
+ from urllib.parse import urljoin
4
+ from typing import Optional, Dict, List, Optional, Union, TYPE_CHECKING
5
+
6
+ from .base import AsyncBase
7
+ from ..enums import TaskStatus
8
+
9
+ if TYPE_CHECKING:
10
+ from . import AsyncGeoboxClient
11
+ from .vectorlayer import AsyncVectorLayer
12
+ from .raster import AsyncRaster
13
+ from .model3d import AsyncModel
14
+ from .file import AsyncFile
15
+ from .tile3d import AsyncTile3d
16
+ from ..api import GeoboxClient
17
+ from ..task import Task
18
+
19
+
20
+ class AsyncTask(AsyncBase):
21
+
22
+ BASE_ENDPOINT: str = 'tasks/'
23
+
24
+ def __init__(self,
25
+ api: 'AsyncGeoboxClient',
26
+ uuid: str,
27
+ data: Optional[Dict] = {}):
28
+ """
29
+ Constructs all the necessary attributes for the Task object.
30
+
31
+ Args:
32
+ api (AsyncGeoboxClient): The API instance.
33
+ uuid (str): The UUID of the task.
34
+ data (Dict, optional): The task data.
35
+ """
36
+ super().__init__(api, uuid=uuid, data=data)
37
+ self._data = data if isinstance(data, dict) else {}
38
+
39
+
40
+ async def refresh_data(self) -> None:
41
+ """
42
+ [async] Updates the task data.
43
+ """
44
+ task = await self.get_task(self.api, self.uuid)
45
+ self._data = task.data
46
+
47
+
48
+ @property
49
+ async def output_asset(self) -> Union['AsyncVectorLayer', 'AsyncRaster', 'AsyncModel', 'AsyncFile', 'AsyncTile3d', None]:
50
+ """
51
+ [async] output asset property
52
+
53
+ Returns:
54
+ AsyncVectorLayer | AsyncRaster | AsyncModel | AsyncFile | AsyncTile3d | None: if task type is publish, it returns the published layer
55
+
56
+ Example:
57
+ >>> from geobox.aio import AsyncGeoboxClient
58
+ >>> from geobox.aio.task import AsyncTask
59
+ >>> async with AsyncGeoboxClient() as client:
60
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
61
+ >>> await task.output_asset
62
+ """
63
+ if self.data.get('result', {}).get('layer_uuid'):
64
+ return await self.api.get_vector(uuid=self.data['result']['layer_uuid'])
65
+
66
+ elif self.data.get('result', {}).get('raster_uuid'):
67
+ return await self.api.get_raster(uuid=self.data['result']['raster_uuid'])
68
+
69
+ elif self.data.get('result', {}).get('model_uuid'):
70
+ return await self.api.get_model(uuid=self.data['result']['model_uuid'])
71
+
72
+ elif self.data.get('result', {}).get('file_uuid'):
73
+ return await self.api.get_file(uuid=self.data['result']['file_uuid'])
74
+
75
+ elif self.data.get('result', {}).get('3dtiles_uuid'):
76
+ return await self.api.get_3dtile(uuid=self.data['result']['3dtiles_uuid'])
77
+
78
+ else:
79
+ return None
80
+
81
+
82
+ @property
83
+ def data(self) -> Dict:
84
+ """
85
+ Returns the task data.
86
+
87
+ Returns:
88
+ Dict: the task data as a dictionary
89
+
90
+ Example:
91
+ >>> from geobox.aio import AsyncGeoboxClient
92
+ >>> from geobox.aio.task import AsyncTask
93
+ >>> async with AsyncGeoboxClient() as client:
94
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
95
+ >>> task.data
96
+ """
97
+ return self._data
98
+
99
+
100
+ @data.setter
101
+ def data(self, value: Dict) -> None:
102
+ """
103
+ Sets the task data.
104
+
105
+ Example:
106
+ >>> from geobox.aio import AsyncGeoboxClient
107
+ >>> from geobox.aio.task import AsyncTask
108
+ >>> async with AsyncGeoboxClient() as client:
109
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
110
+ >>> task.data = {'name': 'test'}
111
+ """
112
+ self._data = value if isinstance(value, dict) else {}
113
+
114
+
115
+ @property
116
+ async def status(self) -> 'TaskStatus':
117
+ """
118
+ [async] Returns the status of the task. (auto refresh)
119
+
120
+ Returns:
121
+ TaskStatus: the status of the task(SUCCESS, FAILURE, ABORTED, PENDING, PROGRESS)
122
+
123
+ Example:
124
+ >>> from geobox.aio import AsyncGeoboxClient
125
+ >>> from geobox.aio.task import AsyncTask
126
+ >>> async with AsyncGeoboxClient() as client:
127
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
128
+ >>> await task.status
129
+ """
130
+ await self.refresh_data()
131
+ return TaskStatus(self._data.get('state'))
132
+
133
+
134
+ @property
135
+ def errors(self) -> Union[Dict, None]:
136
+ """
137
+ Get the task errors.
138
+
139
+ Returns:
140
+ Dict | None: if there are any errors
141
+
142
+ Example:
143
+ >>> from geobox.aio import AsyncGeoboxClient
144
+ >>> from geobox.aio.task import AsyncTask
145
+ >>> async with AsyncGeoboxClient() as client:
146
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
147
+ >>> task.errors
148
+ """
149
+ result = self.data.get('result', {})
150
+ if result.get('errors') or result.get('detail', {}).get('msg'):
151
+ return result
152
+ else:
153
+ return None
154
+
155
+
156
+ @property
157
+ async def progress(self) -> Union[int, None]:
158
+ """
159
+ [async] Returns the progress of the task.
160
+
161
+ Returns:
162
+ int | None: the progress of the task in percentage or None if the task is not running
163
+
164
+ Example:
165
+ >>> from geobox.aio import AsyncGeoboxClient
166
+ >>> from geobox.aio.task import AsyncTask
167
+ >>> async with AsyncGeoboxClient() as client:
168
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
169
+ >>> await task.progress
170
+ """
171
+ endpoint = urljoin(self.endpoint, 'status/')
172
+ response = await self.api.get(endpoint)
173
+
174
+ current = response.get('current')
175
+ total = response.get('total')
176
+ if not total or not current:
177
+ return None
178
+
179
+ return int((current / total) * 100)
180
+
181
+
182
+ async def _wait(self, timeout: Union[int, None] = None, interval: int = 1, progress_bar: bool = True) -> 'TaskStatus':
183
+ start_time = time.time()
184
+ last_progress = 0
185
+ pbar = self._create_progress_bar() if progress_bar else None
186
+
187
+ try:
188
+ while True:
189
+ self._check_timeout(start_time, timeout)
190
+ status = await self.status
191
+
192
+ if self._is_final_state(status):
193
+ await self._update_progress_bar(pbar, last_progress, status)
194
+ return status
195
+
196
+ if pbar:
197
+ last_progress = await self._update_progress_bar(pbar, last_progress)
198
+
199
+ await asyncio.sleep(interval)
200
+
201
+ finally:
202
+ if pbar:
203
+ pbar.close()
204
+
205
+
206
+ async def wait(self, timeout: Union[int, None] = None, interval: int = 1, progress_bar: bool = True, retry: int = 3) -> 'TaskStatus':
207
+ """
208
+ [async] Wait for the task to finish.
209
+
210
+ Args:
211
+ timeout (int, optional): Maximum time to wait in seconds.
212
+ interval (int, optional): Time between status checks in seconds.
213
+ progress_bar (bool, optional): Whether to show a progress bar. default: True
214
+ retry (int, optional): Number of times to retry if waiting for the task fails. default is 3
215
+
216
+ Returns:
217
+ TaskStatus: the status of the task(SUCCESS, FAILURE, ABORTED, PENDING, PROGRESS)
218
+
219
+ Raises:
220
+ TimeoutError: If the task doesn't complete within timeout seconds.
221
+
222
+ Example:
223
+ >>> from geobox.aio import AsyncGeoboxClient
224
+ >>> from geobox.aio.task import AsyncTask
225
+ >>> async with AsyncGeoboxClient() as client:
226
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
227
+ >>> await task.wait() # return the status of the task
228
+ """
229
+ last_exception = None
230
+
231
+ for attempt in range(1, retry + 1):
232
+ try:
233
+ return await self._wait(timeout, interval, progress_bar)
234
+ except Exception as e:
235
+ last_exception = e
236
+ print(f"[Retry {attempt}/{retry}] Task wait failed: {e}")
237
+ time.sleep(interval)
238
+ raise last_exception
239
+
240
+
241
+ def _create_progress_bar(self) -> 'tqdm':
242
+ """Creates a progress bar for the task."""
243
+ try:
244
+ from tqdm.auto import tqdm
245
+ except ImportError:
246
+ from .api import logger
247
+ logger.warning("[tqdm] extra is required to show the progress bar. install with: pip insatll geobox[tqdm]")
248
+ return None
249
+
250
+ return tqdm(total=100, colour='green', desc=f"Task: {self.name}", unit="%", leave=True)
251
+
252
+
253
+ def _check_timeout(self, start_time: float, timeout: Union[int, None]) -> None:
254
+ """Checks if the task has exceeded the timeout period."""
255
+ if timeout and time.time() - start_time > float(timeout):
256
+ raise TimeoutError(f"Task {self.name} timed out after {timeout} seconds")
257
+
258
+
259
+ def _is_final_state(self, status: 'TaskStatus') -> bool:
260
+ """Checks if the task has reached a final state."""
261
+ return status in [TaskStatus.FAILURE, TaskStatus.SUCCESS, TaskStatus.ABORTED]
262
+
263
+
264
+ async def _update_progress_bar(self, pbar: Union['tqdm', None], last_progress: int, status: 'TaskStatus' = None) -> int:
265
+ """
266
+ [async] Updates the progress bar with current progress and returns the new last_progress.
267
+
268
+ Args:
269
+ pbar (tqdm | None): The progress bar to update
270
+ last_progress (int): The last progress value
271
+ status (TaskStatus, optional): The task status. If provided and SUCCESS, updates to 100%
272
+
273
+ Returns:
274
+ int: The new last_progress value
275
+ """
276
+ if not pbar:
277
+ return last_progress
278
+
279
+ if status == TaskStatus.SUCCESS:
280
+ pbar.update(100 - last_progress)
281
+ return 100
282
+
283
+ current_progress = await self.progress
284
+ if current_progress is not None:
285
+ progress_diff = current_progress - last_progress
286
+ if progress_diff > 0:
287
+ pbar.update(progress_diff)
288
+ return current_progress
289
+ return last_progress
290
+
291
+
292
+ async def abort(self) -> None:
293
+ """
294
+ [async] Aborts the task.
295
+
296
+ Returns:
297
+ None
298
+
299
+ Example:
300
+ >>> from geobox.aio import AsyncGeoboxClient
301
+ >>> from geobox.aio.task import AsyncTask
302
+ >>> async with AsyncGeoboxClient() as client:
303
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
304
+ >>> await task.abort()
305
+ """
306
+ endpoint = urljoin(self.endpoint, 'abort/')
307
+ await self.api.post(endpoint)
308
+
309
+
310
+ @classmethod
311
+ async def get_tasks(cls, api: 'AsyncGeoboxClient', **kwargs) -> Union[List['Task'], int]:
312
+ """
313
+ [async] Get a list of tasks
314
+
315
+ Args:
316
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
317
+
318
+ Keyword Args:
319
+ state (TaskStatus): Available values : TaskStatus.PENDING, TaskStatus.PROGRESS, TaskStatus.SUCCESS, TaskStatus.FAILURE, TaskStatus.ABORTED
320
+ q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
321
+ search (str): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value. NOTE: if q param is defined this param will be ignored.
322
+ search_fields (str): comma separated list of fields for searching.
323
+ order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, type D. NOTE: "A" denotes ascending order and "D" denotes descending order.
324
+ return_count (bool): The count of the tasks. default is False.
325
+ skip (int): The skip of the task. default is 0.
326
+ limit (int): The limit of the task. default is 10.
327
+ user_id (int): Specific user. privileges required.
328
+ shared (bool): Whether to return shared tasks. default is False.
329
+
330
+ Returns:
331
+ List[AsyncTask] | int: The list of task objects or the count of the tasks if return_count is True.
332
+
333
+ Example:
334
+ >>> from geobox.aio import AsyncGeoboxClient
335
+ >>> from geobox.aio.task import AsyncTask
336
+ >>> async with AsyncGeoboxClient() as client:
337
+ >>> tasks = await AsyncTask.get_tasks(client)
338
+ or
339
+ >>> tasks = await client.get_tasks()
340
+ """
341
+ params = {
342
+ 'f': 'json',
343
+ 'state': kwargs.get('state').value if kwargs.get('state') else None,
344
+ 'q': kwargs.get('q'),
345
+ 'search': kwargs.get('search'),
346
+ 'search_fields': kwargs.get('search_fields'),
347
+ 'order_by': kwargs.get('order_by'),
348
+ 'return_count': kwargs.get('return_count', False),
349
+ 'skip': kwargs.get('skip'),
350
+ 'limit': kwargs.get('limit'),
351
+ 'user_id': kwargs.get('user_id'),
352
+ 'shared': kwargs.get('shared', False)
353
+ }
354
+ return await super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: AsyncTask(api, item['uuid'], item))
355
+
356
+
357
+
358
+ @classmethod
359
+ async def get_task(cls, api: 'AsyncGeoboxClient', uuid: str) -> 'AsyncTask':
360
+ """
361
+ [async] Gets a task.
362
+
363
+ Args:
364
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
365
+ uuid (str): The UUID of the task.
366
+
367
+ Returns:
368
+ AsyncTask: The task object.
369
+
370
+ Example:
371
+ >>> from geobox.aio import AsyncGeoboxClient
372
+ >>> from geobox.aio.task import AsyncTask
373
+ >>> async with AsyncGeoboxClient() as client:
374
+ >>> task = await AsyncTask.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
375
+ or
376
+ >>> task = await client.get_task(uuid="12345678-1234-5678-1234-567812345678")
377
+ """
378
+ return await super()._get_detail(api, cls.BASE_ENDPOINT, uuid, factory_func=lambda api, item: AsyncTask(api, item['uuid'], item))
379
+
380
+
381
+ def to_sync(self, sync_client: 'GeoboxClient') -> 'Task':
382
+ """
383
+ Switch to sync version of the task instance to have access to the sync methods
384
+
385
+ Args:
386
+ sync_client (SyncGeoboxClient): The sync version of the GeoboxClient instance for making requests.
387
+
388
+ Returns:
389
+ Task: the sync instance of the task.
390
+
391
+ Example:
392
+ >>> from geobox import Geoboxclient
393
+ >>> from geobox.aio import AsyncGeoboxClient
394
+ >>> from geobox.aio.task import AsyncTask
395
+ >>> client = GeoboxClient()
396
+ >>> async with AsyncGeoboxClient() as async_client:
397
+ >>> task = await AsyncTask.get_task(async_client, uuid="12345678-1234-5678-1234-567812345678")
398
+ or
399
+ >>> task = await async_client.get_task(uuid="12345678-1234-5678-1234-567812345678")
400
+ >>> sync_task = task.to_sync(client)
401
+ """
402
+ from ..task import Task
403
+
404
+ return Task(api=sync_client, uuid=self.uuid, data=self.data)