geobox 2.2.6__py3-none-any.whl → 2.3.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.
geobox/aio/table.py ADDED
@@ -0,0 +1,1144 @@
1
+ from typing import List, Dict, Literal, Optional, TYPE_CHECKING, Union, Any
2
+ from urllib.parse import urljoin
3
+
4
+ from geobox.enums import TableExportFormat
5
+
6
+ from .base import AsyncBase
7
+ from .task import AsyncTask
8
+ from ..enums import FieldType
9
+ from ..exception import NotFoundError
10
+ from ..utils import clean_data
11
+
12
+ if TYPE_CHECKING:
13
+ from . import AsyncGeoboxClient
14
+ from .user import AsyncUser
15
+ from .. import GeoboxClient
16
+ from .file import AsyncFile
17
+ from ..table import Table, TableField, TableRow
18
+
19
+
20
+ class AsyncTableRow(AsyncBase):
21
+
22
+ def __init__(self,
23
+ table: 'AsyncTable',
24
+ data: Optional[Dict] = {},
25
+ ):
26
+ """
27
+ Constructs all the necessary attributes for the AsyncTableRow object.
28
+
29
+ Args:
30
+ table (AsyncTable): The table that the row belongs to.
31
+ data (Dict, optional): The data of the field.
32
+ """
33
+ super().__init__(api=table.api, data=data)
34
+ self.table = table
35
+ self.endpoint = urljoin(table.endpoint, f'rows/{self.id}/') if self.data.get('id') else None
36
+
37
+
38
+ def __repr__(self) -> str:
39
+ """
40
+ Return a string representation of the AsyncTableRow.
41
+
42
+ Returns:
43
+ str: The string representation of the AsyncTableRow.
44
+ """
45
+ return f"AsyncTableRow(id={self.id}, table_name={self.table.data.get('name', 'None')})"
46
+
47
+
48
+ @classmethod
49
+ async def create_row(cls, table: 'AsyncTable', **kwargs) -> 'AsyncTableRow':
50
+ """
51
+ [async] Create a new row in the table.
52
+
53
+ Each keyword argument represents a field value for the row, where:
54
+ - The keyword is the field name
55
+ - The value is the field value
56
+
57
+ Args:
58
+ table (AsyncTable): table instance
59
+
60
+ Keyword Args:
61
+ **kwargs: Arbitrary field values matching the table schema.
62
+
63
+ Returns:
64
+ AsyncTableRow: created table row instance
65
+
66
+ Example:
67
+ >>> from geobox.aio import AsyncGeoboxClient
68
+ >>> from geobox.aio.table import AsyncTable, AsyncTableRow
69
+ >>> async with AsyncGeoboxClient() as client:
70
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
71
+ or
72
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
73
+ >>> row_data = {
74
+ ... 'field1': 'value1'
75
+ ... }
76
+ >>> row = await table.create_row(row_data)
77
+ or
78
+ >>> row = await AsyncTableRow.create_row(table, row_data)
79
+ """
80
+ endpoint = urljoin(table.endpoint, 'rows/')
81
+ return await cls._create(table.api, endpoint, kwargs, factory_func=lambda api, item: AsyncTableRow(table, data=item))
82
+
83
+
84
+ @classmethod
85
+ async def get_row(cls,
86
+ table: 'AsyncTable',
87
+ row_id: int,
88
+ user_id: Optional[int],
89
+ ) -> 'AsyncTableRow':
90
+ """
91
+ [async] Get a row by its id
92
+
93
+ Args:
94
+ table (AsyncTable): the table instance
95
+ row_id (int): the row id
96
+ user_id (int, optional): specific user. privileges required.
97
+
98
+ Returns:
99
+ TanbleRow: the table row instance
100
+
101
+ Example:
102
+ >>> from geobox.aio import AsyncGeoboxClient
103
+ >>> from geobox.aio.table import AsyncTable, AsyncTableRow
104
+ >>> async with AsyncGeoboxClient() as client:
105
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
106
+ or
107
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
108
+
109
+ >>> row = await table.get_row(row_id=1)
110
+ or
111
+ >>> row = await AsyncTableRow.get_row(table, row_id=1)
112
+ """
113
+ param = {
114
+ 'f': 'json',
115
+ 'user_id': user_id
116
+ }
117
+ endpoint = urljoin(table.endpoint, f'rows/')
118
+ return await cls._get_detail(table.api, endpoint, uuid=row_id, params=param, factory_func=lambda api, item: AsyncTableRow(table, data=item))
119
+
120
+
121
+ async def update(self, **kwargs) -> Dict:
122
+ """
123
+ [async] Update a row
124
+
125
+ Keyword Args:
126
+ fields to update
127
+
128
+ Returns:
129
+ Dict: updated row data
130
+
131
+ Example:
132
+ >>> from geobox.aio import AsyncGeoboxClient
133
+ >>> async with AsyncGeoboxClient() as client:
134
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
135
+ >>> row = await table.get_row(row_id=1)
136
+ >>> await row.update(field1='new_value')
137
+ """
138
+ await super()._update(self.endpoint, self.data, clean=False)
139
+ return self.data
140
+
141
+
142
+ async def delete(self) -> None:
143
+ """
144
+ [async] Delete a row
145
+
146
+ Returns:
147
+ None
148
+
149
+ Example:
150
+ >>> from geobox.aio import AsyncGeoboxClient
151
+ >>> async with AsyncGeoboxClient() as client:
152
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
153
+ >>> row = await table.get_row(row_id=1)
154
+ >>> await row.delete()
155
+ """
156
+ await super()._delete(self.endpoint)
157
+
158
+
159
+ def to_sync(self, sync_client: 'GeoboxClient') -> 'TableRow':
160
+ """
161
+ Switch to sync version of the table row instance to have access to the sync methods
162
+
163
+ Args:
164
+ sync_client (GeoboxClient): The sync version of the GeoboxClient instance for making requests.
165
+
166
+ Returns:
167
+ TableRow: the sync instance of the TableRow.
168
+
169
+ Example:
170
+ >>> from geobox import Geoboxclient
171
+ >>> from geobox.aio import AsyncGeoboxClient
172
+ >>> async with AsyncGeoboxClient() as client:
173
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
174
+ >>> row = await table.get_row(row_id=1)
175
+ >>> sync_client = GeoboxClient()
176
+ >>> sync_row = row.to_sync(sync_client)
177
+ """
178
+ from ..table import TableRow
179
+
180
+ sync_table = self.table.to_sync(sync_client=sync_client)
181
+ return TableRow(table=sync_table, data=self.data)
182
+
183
+
184
+
185
+ class AsyncTableField(AsyncBase):
186
+
187
+ def __init__(self,
188
+ table: 'AsyncTable',
189
+ data_type: 'FieldType',
190
+ field_id: int = None,
191
+ data: Optional[Dict] = {},
192
+ ):
193
+ """
194
+ Constructs all the necessary attributes for the Field object.
195
+
196
+ Args:
197
+ table (AsyncTable): The table that the field belongs to.
198
+ data_type (FieldType): type of the field
199
+ field_id (int): the id of the field
200
+ data (Dict, optional): The data of the field.
201
+ """
202
+ super().__init__(api=table.api, data=data)
203
+ self.table = table
204
+ self.field_id = field_id
205
+ if not isinstance(data_type, FieldType):
206
+ raise ValueError("data_type must be a FieldType instance")
207
+ self.data_type = data_type
208
+ self.endpoint = urljoin(table.endpoint, f'fields/{self.id}/') if self.data.get('id') else None
209
+
210
+
211
+ def __repr__(self) -> str:
212
+ """
213
+ Return a string representation of the field.
214
+
215
+ Returns:
216
+ str: The string representation of the field.
217
+ """
218
+ return f"AsyncTableField(id={self.id}, name={self.name}, data_type={self.data_type})"
219
+
220
+
221
+ def __getattr__(self, name: str) -> Any:
222
+ """
223
+ Get an attribute from the resource.
224
+
225
+ Args:
226
+ name (str): The name of the attribute
227
+ """
228
+ if name == 'datatype':
229
+ return FieldType(self.data['datatype'])
230
+ return super().__getattr__(name)
231
+
232
+
233
+ @property
234
+ def domain(self) -> Dict:
235
+ """
236
+ Domain property
237
+
238
+ Returns:
239
+ Dict: domain data
240
+ """
241
+ return self.data.get('domain')
242
+
243
+
244
+ @domain.setter
245
+ def domain(self, value: Dict) -> None:
246
+ """
247
+ Domain property setter
248
+
249
+ Returns:
250
+ None
251
+ """
252
+ self.data['domain'] = value
253
+
254
+
255
+ @classmethod
256
+ async def create_field(cls,
257
+ table: 'AsyncTable',
258
+ name: str,
259
+ data_type: 'FieldType',
260
+ data: Dict = {},
261
+ ) -> 'AsyncTableField':
262
+ """
263
+ [async] Create a new field
264
+
265
+ Args:
266
+ table (AsyncTable): field's table
267
+ name (str): name of the field
268
+ data_type (FieldType): type of the field
269
+ data (Dict, optional): the data of the field
270
+
271
+ Returns:
272
+ AsyncTableField: the created field object
273
+
274
+ Example:
275
+ >>> from geobox.aio import AsyncGeoboxClient
276
+ >>> from geobox.aio.table import AsyncTable, AsyncTableField
277
+ >>> async with AsyncGeoboxClient() as client:
278
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
279
+
280
+ >>> field = await table.create_field(name='test', data_type=FieldType.Integer)
281
+ or
282
+ >>> field = await AsyncTableField.create_field(client, table=table, name='test', data_type=FieldType.Integer)
283
+ """
284
+ data.update({
285
+ "name": name,
286
+ "datatype": data_type.value
287
+ })
288
+ endpoint = urljoin(table.endpoint, 'fields/')
289
+ return await super()._create(table.api, endpoint, data, factory_func=lambda api, item: AsyncTableField(table, data_type, item['id'], item))
290
+
291
+
292
+ async def delete(self) -> None:
293
+ """
294
+ [async] Delete the field.
295
+
296
+ Returns:
297
+ None
298
+
299
+ Example:
300
+ >>> from geobox.aio import AsyncGeoboxClient
301
+ >>> from geobox.field import AsyncTableField
302
+ >>> async with AsyncGeoboxClient() as client:
303
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
304
+ >>> field = await table.get_field(field_id=1)
305
+ >>> await field.delete()
306
+ """
307
+ await super()._delete(self.endpoint)
308
+ self.field_id = None
309
+
310
+
311
+ async def update(self, **kwargs) -> Dict:
312
+ """
313
+ [async] Update the field.
314
+
315
+ Keyword Args:
316
+ name (str): The name of the field.
317
+ display_name (str): The display name of the field.
318
+ description (str): The description of the field.
319
+ domain (Dict): the domain of the field
320
+ hyperlink (bool): the hyperlink field.
321
+
322
+ Returns:
323
+ Dict: The updated data.
324
+
325
+ Example:
326
+ >>> from geobox.aio import AsyncGeoboxClient
327
+ >>> from geobox.aio.table import AsyncTableField
328
+ >>> async with AsyncGeoboxClient() as client:
329
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
330
+ >>> field = await table.get_field(field_id=1)
331
+ >>> await field.update(name="my_field", display_name="My Field", description="My Field Description")
332
+ """
333
+ data = {
334
+ "name": kwargs.get('name'),
335
+ "display_name": kwargs.get('display_name'),
336
+ "description": kwargs.get('description'),
337
+ "domain": kwargs.get('domain'),
338
+ "hyperlink": kwargs.get('hyperlink')
339
+ }
340
+ return await super()._update(self.endpoint, data)
341
+
342
+
343
+ async def update_domain(self,
344
+ range_domain: Dict = None,
345
+ list_domain: Dict = None,
346
+ ) -> Dict:
347
+ """
348
+ [async] Update field domian values
349
+
350
+ Args:
351
+ range_domain (Dict): a dictionary with min and max keys.
352
+ list_domain (Dict): a dictionary containing the domain codes and values.
353
+
354
+ Returns:
355
+ Dict: the updated field domain
356
+
357
+ Example:
358
+ >>> from geobox.aio import AsyncGeoboxClient
359
+ >>> async with AsyncGeoboxClient() as client:
360
+ >>> field = await client.get_table(uuid="12345678-1234-5678-1234-567812345678").get_fields()[0]
361
+ >>> range_d = {'min': 1, 'max': 10}
362
+ >>> await field.update_domain(range_domain = range_d)
363
+ or
364
+ >>> list_d = {'1': 'value1', '2': 'value2'}
365
+ >>> await field.update_domain(list_domain=list_d)
366
+ """
367
+ if not self.domain:
368
+ self.domain = {'min': None, 'max': None, 'items': {}}
369
+
370
+ if range_domain:
371
+ self.domain['min'] = range_domain['min']
372
+ self.domain['max'] = range_domain['max']
373
+
374
+ if list_domain:
375
+ self.domain['items'] = {**self.domain['items'], **list_domain}
376
+
377
+ await self.update(domain=self.domain)
378
+ return self.domain
379
+
380
+
381
+ def to_sync(self, sync_client: 'GeoboxClient') -> 'TableField':
382
+ """
383
+ Switch to sync version of the table field instance to have access to the sync methods
384
+
385
+ Args:
386
+ sync_client (GeoboxClient): The sync version of the GeoboxClient instance for making requests.
387
+
388
+ Returns:
389
+ TableField: the sync instance of the TableField.
390
+
391
+ Example:
392
+ >>> from geobox import Geoboxclient
393
+ >>> from geobox.aio import AsyncGeoboxClient
394
+ >>> async with AsyncGeoboxClient() as client:
395
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
396
+ >>> field = await table.get_field(field_id=1)
397
+ >>> sync_client = GeoboxClient()
398
+ >>> sync_field = field.to_sync(sync_client)
399
+ """
400
+ from ..table import TableField
401
+
402
+ sync_table = self.table.to_sync(sync_client=sync_client)
403
+ return TableField(table=sync_table, data_type=self.data_type, field_id=self.field_id, data=self.data)
404
+
405
+
406
+
407
+ class AsyncTable(AsyncBase):
408
+
409
+ BASE_ENDPOINT = 'tables/'
410
+
411
+ def __init__(self,
412
+ api: 'AsyncGeoboxClient',
413
+ uuid: str,
414
+ data: Optional[Dict] = {}):
415
+ """
416
+ Initialize a table instance.
417
+
418
+ Args:
419
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
420
+ uuid (str): The unique identifier for the table.
421
+ data (Dict): The response data of the table.
422
+ """
423
+ super().__init__(api, uuid=uuid, data=data)
424
+
425
+
426
+ @classmethod
427
+ async def get_tables(cls, api: 'AsyncGeoboxClient', **kwargs) -> Union[List['AsyncTable'], int]:
428
+ """
429
+ [async] Get list of tables with optional filtering and pagination.
430
+
431
+ Args:
432
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
433
+
434
+ Keyword Args:
435
+ include_settings (bool): Whether to include table settings. default: False
436
+ temporary (bool): Whether to return temporary tables. default: False
437
+ q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
438
+ 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
439
+ search_fields (str): comma separated list of fields for searching
440
+ 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.
441
+ return_count (bool): Whether to return total count. default: False.
442
+ skip (int): Number of items to skip. default: 0
443
+ limit (int): Number of items to return. default: 10
444
+ user_id (int): Specific user. privileges required
445
+ shared (bool): Whether to return shared tables. default: False
446
+
447
+ Returns:
448
+ List[AsyncTable] | int: A list of table instances or the total number of tables.
449
+
450
+ Example:
451
+ >>> from geobox.aio import AsyncGeoboxClient
452
+ >>> from geobox.aio.table import AsyncTable
453
+ >>> async with AsyncGeoboxClient() as client:
454
+ >>> tables = await client.get_tables(q="name LIKE '%My table%'")
455
+ or
456
+ >>> tables = await AsyncTable.get_tables(client, q="name LIKE '%My table%'")
457
+ """
458
+ params = {
459
+ 'f': 'json',
460
+ 'include_settings': kwargs.get('include_settings', False),
461
+ 'temporary': kwargs.get('temporary'),
462
+ 'q': kwargs.get('q'),
463
+ 'search': kwargs.get('search'),
464
+ 'search_fields': kwargs.get('search_fields'),
465
+ 'order_by': kwargs.get('order_by'),
466
+ 'return_count': kwargs.get('return_count', False),
467
+ 'skip': kwargs.get('skip', 0),
468
+ 'limit': kwargs.get('limit', 10),
469
+ 'user_id': kwargs.get('user_id'),
470
+ 'shared': kwargs.get('shared', False)
471
+ }
472
+ return await super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: AsyncTable(api, item['uuid'], item))
473
+
474
+
475
+ @classmethod
476
+ async def create_table(cls,
477
+ api: 'AsyncGeoboxClient',
478
+ name: str,
479
+ display_name: Optional[str] = None,
480
+ description: Optional[str] = None,
481
+ temporary: bool = False,
482
+ fields: Optional[List[Dict]] = None,
483
+ ) -> 'AsyncTable':
484
+ """
485
+ [async] Create a new table.
486
+
487
+ Args:
488
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
489
+ name (str): The name of the AsyncTable.
490
+ display_name (str, optional): The display name of the table.
491
+ description (str, optional): The description of the table.
492
+ temporary (bool, optional): Whether to create a temporary tables. default: False
493
+ fields (List[Dict], optional): raw table fields. you can use create_field method for simpler and safer field addition. required dictionary keys: name, datatype
494
+
495
+ Returns:
496
+ AsyncTable: The newly created table instance.
497
+
498
+ Raises:
499
+ ValidationError: If the table data is invalid.
500
+
501
+ Example:
502
+ >>> from geobox.aio import AsyncGeoboxClient
503
+ >>> from geobox.aio.table import AsyncTable
504
+ >>> async with AsyncGeoboxClient() as client:
505
+ >>> table = await client.create_table(name="my_table")
506
+ or
507
+ >>> table = await AsyncTable.create_table(client, name="my_table")
508
+ """
509
+ data = {
510
+ "name": name,
511
+ "display_name": display_name,
512
+ "description": description,
513
+ "temporary": temporary,
514
+ "fields": fields,
515
+ }
516
+ return await super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: AsyncTable(api, item['uuid'], item))
517
+
518
+
519
+ @classmethod
520
+ async def get_table(cls, api: 'AsyncGeoboxClient', uuid: str, user_id: int = None) -> 'AsyncTable':
521
+ """
522
+ [async] Get a table by UUID.
523
+
524
+ Args:
525
+ api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
526
+ uuid (str): The UUID of the table to get.
527
+ user_id (int): Specific user. privileges required.
528
+
529
+ Returns:
530
+ AsyncTable: The AsyncTable object.
531
+
532
+ Raises:
533
+ NotFoundError: If the table with the specified UUID is not found.
534
+
535
+ Example:
536
+ >>> from geobox.aio import AsyncGeoboxClient
537
+ >>> from geobox.aio.table import AsyncTable
538
+ >>> async with AsyncGeoboxClient() as client:
539
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
540
+ or
541
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
542
+ """
543
+ params = {
544
+ 'f': 'json',
545
+ 'user_id': user_id,
546
+ }
547
+ return await super()._get_detail(api, cls.BASE_ENDPOINT, uuid, params, factory_func=lambda api, item: AsyncTable(api, item['uuid'], item))
548
+
549
+
550
+ @classmethod
551
+ async def get_table_by_name(cls, api: 'AsyncGeoboxClient', name: str, user_id: int = None) -> Union['AsyncTable', None]:
552
+ """
553
+ [async] Get a table by name
554
+
555
+ Args:
556
+ api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
557
+ name (str): the name of the table to get
558
+ user_id (int, optional): specific user. privileges required.
559
+
560
+ Returns:
561
+ AsyncTable | None: returns the table if a table matches the given name, else None
562
+
563
+ Example:
564
+ >>> from geobox.aio import AsyncGeoboxClient
565
+ >>> from geobox.aio.table import AsyncTable
566
+ >>> async with AsyncGeoboxClient() as client:
567
+ >>> table = await client.get_table_by_name(name='test')
568
+ or
569
+ >>> table = await AsyncTable.get_table_by_name(client, name='test')
570
+ """
571
+ tables = await cls.get_tables(api, q=f"name = '{name}'", user_id=user_id)
572
+ if tables and tables[0].name == name:
573
+ return tables[0]
574
+ else:
575
+ return None
576
+
577
+
578
+ async def update(self, **kwargs) -> Dict:
579
+ """
580
+ [async] Update the table.
581
+
582
+ Keyword Args:
583
+ name (str): The name of the table.
584
+ display_name (str): The display name of the table.
585
+ description (str): The description of the table.
586
+
587
+ Returns:
588
+ Dict: The updated table data.
589
+
590
+ Raises:
591
+ ValidationError: If the table data is invalid.
592
+
593
+ Example:
594
+ >>> from geobox.aio import AsyncGeoboxClient
595
+ >>> from geobox.aio.table import AsyncTable
596
+ >>> async with AsyncGeoboxClient() as client:
597
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
598
+ >>> await table.update_table(display_name="New Display Name")
599
+ """
600
+ data = {
601
+ "name": kwargs.get('name'),
602
+ "display_name": kwargs.get('display_name'),
603
+ "description": kwargs.get('description'),
604
+ }
605
+ return await super()._update(self.endpoint, data)
606
+
607
+
608
+ async def delete(self) -> None:
609
+ """
610
+ [async] Delete the AsyncTable.
611
+
612
+ Returns:
613
+ None
614
+
615
+ Example:
616
+ >>> from geobox.aio import AsyncGeoboxClient
617
+ >>> from geobox.aio.table import AsyncTable
618
+ >>> async with AsyncGeoboxClient() as client:
619
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
620
+ >>> await table.delete()
621
+ """
622
+ await super()._delete(self.endpoint)
623
+
624
+
625
+ @property
626
+ async def settings(self) -> Dict:
627
+ """
628
+ [async] Get the table's settings.
629
+
630
+ Returns:
631
+ Dict: The table settings.
632
+
633
+ Example:
634
+ >>> from geobox.aio import AsyncGeoboxClient
635
+ >>> from geobox.aio.table import AsyncTable
636
+ >>> async with AsyncGeoboxClient() as client:
637
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
638
+ >>> setting = await table.settings
639
+ """
640
+ return await super()._get_settings(endpoint=self.endpoint)
641
+
642
+
643
+ async def update_settings(self, settings: Dict) -> Dict:
644
+ """
645
+ [async] Update the settings
646
+
647
+ settings (Dict): settings dictionary
648
+
649
+ Returns:
650
+ Dict: updated settings
651
+
652
+ Example:
653
+ >>> from geobox.aio import AsyncGeoboxClient
654
+ >>> async with AsyncGeoboxClient() as client:
655
+ >>> table1 = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
656
+ >>> table2 = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
657
+ >>> await table1.update_settings(table2.settings)
658
+ """
659
+ return await super()._set_settings(self.endpoint, settings)
660
+
661
+
662
+ async def get_fields(self) -> List['AsyncTableField']:
663
+ """
664
+ [async] Get all fields of the table.
665
+
666
+ Returns:
667
+ List[AsyncTableField]: A list of Field instances representing the table's fields.
668
+
669
+ Example:
670
+ >>> from geobox.aio import AsyncGeoboxClient
671
+ >>> from geobox.aio.table import AsyncTable
672
+ >>> async with AsyncGeoboxClient() as client:
673
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
674
+ >>> fields = await table.get_fields()
675
+ """
676
+ endpoint = urljoin(self.endpoint, 'fields/')
677
+ return await super()._get_list(
678
+ api=self.api,
679
+ endpoint=endpoint,
680
+ factory_func=lambda api, item: AsyncTableField(table=self, data_type=FieldType(item['datatype']), field_id=item['id'], data=item),
681
+ )
682
+
683
+
684
+ async def get_field(self, field_id: int) -> 'AsyncTableField':
685
+ """
686
+ [async] Get a specific field by ID.
687
+
688
+ Args:
689
+ field_id (int, optional): The ID of the field to retrieve.
690
+
691
+ Returns:
692
+ AsyncTableField: The requested field instance.
693
+
694
+ Raises:
695
+ NotFoundError: If the field with the specified ID is not found.
696
+
697
+ Example:
698
+ >>> from geobox.aio import AsyncGeoboxClient
699
+ >>> from geobox.aio.table import AsyncTable
700
+ >>> async with AsyncGeoboxClient() as client:
701
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
702
+ >>> field = await table.get_field(field_id=1)
703
+ """
704
+ field = next((f for f in await self.get_fields() if f.id == field_id), None)
705
+ if not field:
706
+ raise NotFoundError(f'Field with ID {field_id} not found in table {self.name}')
707
+
708
+ return field
709
+
710
+
711
+ async def get_field_by_name(self, name: str) -> 'AsyncTableField':
712
+ """
713
+ [async] Get a specific field by name.
714
+
715
+ Args:
716
+ name (str): The name of the field to retrieve.
717
+
718
+ Returns:
719
+ AsyncTableField: The requested field instance.
720
+
721
+ Raises:
722
+ NotFoundError: If the field with the specified name is not found.
723
+
724
+ Example:
725
+ >>> from geobox.aio import AsyncGeoboxClient
726
+ >>> from geobox.aio.table import AsyncTable
727
+ >>> async with AsyncGeoboxClient() as client:
728
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
729
+ >>> field = await table.get_field_by_name(name='test')
730
+ """
731
+ field = next((f for f in await self.get_fields() if f.name == name), None)
732
+ if not field:
733
+ raise NotFoundError(f"Field with name '{name}' not found in table {self.name}")
734
+
735
+ return field
736
+
737
+
738
+ async def add_field(self, name: str, data_type: 'FieldType', data: Dict = {}) -> 'AsyncTableField':
739
+ """
740
+ [async] Add a new field to the table.
741
+
742
+ Args:
743
+ name (str): The name of the new field.
744
+ data_type (FieldType): The data type of the new field.
745
+ data (Dict, optional): Additional field properties (display_name, description, etc.).
746
+
747
+ Returns:
748
+ Field: The newly created field instance.
749
+
750
+ Raises:
751
+ ValidationError: If the field data is invalid.
752
+
753
+ Example:
754
+ >>> from geobox.aio import AsyncGeoboxClient
755
+ >>> from geobox.aio.table import AsyncTable
756
+ >>> async with AsyncGeoboxClient() as client:
757
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
758
+ >>> field = await table.add_field(name="new_field", data_type=FieldType.String)
759
+ """
760
+ return await AsyncTableField.create_field(self, name=name, data_type=data_type, data=data)
761
+
762
+
763
+ async def calculate_field(self,
764
+ target_field: str,
765
+ expression: str,
766
+ q: Optional[str] = None,
767
+ search: Optional[str] = None,
768
+ search_fields: Optional[str] = None,
769
+ row_ids: Optional[str] = None,
770
+ run_async: bool = True,
771
+ user_id: Optional[int] = None,
772
+ ) -> Union['AsyncTask', Dict]:
773
+ """
774
+ [async] Calculate values for a field based on an expression.
775
+
776
+ Args:
777
+ target_field (str): The field to calculate values for.
778
+ expression (str): The expression to use for calculation.
779
+ q (str, optional): Query to filter features. default: None.
780
+ search (str, optional): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value
781
+ search_fields (str, optional): comma separated list of fields for searching
782
+ row_ids (str, optional): List of specific row IDs to include. default: None
783
+ run_async (bool, optional): Whether to run the calculation asynchronously. default: True.
784
+ user_id (int, optional): Specific user. privileges required.
785
+
786
+ Returns:
787
+ Task | Dict: The task instance of the calculation operation or the api response if the run_async=False.
788
+
789
+ Example:
790
+ >>> from geobox.aio import AsyncGeoboxClient
791
+ >>> from geobox.aio.table import AsyncTable
792
+ >>> async with AsyncGeoboxClient() as client:
793
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
794
+ >>> task = await table.calculate_field(target_field="target_field",
795
+ ... expression="expression",
796
+ ... q="name like 'my_layer'",
797
+ ... row_ids=[1, 2, 3],
798
+ ... run_async=True)
799
+ """
800
+ data = clean_data({
801
+ "target_field": target_field,
802
+ "expression": expression,
803
+ "q": q,
804
+ "search": search,
805
+ "search_fields": search_fields,
806
+ "row_ids": row_ids,
807
+ "run_async": run_async,
808
+ "user_id": user_id
809
+ })
810
+
811
+ endpoint = urljoin(self.endpoint, 'calculateField/')
812
+ response = await self.api.post(endpoint, data, is_json=False)
813
+ if run_async:
814
+ task = await AsyncTask.get_task(self.api, response.get('task_id'))
815
+ return task
816
+
817
+ return response
818
+
819
+
820
+ async def get_rows(self, **kwargs) -> List['AsyncTableRow']:
821
+ """
822
+ [async] Query rows of a table
823
+
824
+ Keyword Args:
825
+ q (str): Advanced filtering expression, e.g., 'status = "active" and age > 20'
826
+ search (str): Search term for keyword-based searching among fields/columns
827
+ search_fields (str): Comma separated column names to search in
828
+ row_ids (str): Comma separated list of row ids to filter for
829
+ fields (str): Comma separated column names to include in results, or [ALL]
830
+ exclude (str): Comma separated column names to exclude from result
831
+ order_by (str): Comma separated list for ordering, e.g., 'name A, id D'
832
+ skip (int): Number of records to skip for pagination. default: 0
833
+ limit (int): Maximum number of records to return. default: 100
834
+ return_count (bool): If true, returns only the count of matching rows
835
+ user_id (int): Specific user. privileges required
836
+
837
+ Returns:
838
+ List[AsyncTableRow]: list of table rows objects
839
+
840
+ Example:
841
+ >>> from geobox.aio import AsyncGeoboxClient
842
+ >>> from geobox.aio.table import AsyncTable
843
+ >>> async with AsyncGeoboxClient() as client:
844
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
845
+ or
846
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
847
+
848
+ >>> rows = await table.get_rows()
849
+ """
850
+ params = {
851
+ 'f': 'json',
852
+ 'q': kwargs.get('q'),
853
+ 'search': kwargs.get('search'),
854
+ 'search_fields': kwargs.get('search_fields'),
855
+ 'row_ids': kwargs.get('row_ids'),
856
+ 'fields': kwargs.get('fields'),
857
+ 'exclude': kwargs.get('exclude'),
858
+ 'order_by': kwargs.get('order_by'),
859
+ 'skip': kwargs.get('skip', 0),
860
+ 'limit': kwargs.get('limit', 100),
861
+ 'return_count': kwargs.get('return_count', False),
862
+ 'user_id': kwargs.get('user_id'),
863
+ }
864
+
865
+ endpoint = f'{self.endpoint}rows/'
866
+
867
+ return await super()._get_list(
868
+ api=self.api,
869
+ endpoint=endpoint,
870
+ params=params,
871
+ factory_func=lambda api, item: AsyncTableRow(self, item),
872
+ )
873
+
874
+
875
+ async def get_row(self,
876
+ row_id: int,
877
+ user_id: Optional[int] = None,
878
+ ) -> 'AsyncTableRow':
879
+ """
880
+ [async] Get a row by its id
881
+
882
+ Args:
883
+ row_id (int): the row id
884
+ user_id (int, optional): specific user. privileges required.
885
+
886
+ Returns:
887
+ TanbleRow: the table row instance
888
+
889
+ Example:
890
+ >>> from geobox.aio import AsyncGeoboxClient
891
+ >>> from geobox.aio.table import AsyncTable
892
+ >>> async with AsyncGeoboxClient() as client:
893
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
894
+ or
895
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
896
+
897
+ >>> row = await table.get_row(row_id=1)
898
+ """
899
+ return await AsyncTableRow.get_row(self, row_id, user_id)
900
+
901
+
902
+ async def create_row(self, **kwargs) -> 'AsyncTableRow':
903
+ """
904
+ [async] Create a new row in the table.
905
+
906
+ Each keyword argument represents a field value for the row, where:
907
+ - The keyword is the field name
908
+ - The value is the field value
909
+
910
+ Keyword Args:
911
+ **kwargs: Arbitrary field values matching the table schema.
912
+
913
+ Returns:
914
+ AsyncTableRow: created table row instance
915
+
916
+ Example:
917
+ >>> from geobox.aio import AsyncGeoboxClient
918
+ >>> from geobox.aio.table import AsyncTable
919
+ >>> async with AsyncGeoboxClient() as client:
920
+ >>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
921
+ or
922
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
923
+
924
+ >>> row = await table.create_row(
925
+ ... field1=value1
926
+ ... )
927
+ """
928
+ return await AsyncTableRow.create_row(self, **kwargs)
929
+
930
+
931
+ async def import_rows(self,
932
+ file: 'AsyncFile',
933
+ *,
934
+ file_encoding: str = "utf-8",
935
+ input_dataset: Optional[str] = None,
936
+ delimiter: str = ',',
937
+ has_header: bool = True,
938
+ report_errors: bool = False,
939
+ bulk_insert: bool = True,
940
+ ) -> 'AsyncTask':
941
+ """
942
+ Import rows from a CSV file into a table
943
+
944
+ Args:
945
+ file (AsyncFile): file object to import.
946
+ file_encoding (str, optional): Character encoding of the input file. default: utf-8
947
+ input_dataset (str, optional): Name of the dataset in the input file.
948
+ delimiter (str, optional): the delimiter of the dataset. default: ,
949
+ has_header (bool, optional): Whether the file has header or not. default: True
950
+ report_errors (bool, optional): Whether to report import errors. default: False
951
+ bulk_insert (bool, optional):
952
+
953
+ Returns:
954
+ AsyncTask: The task instance of the import operation.
955
+
956
+ Raises:
957
+ ValidationError: If the import parameters are invalid.
958
+
959
+ Example:
960
+ >>> from geobox.aio import AsyncGeoboxClient
961
+ >>> from geobox.aio.table import AsyncTable
962
+ >>> async with AsyncGeoboxClient() as client:
963
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
964
+ >>> file = await client.get_file(uuid="12345678-1234-5678-1234-567812345678")
965
+ >>> task = await table.import_rows(
966
+ ... file=file,
967
+ ... )
968
+ """
969
+ data = clean_data({
970
+ "file_uuid": file.uuid,
971
+ "file_encoding": file_encoding,
972
+ "input_dataset": file.name if not input_dataset else input_dataset,
973
+ "delimiter": delimiter,
974
+ "has_header": has_header,
975
+ "report_errors": report_errors,
976
+ "bulk_insert": bulk_insert,
977
+ })
978
+
979
+ endpoint = urljoin(self.endpoint, 'import-csv/')
980
+ response = await self.api.post(endpoint, data, is_json=False)
981
+ task = await AsyncTask.get_task(self.api, response.get('task_id'))
982
+ return task
983
+
984
+
985
+ async def export_rows(self,
986
+ out_filename: str,
987
+ *,
988
+ out_format: 'TableExportFormat' = TableExportFormat.CSV,
989
+ q: Optional[str] = None,
990
+ search: Optional[str] = None,
991
+ search_fields: Optional[str] = None,
992
+ row_ids: Optional[str] = None,
993
+ fields: Optional[str] = None,
994
+ exclude: Optional[str] = None,
995
+ order_by: Optional[str] = None,
996
+ zipped: bool = False,
997
+ run_async: bool = True,
998
+ ) -> Union['AsyncTask', str]:
999
+ """
1000
+ [async] Export rows of a table to a file
1001
+
1002
+ Args:
1003
+ out_filename (str): Name of the output file without the format (.csv)
1004
+ out_format (TableExportFormat, optional): Format of the output file
1005
+ q (str, optional): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
1006
+ search (str, optional): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value
1007
+ search_fields (str, optional): comma separated list of fields for searching
1008
+ row_ids (str, optional): List of specific row IDs to include
1009
+ fields (str, optional): List of specific field names to include
1010
+ exclude (str, optional): List of specific field names to exclude
1011
+ order_by (str, optional): 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.
1012
+ zipped (str, optional): Whether to compress the output file
1013
+ run_async (bool, optional): Whether to run the export asynchronously. default: True
1014
+
1015
+ Returns:
1016
+ AsyncTask | Dict: The task instance of the export operation (run_async=True) or the export result (run_async=False)
1017
+
1018
+ Raises:
1019
+ ValidationError: If the export parameters are invalid.
1020
+
1021
+ Example:
1022
+ >>> from geobox.aio import AsyncGeoboxClient
1023
+ >>> from geobox.aio.table import AsyncTable
1024
+ >>> async with AsyncGeoboxClient() as client:
1025
+ >>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
1026
+ >>> file = await client.get_file(uuid="12345678-1234-5678-1234-567812345678")
1027
+ >>> task = await table.export_rows(
1028
+ ... file=file,
1029
+ ... )
1030
+ """
1031
+ data = clean_data({
1032
+ "out_filename": out_filename,
1033
+ "out_format": out_format.value if out_format else None,
1034
+ "q": q,
1035
+ "search": search,
1036
+ "search_fields": search_fields,
1037
+ "row_ids": row_ids,
1038
+ "fields": fields,
1039
+ "exclude": exclude,
1040
+ "order_by": order_by,
1041
+ "zipped": zipped,
1042
+ "run_async": run_async,
1043
+ })
1044
+
1045
+ endpoint = urljoin(self.endpoint, 'export/')
1046
+ response = await self.api.post(endpoint, data, is_json=False)
1047
+ if run_async:
1048
+ task = await AsyncTask.get_task(self.api, response.get('task_id'))
1049
+ return task
1050
+
1051
+ return response
1052
+
1053
+
1054
+ async def share(self, users: List['AsyncUser']) -> None:
1055
+ """
1056
+ [async] Shares the table with specified users.
1057
+
1058
+ Args:
1059
+ users (List[AsyncUser]): The list of user objects to share the table with.
1060
+
1061
+ Returns:
1062
+ None
1063
+
1064
+ Example:
1065
+ >>> from geobox.aio import AsyncGeoboxClient
1066
+ >>> from geobox.aio.table import AsyncTable
1067
+ >>> async with AsyncGeoboxClient() as client:
1068
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
1069
+ >>> users = await client.search_users(search='John')
1070
+ >>> await table.share(users=users)
1071
+ """
1072
+ await super()._share(self.endpoint, users)
1073
+
1074
+
1075
+ async def unshare(self, users: List['AsyncUser']) -> None:
1076
+ """
1077
+ [async] Unshares the table with specified users.
1078
+
1079
+ Args:
1080
+ users (List[User]): The list of user objects to unshare the table with.
1081
+
1082
+ Returns:
1083
+ None
1084
+
1085
+ Example:
1086
+ >>> from geobox.aio import AsyncGeoboxClient
1087
+ >>> from geobox.aio.table import AsyncTable
1088
+ >>> async with AsyncGeoboxClient() as client:
1089
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
1090
+ >>> users = await client.search_users(search='John')
1091
+ >>> await table.unshare(users=users)
1092
+ """
1093
+ await super()._unshare(self.endpoint, users)
1094
+
1095
+
1096
+ async def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['AsyncUser']:
1097
+ """
1098
+ [async] Retrieves the list of users the table is shared with.
1099
+
1100
+ Args:
1101
+ search (str, optional): The search query.
1102
+ skip (int, optional): The number of users to skip.
1103
+ limit (int, optional): The maximum number of users to retrieve.
1104
+
1105
+ Returns:
1106
+ List[AsyncUser]: The list of shared users.
1107
+
1108
+ Example:
1109
+ >>> from geobox.aio import AsyncGeoboxClient
1110
+ >>> from geobox.aio.table import AsyncTable
1111
+ >>> async with AsyncGeoboxClient() as client:
1112
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
1113
+ >>> await table.get_shared_users(search='John', skip=0, limit=10)
1114
+ """
1115
+ params = {
1116
+ 'search': search,
1117
+ 'skip': skip,
1118
+ 'limit': limit
1119
+ }
1120
+ return await super()._get_shared_users(self.endpoint, params)
1121
+
1122
+
1123
+ def to_sync(self, sync_client: 'GeoboxClient') -> 'Table':
1124
+ """
1125
+ Switch to sync version of the table instance to have access to the sync methods
1126
+
1127
+ Args:
1128
+ sync_client (GeoboxClient): The sync version of the GeoboxClient instance for making requests.
1129
+
1130
+ Returns:
1131
+ Table: the sync instance of the table.
1132
+
1133
+ Example:
1134
+ >>> from geobox import Geoboxclient
1135
+ >>> from geobox.aio import AsyncGeoboxClient
1136
+ >>> from geobox.aio.table import AsyncTable
1137
+ >>> async with AsyncGeoboxClient() as client:
1138
+ >>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
1139
+ >>> sync_client = GeoboxClient()
1140
+ >>> sync_table = table.to_async(sync_client)
1141
+ """
1142
+ from ..table import Table
1143
+
1144
+ return Table(api=sync_client, uuid=self.uuid, data=self.data)