geobox 1.4.2__py3-none-any.whl → 2.0.1__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 (66) hide show
  1. geobox/__init__.py +2 -2
  2. geobox/aio/__init__.py +63 -0
  3. geobox/aio/api.py +2640 -0
  4. geobox/aio/apikey.py +263 -0
  5. geobox/aio/attachment.py +339 -0
  6. geobox/aio/base.py +262 -0
  7. geobox/aio/basemap.py +196 -0
  8. geobox/aio/dashboard.py +342 -0
  9. geobox/aio/feature.py +527 -0
  10. geobox/aio/field.py +321 -0
  11. geobox/aio/file.py +522 -0
  12. geobox/aio/layout.py +341 -0
  13. geobox/aio/log.py +145 -0
  14. geobox/aio/map.py +1034 -0
  15. geobox/aio/model3d.py +415 -0
  16. geobox/aio/mosaic.py +696 -0
  17. geobox/aio/plan.py +315 -0
  18. geobox/aio/query.py +693 -0
  19. geobox/aio/raster.py +869 -0
  20. geobox/aio/route.py +63 -0
  21. geobox/aio/scene.py +342 -0
  22. geobox/aio/settings.py +194 -0
  23. geobox/aio/task.py +402 -0
  24. geobox/aio/tile3d.py +339 -0
  25. geobox/aio/tileset.py +672 -0
  26. geobox/aio/usage.py +243 -0
  27. geobox/aio/user.py +507 -0
  28. geobox/aio/vectorlayer.py +1363 -0
  29. geobox/aio/version.py +273 -0
  30. geobox/aio/view.py +983 -0
  31. geobox/aio/workflow.py +341 -0
  32. geobox/api.py +14 -15
  33. geobox/apikey.py +28 -1
  34. geobox/attachment.py +27 -1
  35. geobox/base.py +4 -4
  36. geobox/basemap.py +30 -1
  37. geobox/dashboard.py +27 -0
  38. geobox/feature.py +33 -13
  39. geobox/field.py +33 -21
  40. geobox/file.py +40 -46
  41. geobox/layout.py +28 -1
  42. geobox/log.py +31 -7
  43. geobox/map.py +34 -2
  44. geobox/model3d.py +31 -37
  45. geobox/mosaic.py +28 -7
  46. geobox/plan.py +29 -3
  47. geobox/query.py +39 -14
  48. geobox/raster.py +26 -13
  49. geobox/scene.py +26 -0
  50. geobox/settings.py +30 -1
  51. geobox/task.py +28 -6
  52. geobox/tile3d.py +27 -1
  53. geobox/tileset.py +26 -5
  54. geobox/usage.py +32 -1
  55. geobox/user.py +62 -6
  56. geobox/utils.py +34 -0
  57. geobox/vectorlayer.py +40 -4
  58. geobox/version.py +25 -1
  59. geobox/view.py +37 -17
  60. geobox/workflow.py +27 -1
  61. {geobox-1.4.2.dist-info → geobox-2.0.1.dist-info}/METADATA +4 -1
  62. geobox-2.0.1.dist-info/RECORD +68 -0
  63. geobox-1.4.2.dist-info/RECORD +0 -38
  64. {geobox-1.4.2.dist-info → geobox-2.0.1.dist-info}/WHEEL +0 -0
  65. {geobox-1.4.2.dist-info → geobox-2.0.1.dist-info}/licenses/LICENSE +0 -0
  66. {geobox-1.4.2.dist-info → geobox-2.0.1.dist-info}/top_level.txt +0 -0
geobox/aio/task.py ADDED
@@ -0,0 +1,402 @@
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)