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